Yi's Blog

思绪来得快,去得也快

面试中被问到的 iOS 开发相关的问题

说来惭愧,已经面了几个公司的 iOS 开发岗位,几次被问倒——有些问题回答得比较含糊,有些问题只知道结果,不知道原理。根据回忆,梳理一下不太明白的一些问题,尽管对问题的解答只是点到为止,也算为以后的学习留点线索。

1. 基础知识

1.1 为什么使用 Category?

问题1:使用继承可以完成的 Category 的特性,为什么还要设计出 Category?

Category的使用场景:

  • 当你在定义类的时候,在某些情况下(例如需求变更),你可能想要为其中的某个或几个类中添加方法。
  • 一个类中包含了许多不同的方法需要实现,而这些方法需要不同的团队成员实现
  • 当你在使用基础类库中的类时,你可能希望这些类实现一些你需要的方法。

引自:Objective-C——消息、Category和Protocol - 池建强 - 博客园

1.2 如何手写一段 Protocal 的代码?

@protocol A
@optional
- (void)methodA;
@end

@protocol B < A >
- (void)methodB;
@end

1.3 为什么在 init 函数中执行 self = [super init]?

一直没思考过这个问题,去面试的时候被问到了,才意识到这里其实是有些门道的。示例代码:

- (id)init {
	self = [super init];  // Call a designated initializer here.
	if (self != nil) {
	}
	return self;
}

问题 1:首先,这样传递有没有问题?

既然官方的文档都是这样写的,这样传递就是没有问题。具体为什么没有问题,我猜测是子类的内容分配的前部和父类是相同的,所以父类只是把内存中属于自己负责的那部分内存初始化了,所以可以传递给子类。

问题 2:然后,为什么要这么赋值?self 在传递进来的时候就不是 nil 了,已经是经过了 [ClassName alloc] 函数分配好了空间的指针,所以,从常规上来看直接执行 [super init] 就可以了,并不需要把 [super init] 的返回值传递给 self,但是这里为什么要赋值呢?

我们先分以下几种情况分别分析:(假设 superSelf 是 [super init] 的返回值)

  1. superSelf == nil,此时父类初始化失败,self随之被赋值为nil并返回,表现正常。
  2. superSelf == self,大部分类的初始化都是这个结果。此时赋值没有任何影响。 
  3. superSelf != self,这种情况正是大部分人疑惑的地方。执行self = [super init] 之后,我们创建的对象将会被重定向到另外一块内存上。

问题 3:什么时候父类返回的指针会指向另一块内存呢?

  • 父类是实现了单例模式的类时,父类返回的就是同一块内存(不建议这样使用)
  • ClassClusters(类簇),初始化方法返回了不同的子类。
NSString *str1 = [NSString alloc];
NSString *str2 = [str1 initWithString:@"hello"]; 
  • 共享。由于 NSNumber 是创建之后就不能修改的对象,所以 Foundation 在这里做了一些优化,相同数值的 NSNumber 对象将共享同一块内存。
NSNumber *n1 = [[NSNumber alloc] initWithInt:1];
NSNumber *n2 = [[NSNumber alloc] initWithInt:1]; 

到这里,问题应该就已经说清楚了。

引自: 关于 self = [super init]; - 苹果君的工作室 - 博客频道 - CSDN.NET

1.4 应用的生命周期是怎么样的?有哪几种状态?

iOS 4.0 引入了后台运行以后,一共有 5 种状态:

State Meaning
Non-running The app is not running.
Inactive The app is running in the foreground, but not receiving events. An iOS app can be placed into an inactive state, for example, when a call or SMS message is received.
Active The app is running in the foreground, and receiving events.
Background The app is running in the background, and executing code.
Suspended The app is in the background, but no code is being executed.

From: Execution States for Apps

2. 内存管理

2.1 简单介绍引用计数的原理和实现以及自动引用计数

想要简单说清楚还是挺困难的,参见《Objective-C高级编程》第一章吧。

2.2 手写代码或者用 xib 中创建的 View 应该用 strong 还是 weak 来修饰?

无论使用的是 strong 还是 weak,在将 UIView 添加到另一个 UIView 的 subview 中时,引用计数就已经被置为 1 了。那为什么还要使用 strong 标签呢?

我觉得这主要取决于你的用法,如果这个 UIView 你可能会把它从 UIView hierarchy 中移除并再添加进去的话,就需要使用 strong 来保留住这个 UIView。

2.3 以下情况会造成内容泄露吗?

示例代码:

- (void)someMethod {
	obj1 = [[ClassA alloc] init];
	obj2 = [[ClassB alloc] init];
	obj1.delegate = obj2;
	obj2.delegate = obj1;
}

会造成循环引用。

2.4 为什么要使用 autoreleasepool?

这个问题真把我难倒了,搜索了一下觉得以下的解答还是比较有说服力的:

Avoiding the autoreleasepool is a bad advice, the coin has two sides. Using autoreleased objects carries a certain amount of overhead (although insignificant in most scenarios) that should be avoided when possible. Especially in cases where there are multiple exits to a method, or an exception can be encountered, autoreleasing helps avoiding memory leaks and makes code cleaner.

From: objective c - Why use Autorelease pool? - Stack Overflow

使用 autoreleased 对象会造成一定的内存的开支,但是对于有多个返回的函数,或者需要处理异常的函数,使用 autorelease 可以避免内存的泄露并让代码更整洁。

3. 多线程

3.1 有哪些实现异步的方法?

  • 使用 NSObject 类的方法 performSelectorInBackground:withObject: 来创建一个线程
[Object performSelectorInBackground:@selector(doSomething:) withObject:nil];
  • 使用 NSThread 实现多线程
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];

NSThread* myThread = [[NSThread alloc] initWithTarget:self   selector:@selector(doSomething:) object:nil];
[myThread start];

3.2 不同线程之间的 Notification 是否可以相互传递?为什么?

In a multithreaded application, notifications are always delivered in the thread in which the notification was posted, which may not be the same thread in which an observer registered itself.

From: Notification Programming Topics: Notification Centers

每个线程都有一个自己的 Notifications QueueNSNotificationCenter 只能将内容只能将 Notification 发送到 post 时所在线程中的 Notification Queue 中。如果要把 Notification 发送给所有的线程的话,涉及到 Queue 的线程安全,而目前的实现中并不能保证线程安全,因此不能发送给多个线程。

如果只是简单地想把后台的信息发送给主线程的话,可以在后台线程中执行:

dispatch_async(dispatch_get_main_queue(),^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"ImageRetrieved" 
                                                    object:nil 
                                               userInfo:imageDict];
});

如果想要让指定的线程接收 Notification,可以参见苹果的文档:Notification Programming Topics: Delivering Notifications To Particular Threads

以上就是我被问到的一些回答得比较含糊的问题,想要不被人问倒,还有很多要学的东西。

- EOF -