iOS开发之高级面试题(三)
小编加了一个不错的面试题和iOS技巧分享群,883872094群资料可以自取。分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!希望帮助开发者少走弯路。
索引
-
21\. @implementation Son : Father - (id)init { self = [super init]; if (self) { NSLog(@"%@", NSStringFromClass([self class])); NSLog(@"%@", NSStringFromClass([super class])); } return self; } @end
下面的代码输出什么?
-
-
22. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
-
23. 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
-
24. objc中的类方法和实例方法有什么本质区别和联系?
-
30. 以+ scheduledTimerWithTimeInterval...的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?
-
34. 不手动指定autoreleasepool的前提下,一个autorealese对象在什么时刻释放?(比如在一个vc的viewDidLoad中创建)
```source-objc
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
```
-
45. addObserver:forKeyPath:options:context:各个参数的作用分别是什么,observer中需要实现哪个方法才能获得KVO回调?
-
47. 若一个类有实例变量 NSString *_foo ,调用setValue:forKey:时,可以以foo还是 _foo 作为key?
21. 下面的代码输出什么?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
答案:
都输出 Son
NSStringFromClass([self class]) = SonNSStringFromClass([super class]) = Son
这个题目主要是考察关于 Objective-C 中对 self 和 super 的理解。
我们都知道:self 是类的隐藏参数,指向当前调用方法的这个类的实例。那 super 呢?
很多人会想当然的认为“ super 和 self 类似,应该是指向父类的指针吧!”。这是很普遍的一个误区。其实 super 是一个 Magic Keyword, 它本质是一个编译器标示符,和 self 是指向的同一个消息接受者!他们两个的不同点在于:super 会告诉编译器,调用 class 这个方法时,要去父类的方法,而不是本类里的。
上面的例子不管调用[self class]
还是[super class]
,接受消息的对象都是当前 Son *xxx
这个对象。
当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当使用 super 时,则从父类的方法列表中开始找。然后调用父类的这个方法。
这也就是为什么说“不推荐在 init 方法中使用点语法”,如果想访问实例变量 iVar 应该使用下划线( _iVar
),而非点语法( self.iVar
)。
点语法( self.iVar
)的坏处就是子类有可能覆写 setter 。假设 Person 有一个子类叫 ChenPerson,这个子类专门表示那些姓“陈”的人。该子类可能会覆写 lastName 属性所对应的设置方法:
//
// ChenPerson.m
//
//
// Created by https://github.com/ChenYilong on 15/8/30.
// Copyright (c) 2015年 http://weibo.com/luohanchenyilong/ 微博@iOS程序犭袁. All rights reserved.
//
#import "ChenPerson.h"
@implementation ChenPerson
@synthesize lastName = _lastName;
- (instancetype)init
{
self = [super init];
if (self) {
NSLog(@"类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, NSStringFromClass([self class]));
NSLog(@"类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, NSStringFromClass([super class]));
}
return self;
}
- (void)setLastName:(NSString*)lastName
{
//设置方法一:如果setter采用是这种方式,就可能引起崩溃
// if (![lastName isEqualToString:@"陈"])
// {
// [NSException raise:NSInvalidArgumentException format:@"姓不是陈"];
// }
// _lastName = lastName;
//设置方法二:如果setter采用是这种方式,就可能引起崩溃
_lastName = @"陈";
NSLog(@"类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, @"会调用这个方法,想一下为什么?");
}
@end
在基类 Person 的默认初始化方法中,可能会将姓氏设为空字符串。此时若使用点语法( self.lastName
)也即 setter 设置方法,那么调用将会是子类的设置方法,如果在刚刚的 setter 代码中采用设置方法一,那么就会抛出异常,
为了方便采用打印的方式展示,究竟发生了什么,我们使用设置方法二。
如果基类的代码是这样的:
//
// Person.m
// nil对象调用点语法
//
// Created by https://github.com/ChenYilong on 15/8/29.
// Copyright (c) 2015年 http://weibo.com/luohanchenyilong/ 微博@iOS程序犭袁. All rights reserved.
//
#import "Person.h"
@implementation Person
- (instancetype)init
{
self = [super init];
if (self) {
self.lastName = @"";
//NSLog(@"类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, NSStringFromClass([self class]));
//NSLog(@"类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, self.lastName);
}
return self;
}
- (void)setLastName:(NSString*)lastName
{
NSLog(@"类名与方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, @"根本不会调用这个方法");
_lastName = @"炎黄";
}
@end
那么打印结果将会是这样的:
类名与方法名:-[ChenPerson setLastName:](在第36行),描述:会调用这个方法,想一下为什么?
类名与方法名:-[ChenPerson init](在第19行),描述:ChenPerson
类名与方法名:-[ChenPerson init](在第20行),描述:ChenPerson
我在仓库里也给出了一个相应的 Demo(名字叫:Demo_21题_下面的代码输出什么)。有兴趣可以跑起来看一下,主要看下他是怎么打印的,思考下为什么这么打印。
接下来让我们利用 runtime 的相关知识来验证一下 super 关键字的本质,使用clang重写命令:
$ clang -rewrite-objc test.m
将这道题目中给出的代码被转化为:
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("Son")) }, sel_registerName("class"))));
从上面的代码中,我们可以发现在调用 [self class] 时,会转化成 objc_msgSend
函数。看下函数定义:
id objc_msgSend(id self, SEL op, ...)
我们把 self 做为第一个参数传递进去。
而在调用 [super class]时,会转化成 objc_msgSendSuper
函数。看下函数定义:
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
第一个参数是 objc_super
这样一个结构体,其定义如下:
struct objc_super {
__unsafe_unretained id receiver;
__unsafe_unretained Class super_class;
};
结构体有两个成员,第一个成员是 receiver, 类似于上面的 objc_msgSend
函数第一个参数self 。第二个成员是记录当前类的父类是什么。
所以,当调用 [self class] 时,实际先调用的是 objc_msgSend
函数,第一个参数是 Son当前的这个实例,然后在 Son 这个类里面去找 - (Class)class这个方法,没有,去父类 Father里找,也没有,最后在 NSObject类中发现这个方法。而 - (Class)class的实现就是返回self的类别,故上述输出结果为 Son。
objc Runtime开源代码对- (Class)class方法的实现:
- (Class)class {
return object_getClass(self);
}
而当调用 [super class]
时,会转换成objc_msgSendSuper函数
。第一步先构造 objc_super
结构体,结构体第一个成员就是 self
。 第二个成员是 (id)class_getSuperclass(objc_getClass(“Son”))
, 实际该函数输出结果为 Father。
第二步是去 Father这个类里去找 - (Class)class
,没有,然后去NSObject类去找,找到了。最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class))
去调用,
此时已经和[self class]
调用相同了,故上述输出结果仍然返回 Son。
参考链接:微博@Chun_iOS的博文刨根问底Objective-C Runtime(1)- Self & Super
22. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
23. 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
-
在ARC下不需要。
-
在MRC中,对于使用retain或copy策略的需要 。在MRC下也不需要
无论在MRC下还是ARC下均不需要。
2011年版本的Apple API 官方文档 - Associative References 一节中有一个MRC环境下的例子:
// 在MRC下,使用runtime Associate方法关联的对象,不需要在主对象dealloc的时候释放
// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong
// 摘自2011年版本的Apple API 官方文档 - Associative References
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
文档指出
At point 1, the string
overview
is still valid because theOBJC_ASSOCIATION_RETAIN
policy specifies that the array retains the associated object. When the array is deallocated, however (at point 2),overview
is released and so in this case also deallocated.
我们可以看到,在[array release];
之后,overview就会被release释放掉了。
既然会被销毁,那么具体在什么时间点?
根据 WWDC 2011, Session 322 (第36分22秒) 中发布的内存销毁时间表,被关联的对象在生命周期内要比对象本身释放的晚很多。它们会在被 NSObject -dealloc 调用的 object_dispose() 方法中释放。
对象的内存销毁时间表,分四个步骤:
// 对象的内存销毁时间表// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)// https://github.com/ChenYilong// 根据 WWDC 2011, Session 322 (36分22秒)中发布的内存销毁时间表 1. 调用 -release :引用计数变为零 * 对象正在被销毁,生命周期即将结束. * 不能再有新的 __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()
对象的内存销毁时间表:参考链接。
24. objc中的类方法和实例方法有什么本质区别和联系?
类方法:
- 类方法是属于类对象的
- 类方法只能通过类对象调用
- 类方法中的self是类对象
- 类方法可以调用其他的类方法
- 类方法中不能访问成员变量
- 类方法中不能直接调用对象方法
实例方法:
- 实例方法是属于实例对象的
- 实例方法只能通过实例对象调用
- 实例方法中的self是实例对象
- 实例方法中可以访问成员变量
- 实例方法中直接调用实例方法
- 实例方法中也可以调用类方法(通过类名)
25
_objc_msgForward
函数是做什么的,直接调用它将会发生什么?
_objc_msgForward
是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,
_objc_msgForward
会尝试做消息转发。
我们可以这样创建一个
_objc_msgForward
对象:
IMP msgForwardIMP = _objc_msgForward;
在上篇中的《objc中向一个对象发送消息
[obj foo]
和
objc_msgSend()
函数之间有什么关系?》曾提到
objc_msgSend
在“消息传递”中的作用。在“消息传递”过程中,
objc_msgSend
的动作比较清晰:首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),如果没找到,则向父类的 Class 查找。如果一直查找到根类仍旧没有实现,则用
_objc_msgForward
函数指针代替 IMP 。最后,执行这个 IMP 。
Objective-C运行时是开源的,所以我们可以看到它的实现。打开 Apple Open Source 里Mac代码里的obj包下载一个最新版本,找到
objc-runtime-new.mm,
进入之后搜索
_objc_msgForward
。
里面有对
_objc_msgForward
的功能解释:
/***********************************************************************
- lookUpImpOrForward.
- The standard IMP lookup.
- initialize==NO tries to avoid +initialize (but sometimes fails)
- cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
- Most callers should use initialize==YES and cache==YES.
- inst is an instance of cls or a subclass thereof, or nil if none is known.
- If cls is an un-initialized metaclass then a non-nil inst is faster.
- May return _objc_msgForward_impcache. IMPs destined for external use
- must be converted to _objc_msgForward or _objc_msgForward_stret.
- If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
对
objc-runtime-new.mm
文件里与
_objc_msgForward
有关的三个函数使用伪代码展示下:
// objc-runtime-new.mm 文件里与 _objc_msgForward 有关的三个函数使用伪代码展示
// Created by https://github.com/ChenYilong
// Copyright (c) 微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/). All rights reserved.
// 同时,这也是 obj_msgSend 的实现过程
id objc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, ...); //调用这个函数,伪代码...
}
//查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
if (!cls || !sel) return nil;
IMP imp = lookUpImpOrNil(cls, sel);
if (!imp) return _objc_msgForward; //_objc_msgForward 用于消息转发
return imp;
}
IMP lookUpImpOrNil(Class cls, SEL sel) {
if (!cls->initialize()) {
_class_initialize(cls);
}
Class curClass = cls;
IMP imp = nil;
do { //先查缓存,缓存没有时重建,仍旧没有则向父类查询
if (!curClass) break;
if (!curClass->cache) fill_cache(cls, curClass);
imp = cache_getImp(curClass, sel);
if (imp) break;
} while (curClass = curClass->superclass);
return imp;
}
虽然Apple没有公开
_objc_msgForward
的实现源码,但是我们还是能得出结论:
_objc_msgForward
是一个函数指针(和 IMP 的类型一样),是用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,
_objc_msgForward
会尝试做消息转发。
在上篇中的《objc中向一个对象发送消息
[obj foo]
和
objc_msgSend()
函数之间有什么关系?》曾提到
objc_msgSend
在“消息传递”中的作用。在“消息传递”过程中,
objc_msgSend
的动作比较清晰:首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),如果没找到,则向父类的 Class 查找。如果一直查找到根类仍旧没有实现,则用
_objc_msgForward
函数指针代替 IMP 。最后,执行这个 IMP 。
为了展示消息转发的具体动作,这里尝试向一个对象发送一条错误的消息,并查看一下
_objc_msgForward
是如何进行转发的。
首先开启调试模式、打印出所有运行时发送的消息: 可以在代码里执行下面的方法:
(void
)instrumentObjcMessageSends(YES
);
或者断点暂停程序运行,并在 gdb 中输入下面的命令:
call (void
)instrumentObjcMessageSends(YES
)
以第二种为例,操作如下所示:之后,
运行时发送的所有消息都会打印到
/tmp/msgSend-xxxx
文件里了。
终端中输入命令前往:
open /private/tmp
可能看到有多条,找到最新生成的,
双击打开在模拟器上执行执行以下语句(这一套调试方案仅适用于模拟器,真机不可用,关于该调试方案的拓展链接:Can the messages sent to an object in Objective-C be monitored or printed out? ),向一个对象发送一条错误的消息://
// main.m
// CYLObjcMsgForwardTest
//
// Created by http://weibo.com/luohanchenyilong/.
// Copyright (c) 2015年 微博@iOS程序犭袁. All rights reserved.
//
import <UIKit/UIKit.h>
import "AppDelegate.h"
import "CYLTest.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
CYLTest *test = [[CYLTest alloc] init];
[test performSelector:(@selector(iOS程序犭袁))];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
image.png
你可以在
/tmp/msgSend-xxxx
(我这一次是
/tmp/msgSend-9805
)文件里,看到打印出来:
- +CYLTest NSObject initialize
- +CYLTest NSObject alloc
- -CYLTest NSObject init
- -CYLTest NSObject performSelector:
- +CYLTest NSObject resolveInstanceMethod:
- +CYLTest NSObject resolveInstanceMethod:
- -CYLTest NSObject forwardingTargetForSelector:
- -CYLTest NSObject forwardingTargetForSelector:
- -CYLTest NSObject methodSignatureForSelector:
- -CYLTest NSObject methodSignatureForSelector:
- -CYLTest NSObject class
- -CYLTest NSObject doesNotRecognizeSelector:
- -CYLTest NSObject doesNotRecognizeSelector:
- -CYLTest NSObject class
结合《NSObject官方文档》,排除掉 NSObject 做的事,剩下的就是_objc_msgForward
消息转发做的几件事:
1.调用
resolveInstanceMethod
:方法 (或
resolveClassMethod:
)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始
objc_msgSend
流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod
。如果仍没实现,继续下面的动作。
2.调用
forwardingTargetForSelector:
方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。
3.调用
methodSignatureForSelector:
方法,尝试获得一个方法签名。如果获取不到,则直接调用4.
doesNotRecognizeSelector
抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:
。
4.调用
forwardInvocation:
方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。
5.调用
doesNotRecognizeSelector:
,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。
上面前4个方法均是模板方法,开发者可以override,由 runtime 来调用。最常见的实现消息转发:就是重写方法3和4,吞掉一个消息或者代理给其他对象都是没问题的
也就是说
_objc_msgForward
在进行消息转发的过程中会涉及以下这几个方法:
1.
resolveInstanceMethod:
方法 (或
resolveClassMethod:
)。
2.forwardingTargetForSelector:
方法
3.methodSignatureForSelector:
方法
4.forwardInvocation:
方法
5.doesNotRecognizeSelector:`
方法
为了能更清晰地理解这些方法的作用,git仓库里也给出了一个Demo,名称叫“
_objc_msgForward_demo
”,可运行起来看看。
下面回答下第二个问题“直接
_objc_msgForward
调用它将会发生什么?”
直接调用
_objc_msgForward
是非常危险的事,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。
就好像跑酷,干得好,叫“耍酷”,干不好就叫“作死”。
正如前文所说:
_objc_msgForward
是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,
_objc_msgForward
会尝试做消息转发。
如何调用
_objc_msgForward
?
_objc_msgForward
隶属 C 语言,有三个参数 :
-- | objc_msgForward | 类型 |
---|---|---|
1 | 所属对象 | id类型 |
2 | 方法名 | SEL类型 |
3 | 可变参数 | 可变参数类型 |
首先了解下如何调用 IMP 类型的方法,IMP类型是如下格式:
为了直观,我们可以通过如下方式定义一个 IMP类型 :
typedef void (*voidIMP)(id, SEL, ...)
一旦调用
_objc_msgForward
,将跳过查找 IMP 的过程,直接触发“消息转发”,如果调用了
_objc_msgForward
,即使这个对象确实已经实现了这个方法,你也会告诉
objc_msgSend
:
“我没有在这个对象里找到这个方法的实现”想象下
objc_msgSend
会怎么做?通常情况下,下面这张图就是你正常走
objc_msgSend
过程,和直接调用
_objc_msgForward
的前后差别:
有哪些场景需要直接调用
_objc_msgForward
?最常见的场景是:你想获取某方法所对应的
NSInvocation
对象。举例说明:
JSPatch (Github 链接)
就是直接调用_objc_msgForward
来实现其核心功能的:
JSPatch 以小巧的体积做到了让JS调用/替换任意OC方法,让iOS APP具备热更新的能力。
作者的博文《JSPatch实现原理详解》详细记录了实现原理,有兴趣可以看下。
同时 RAC(ReactiveCocoa) 源码中也用到了该方法。
26. runtime如何实现weak变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
__weak
引用的解除时间。)我们可以设计一个函数(伪代码)来表示上述机制:objc_storeWeak(&a, b)
函数:
objc_storeWeak
函数把第二个参数--赋值对象(b)的内存地址作为键值key,将第一个参数--weak修饰的属性变量(a)的内存地址(&a)作为value,注册到 weak 表中。如果第二个参数(b)为0(nil),那么把变量(a)的内存地址(&a)从weak表中删除,你可以把
objc_storeWeak(&a, b)
理解为:
objc_storeWeak(value, key)
,并且当key变nil,将value置nil。
在b非nil时,a和b指向同一个内存地址,在b变nil时,a变nil。此时向a发送消息不会崩溃:在Objective-C中向nil发送消息是安全的。
而如果a是由assign修饰的,则: 在b非nil时,a和b指向同一个内存地址,在b变nil时,a还是指向该内存地址,变野指针。此时向a发送消息极易崩溃。
下面我们将基于
objc_storeWeak(&a, b)
函数,使用伪代码模拟“runtime如何实现weak属性”:
// 使用伪代码模拟:runtime如何实现weak属性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
id obj1;
objc_initWeak(&obj1, obj);
/*obj引用计数变为0,变量作用域结束*/
objc_destroyWeak(&obj1);
下面对用到的两个方法
objc_initWeak
和
objc_destroyWeak
做下解释:总体说来,作用是: 通过
objc_initWeak
函数初始化“附有weak修饰符的变量(obj1)”,在变量作用域结束时通过objc_destoryWeak
函数释放该变量(obj1)。下面分别介绍下方法的内部实现:objc_initWeak
函数的实现是这样的:在将“附有weak修饰符的变量(obj1)”初始化为0(nil)后,会将“赋值对象”(obj)作为参数,调用
objc_storeWeak
函数。
obj1 = 0;
obj_storeWeak(&obj1, obj);
也就是说:weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)
然后
obj_destroyWeak
函数将0(nil)作为参数,调用
objc_storeWeak
函数。
objc_storeWeak(&obj1, 0);
前面的源代码与下列源代码相同。
// 使用伪代码模拟:runtime如何实现weak属性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/* ... obj的引用计数变为0,被置nil ... */
objc_storeWeak(&obj1, 0);
objc_storeWeak
函数把第二个参数--赋值对象(obj)的内存地址作为键值,将第一个参数--weak修饰的属性变量(obj1)的内存地址注册到 weak 表中。如果第二个参数(obj)为0(nil),那么把变量(obj1)的地址从weak表中删除。
27. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
- 不能向编译后得到的类中增加实例变量;
- 能向运行时创建的类中添加实例变量;
解释下: - 因为编译后的类已经注册在 runtime 中,类结构体中的
objc_ivar_list
实例变量的链表 和
instance_size
实例变量的内存大小已经确定,同时runtime 会调用
class_setIvarLayout
或
class_setWeakIvarLayout
来处理 strong weak 引用。所以不能向存在的类中添加实例变量;运行时创建的类是可以添加实例变量,调用
class_addIvar
函数。
但是得在调用
objc_allocateClassPair
之后,
objc_registerClassPair
之前,原因同上。
[iOS开发之高级面试题(一)]( https://www.jianshu.com/p/78f66f1d633e)
[iOS开发之高级面试题(二)](https://www.jianshu.com/p/353f93bf4912)
[iOS开发之高级面试题(四) ](https://www.jianshu.com/p/ebcac56221e9)
>小编加了一个不错的面试题和iOS技巧分享群,883872094群资料可以自取。分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!希望帮助开发者少走弯路。
>[原文地址](https://www.jianshu.com/p/c71397fbcebf)
图文来源于网络,如有侵权请联系小编删除