Runtime综合面试题
isa指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| //***********♦️♦️CLPerson♦️♦️************ @interface CLPerson : NSObject @property (nonatomic, copy) NSString *name; -(void)print; @end
@implementation CLPerson -(void)print { NSLog(@"My name's %@", self.name); } @end
//***********🥝🥝ViewController.m🥝🥝************
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; id cls = [CLPerson class]; void *obj = &cls; [(__bridge id)obj print]; } @end
|
最终输出结果:
1
| My name's <ViewController: 0x7fce43e08aa0>
|
为什么print
可以被调用
因为:
- 实例对象 = 指向类的指针
- cls指向类,obj指向cls,相当于obj是指向类的指针。
所以(__bridge id)obj
就相当于实例变量的效果。
为什么打印是<ViewController: 0x7fce43e08aa0>
首先self.name
就是通过指针调用的self->_name
。
实例对象底层是一个结构体,存放isa指针和成员变量列表,因为指针在arm64位上占8位,name
又是CLPerson的第一个成员,所以self->_name
就是基于对象地址往高地址偏移8位读取的内存。
栈空间是存放被调用函数内部所定义的局部变量的。先定义的局部变量在栈底高地址。所以上述代码的局部变量布局为:
这里隐藏了个细节:[super viewDidLoad];
。该代码的底层调用是:
1 2 3 4 5
| objc_msgSendSuper( (__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, @selector(viewDidLoad));
|
相当于cls的高地址方向还有一个self局部变量,就ViewController实例对象,所以self->_name
指向的就是cls的上一个局部变量,即高地址方向偏移8位——ViewController实例对象。
扩展:类似的,如果没有[super viewDidLoad];
就会出现BAD_ACCESS错误。如果cls前面多了个OC对象局部变量,则打印该局部变量。注意还是需要前面有个OC对象,否则还是会BAD_ACCESS。
更多扩展:iOS探索 isa面试题分析 - 掘金
autoreleasepool
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @interface ViewController () { __weak NSString *string_weak; }
@end
@implementation ViewController
- (void)viewDidLoad { [super viewDidLoad]; // 各场景 NSLog(@"string: %@ %s", string_weak,__func__); } - (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; NSLog(@"string: %@ %s", string_weak,__func__); }
- (void)viewDidAppear:(BOOL)animated{ [super viewDidAppear:animated]; NSLog(@"string: %@ %s", string_weak,__func__); }
|
场景一
1 2 3 4 5 6 7
| NSString *str = [NSString stringWithFormat:@"https://ityongzhen.github.io/"]; string_weak = str;
// 输出 string: https://ityongzhen.github.io/ -[ViewController viewDidLoad] string: https://ityongzhen.github.io/ -[ViewController viewWillAppear:] string: (null) -[ViewController viewDidAppear:]
|
- 创建对象,ref=1,并添加到当前的autoreleasepool中;
- 赋值到局部变量,ref+1=2;
viewDidLoad
方法返回,局部变量被回收,ref-1=1;
viewDidLoad
和viewWillAppear
在同一个RunLoop中,所以还能访问;viewDidLoad
已经是下一个RunLoop,已经被释放。
场景二
1 2 3 4 5 6 7 8 9
| @autoreleasepool { NSString *str = [NSString stringWithFormat:@"https://ityongzhen.github.io/"]; string_weak = str; }
// 输出 string: (null) -[ViewController viewDidLoad] string: (null) -[ViewController viewWillAppear:] string: (null) -[ViewController viewDidAppear:]
|
- 创建对象,ref=1;
- 赋值到局部变量,ref+1=2;
- 离开作用域域,ref-1=1;
- 离开autoreleasepool,调用release,ref-1=0,对象释放。
所以后序在viewDidLoad
方法中访问的对象已经被释放。
场景三
1 2 3 4 5 6 7 8 9 10
| NSString *str = nil; @autoreleasepool { str = [NSString stringWithFormat:@"https://ityongzhen.github.io/"]; string_weak = str; }
// 输出 string: https://ityongzhen.github.io/ -[ViewController viewDidLoad] string: (null) -[ViewController viewWillAppear:] string: (null) -[ViewController viewDidAppear:]
|
- 创建对象,ref=1;
- 赋值到局部变量,ref+1=2;
- 离开autoreleasepool,调用release,ref-1=1,对象释放。
viewDidLoad
方法返回时,ref-1=0,对象被释放。
所以在viewDidLoad
方法中访问对象时,还能访问,离开方法后就无法访问。
注意
- 如果字符串过短,会变成存储在栈的TaggedPointer,无需引用计数管理,在所有方法中都可以访问。
- 类似的,如果字符串位
@"..."
形式,则存储到常量区,也无需引用计数管理,在所有方法中也都可以访问。
对于栈上的内存,会在离开作用域后被回收。
更多