由autorelease用NSString做例子遇到的问题
我们知道一个对象在用__weak修饰后,创建之后在当前作用域结束之后会被置为nil,就如以下
__weak id reference = nil;
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"string: %@",reference);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"string: %@",reference);
}
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *obj = [[NSObject alloc]init];
reference = obj;
NSLog(@"string: %@",reference);
}
打印结果很明显是
2017-12-25 17:47:48.336248+0800 chanbendong[5975:2288145] string: <NSObject: 0x608000006d10>
2017-12-25 17:47:48.336486+0800 chanbendong[5975:2288145] string: (null)
2017-12-25 17:47:48.339677+0800 chanbendong[5975:2288145] string: (null)
但是如果我们换成NSString,得到的结果会是怎样的呢?
__weak id reference = nil;
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"string: %@",reference);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"string: %@",reference);
}
- (void)viewDidLoad {
[super viewDidLoad];
NSString *obj = @"chanbendong";
reference = obj;
NSLog(@"string: %@",reference);
}
得到的结果还是一样吗?
结果是
2017-12-25 17:51:10.090734+0800 chanbendong[6029:2315646] string: chanbendong
2017-12-25 17:51:10.090963+0800 chanbendong[6029:2315646] string: chanbendong
2017-12-25 17:51:10.094109+0800 chanbendong[6029:2315646] string: chanbendong
结果是NSString一直还在,这个原因是NSString初始化的时候放在常量区,所以没有释放
我们继续看这个NSString
我们在赋值的时候打个断点
image.png
利用lldb命令watchpoint set variable obj 来观察,可以看到obj由0x0000000000000000变成0x0000000108aa49a8
然后点击继续程序,会发现obj变成0x0000000000000000,控制台就打印了一次obj的值,也就是说viewWillAppear还没执行
image.png
这里定义一个log语句
#define log(_var) ({ NSString *name = @#_var;NSLog(@"%@: %@ -> %p: %@",name,[_var class],_var, _var);})
看以下代码
- (void)viewDidLoad {
[super viewDidLoad];
NSString *str = @"123456789";
NSString *bstr = [NSString stringWithFormat:@"123456789"];
NSString *cstr = [str mutableCopy];
NSNumber *xnum = [NSNumber numberWithInteger:8];
reference = str;
self.num = xnum;
log(str);
log(bstr);
log(cstr);
}
打印如下
2017-12-26 10:39:11.285394+0800 chanbendong[1741:94708] str: __NSCFConstantString -> 0x100f949a8: 123456789
2017-12-26 10:39:11.285567+0800 chanbendong[1741:94708] bstr: NSTaggedPointerString -> 0xa1ea1f72bb30ab19: 123456789
2017-12-26 10:39:11.285646+0800 chanbendong[1741:94708] cstr: __NSCFString -> 0x6040002456a0: 123456789
2017-12-26 10:39:11.285789+0800 chanbendong[1741:94708] string: (null)
2017-12-26 10:39:11.289097+0800 chanbendong[1741:94708] string: (null)
这里可以看到有三种String,其中reference指向了CFString,所以为null
这里来看这三种String
·NSCFConstantString:字符串常量,放在常量区,对它retain或者release都没有用,程序结束后释放,所以在viewDidLoad结束之后还能打印出来,其中指针str也是一个对象储存在栈中,由系统释放
· NSTaggedPointerString:Tagged Point,标签指针,苹果在64位环境下对NSString和NSNumber做了优化,把对象的内容放在了指针里面,这样就不需要在堆内存里面开辟一块空间存放对象了,一般是用来优化长度较小的内容。
举个例子
NSNumber *n1 = @1;
NSNumber *n2 = @2;
NSNumber *n3 = @3;
NSNumber *nf = @(0xFFFF);
NSLog(@"n1 : %p",n1);
NSLog(@"n2 : %p",n2);
NSLog(@"n3 : %p",n3);
NSLog(@"nf : %p",nf);
打印结果如下
2017-12-26 10:53:13.235424+0800 chanbendong[2002:141469] n1 : 0xb000000000000012
2017-12-26 10:53:13.235561+0800 chanbendong[2002:141469] n2 : 0xb000000000000022
2017-12-26 10:53:13.235693+0800 chanbendong[2002:141469] n3 : 0xb000000000000032
2017-12-26 10:53:13.235784+0800 chanbendong[2002:141469] nf : 0xb0000000000ffff2
可以看到除去最后的数字末尾的2和最开头的0xb,其他数字正好表示了NSNumber的值
这方便不是本文重点,感兴趣可以去看唐巧的博客,例子也是选取唐巧博客里面的深入理解Tagged Pointer
对于NSString,如果不是由字面量直接赋值(即[NSString alloc]initWithString:
或者@""
)英文字母字符串小于等于9的时候会自动成为NSTaggedPointerString类型,即bstr再加一位或者有中文则变成NSCFString,而NSTaggedPointerString也是不会释放的,它的内容就在本身的指针里,所以即使把reference指向它,在viewWillAppear和viewDidAppear也是能打印出来的
·NSCFString:这种String就类似普通的NS对象了,储存在堆上,有正常的引用计数,需要程序员分配和释放。所以在viewWillAppear和viewDidAppear为null。
然而
NSString *bstr = [NSString stringWithFormat:@"12345678900"];
reference = bstr;
在viewWillAppear和viewDidAppear打印结果却是
2017-12-26 11:07:38.277489+0800 chanbendong[2197:197549] string: 12345678900
2017-12-26 11:07:38.280625+0800 chanbendong[2197:197549] string: (null)
根据黑幕背后的autorelease里面说viewDidLoad和viewWillAppear是在同一个runloop调用的,如果是这样的话cstr应该也能打印出来,普通的NSObject对象应该也能打印出来。
说到底其实不是runloop的问题,问题是stringWithFormat这个工厂方法上。
以 alloc, copy, init,mutableCopy和new这些方法打头的方法,返回的都是 retained return value,例如[[NSString alloc] initWithFormat:],而其他的则是unretained return value,例如 [NSString stringWithFormat:]。对于前者调用者是要负责释放的,对于后者就不需要了。而且对于后者ARC会把对象的生命周期延长,确保调用者能拿到并且使用这个返回值,但是并不一定会使用 autorelease,在worst case 的情况下才可能会使用,因此调用者不能假设返回值真的就在 autorelease pool中。从性能的角度,这种做法也是可以理解的。如果我们能够知道一个对象的生命周期最长应该有多长,也就没有必要使用 autorelease 了,直接使用 release 就可以。如果很多对象都使用 autorelease 的话,也会导致整个 pool 在 drain 的时候性能下降。
我们可以看看array的方法
NSObject *object = [NSObject new];
NSArray *arr = @[object];//1
// NSArray *arr = [NSArray arrayWithObjects:object, nil];//2
reference = arr;
1打印如下
2017-12-26 11:26:44.945028+0800 chanbendong[2354:251369] string: (null)
2017-12-26 11:26:44.948504+0800 chanbendong[2354:251369] string: (null)
2打印如下
2017-12-26 11:27:30.558241+0800 chanbendong[2387:255117] string: (
"<NSObject: 0x60c000008050>"
)
2017-12-26 11:27:30.561603+0800 chanbendong[2387:255117] string: (null)
可以看到通过工厂方法创建的array生命周期变长了。
总结
·方法里的临时变量是会通过autoreleasepool释放的
·NSCFString跟普通对象是一样的
·NSString和NSArray,NSDictionary的工厂方法可以延长对象的生命周期