| 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么? |
答案是:在ARC和MRC下均不需要。
以下代码摘自2011,Apple API官方文档 - Associative References:(在MRC下)
static char overviewKey;
NSArray *array =
[[NSArray alloc] initWithObjects:@"One", @"Two", @"Three", nil];
// For the purposes of illustration, use initWithFormat: to ensure
// the string can be deallocated
NSString *overview =
[[NSString alloc] initWithFormat:@"%@", @"First three numbers"];
objc_setAssociatedObject (
array,
&overviewKey,
overview,
OBJC_ASSOCIATION_RETAIN
);
[overview release];
// (1) overview valid
[array release];
// (2) overview invalid
文档中指明:当宿主对象被销毁时,其所关联的对象随后也会被销毁。
根据WWDC 2011,Session 332中发布的内存销毁时间表(由微博@iOS程序犭袁翻译)可知具体的时刻:
//于2017.8.9记
//计算引用计数的源码
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
inline uintptr_t objc_object::rootRetainCount() {
isa_t bits = LoadExclusive(&isa.bits);
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
return rc;
}
/*
从运行时源码可以知道,retainCount有三部分组成:
- 1
- bits.extra_rc
- sidetable_getExtraRC_nolock
可以清楚知道,引用计数min为1.
*/
1. 调用 -release :引用计数变为零(勘误:当对象的引用计数为1时,接收到release消息时,引用计数将不会改变,因为系统已经明确知道该对象将被销毁,改变它的引用计数已经毫无意义。(此处的关于引用计数的结果是通过调用retainCount来获取的,然而在https://developer.apple.com/documentation/objectivec/1418956-nsobject/1571952-retaincount)这里官方说明了这个方法是不可靠的。故这里的论述并不恰当,目前任未找到合适的方法取得正确的RC -- 2017-8-9记)(于2017-7-17改)
* 对象正在被销毁,生命周期即将结束.
* 不能再有新的 __weak 弱引用, 否则将指向 nil.
* 调用 [self dealloc]
2. 子类 调用 -dealloc
* 继承关系中最底层的子类 在调用 -dealloc
* 如果是 MRC 代码 则会手动释放实例变量们(iVars)
* 继承关系中每一层的父类 都在调用 -dealloc
3. NSObject 调 -dealloc
* 只做一件事:调用 Objective-C runtime 中的 object_dispose() 方法
4. 调用 object_dispose()
* 为 C++ 的实例变量们(iVars)调用 destructors
* 为 ARC 状态下的 实例变量们(iVars) 调用 -release
* 解除所有使用 runtime Associate方法关联的对象
* 解除所有 __weak 引用
* 调用 free()
亦即被关联的对象在生命周期内要比宿主对象本身释放晚很多。
| _objc_msgForward函数是做什么的,直接调用它将会发生什么? |
由于解答者的篇幅过大,这里简明扼要给出解答,更多详情请猛戳文章后面所附上的文章链接。
当向一个对象发送一条消息时,如果该对象所属类没有实现该方法,运行时就会使用objc_msgForward()函数进行消息转发,即Fast forwarding、Normal forwarding。在消息转发之前,这个函数指针会成为消息的IMP,进行消息转发时则执行IMP。
直接使用此函数是很危险的,如果你没有为Fast forwarding、Normal forwarding做相关支持的话,程序就会崩溃。但是如果你是故意为之的话,它可以帮助我们实现一些很Cool的东西,一个典型代表就是JSPatch.(非常不安全!易被不法分子利用,现在所有涉及到这个框架的App已经全部下架。)
JSPatch以小巧的体积做到了让JS调用/替换任意OC方法,让iOS APP具备热更新的能力。(现已被禁用,具体原因可自行搜索。)
| runtime如何实现weak变量的自动置nil? |
runtime 会对注册的类进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为键( key),当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil,在这篇文章的第一条tip里对weak类型的对象释放时间表有所交代。(以观察者模式实现)
| 能向编译后得到的类和运行时新建的类中添加实例变量吗? |
- 不能向编译后得到的类中添加实例变量
- 能向运行时新建的类中添加实例变量
前者区别于为对象动态绑定属性,因为动态绑定是针对于单个独立的对象,这是在运行时获得的特质,这种表现与类毫无关系,以下是一段动态绑定的代码:
- (void)setCallBackDelegate:(id<RefreshingCallBack>)callBackDelegate {
objc_setAssociatedObject(self, ((__bridge void *)@"callBackDelegate"), callBackDelegate, OBJC_ASSOCIATION_ASSIGN);//从self就可显而易见,这个属性是对于当前的这个对象
}
下面就是关于前者不可行的原因:
编译后的类是注册在runtime中的,该类用于记录实例变量的链表objc_ivar_list结构体和实例变量的内存大小instance_size已经确定,同时runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong、weak 引用。在Objective-C中类的本质是结构体,所以对象在内存中的排布是确定的,它的大小是不能动态更改的。而所谓的动态绑定机制并不是改变了对象的内存结构。
下面是后者可行的原因:
在你注册新类时调用 class_addIvar 即可达成目的,但前提是在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。如果你想手动实现KVO的话,你会用到这个。
| 以下代码运行结果如何? |
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
执行完 NSLog(@”1”)后,主线程进入死锁状态。
dispatch_sync()是与线程同步执行的函数,造成这段代码死锁的原因是:
viewDidLoad是在主线程中被执行的,即dispatch_get_main_queue(),执行到这个与主线程同步的函数时,它的逻辑代码块(Block),对应于代码中的NSLog(@”2”),就会被同步插入到主线程中,这个函数的返回时机是在逻辑代码块执行完毕后。因为主线程是串行队列,dispatch_sync()的执行时机在viewDidLoad执行完毕之后。而viewDidLoad的执行完毕又依赖于dispatch_sync()的返回,dispatch_sync()的执行返回又依赖于viewDidLoad的任务结束,从而导致死锁,程序将会卡死,将不再执行下去。
参考并整理自:
@微博@iOS程序犭袁:iOSInterviewQuestions
sunnyxx:招聘一个靠谱的iOS
唐巧:Objective-C对象模型及应用

被折叠的 条评论
为什么被折叠?



