由autorelease用NSString做例子遇到的问题

2017-12-26  本文已影响26人  程序狗

我们知道一个对象在用__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的工厂方法可以延长对象的生命周期

上一篇下一篇

猜你喜欢

热点阅读