__NSCFConstantString
1 | NSString *str1 = @"ab"; |
输出结果
1 | 0x1097013f8 isa: __NSCFConstantString |
其中 str1/str2/str3 的指向的内容地址相同,类都是 __NSCFConstantString
这是字符串常量,编译时分配内存,存储在常量区,引用计数是不会变的
而 str4 是一个 __NSTaggedPointerString
__NSTaggedPointerString
这是苹果在 64 位环境下对 NSString,NSNumber 等对象做的一些优化。
64 位环境下指针变量长达 8 位,苹果把指针指向的内容直接放在了指针变量的内存地址中
这种指针不通过解引用 isa 来获取其所属类(通过其地址的部分保留字段),因此可以当作一种伪对象
这是一个单例常量,不会被释放。
对于 NSString 对象来讲,当非字面值常量的数字,英文字母字符串的长度小于等于 9 的时候会自动成为 NSTaggedPointerString 类型
如果有中文或其他特殊符号(可能是非 ASCII 字符)存在的话则会直接成为 __NSCFString
类型
1 | NSString *str5 = [NSString stringWithFormat:@"abcdedfgh"]; // 长度<=9 |
输出结果
1 | 0x8166d7f2baa92699 isa: NSTaggedPointerString |
NSString 与引用计数
__NSCFConstantString
的引用计数无限大__NSTaggedPointerString
的引用计数无限大__NSCFString
的引用计数正常,对一个__NSCFString
进行 copy 操作会使得该对象的引用计数 +1
可以通过
po @(CFGetRetainCount((__bridge CFTypeRef)(s)))
查看其引用计数
一道面试题
1 | @property (nonatomic, copy) NSString *str; |
可能挂在这里
1 | #0 0x00007fff51a9033a in __pthread_kill () |
原因是多个线程调用 string 的 setter 时,当 _str
引用计数为 1 时,release 被调用了,过度释放造成 crash
1 | - (void)setStr:(NSString *)str |
- 改为
self.str = [NSString stringWithFormat:@"abcdedfgh"];
就不会挂了,因为__NSTaggedPointerString
的引用计数无限大,多次 release 也没事 - 同理,改为
self.str = @"abcdedfgh"
也不会挂,因为__NSCFConstantString
的引用计数无限大 - 改为 atomic 也可以防止 crash
另外在 autoreleasepool pop 的时候也会调用 release,也可能会挂,堆栈如下
1 | #0 0x00007fff50aed94b in objc_release () |