0%

Runtime:综合面试题

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:]
  1. 创建对象,ref=1,并添加到当前的autoreleasepool中;
  2. 赋值到局部变量,ref+1=2;
  3. viewDidLoad方法返回,局部变量被回收,ref-1=1;
  4. viewDidLoadviewWillAppear在同一个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:]
  1. 创建对象,ref=1;
  2. 赋值到局部变量,ref+1=2;
  3. 离开作用域域,ref-1=1;
  4. 离开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:]
  1. 创建对象,ref=1;
  2. 赋值到局部变量,ref+1=2;
  3. 离开autoreleasepool,调用release,ref-1=1,对象释放。
  4. viewDidLoad方法返回时,ref-1=0,对象被释放。

所以在viewDidLoad方法中访问对象时,还能访问,离开方法后就无法访问。

注意

  • 如果字符串过短,会变成存储在栈的TaggedPointer,无需引用计数管理,在所有方法中都可以访问。
  • 类似的,如果字符串位@"..."形式,则存储到常量区,也无需引用计数管理,在所有方法中也都可以访问。

对于栈上的内存,会在离开作用域后被回收。

更多

欢迎关注我的其它发布渠道