先看下iOS程序中的内存布局。
内存管理的方案
用于优化NSNumber、NSDate、NSString等小对象的存储。
节省了内存空间
将对象的值放在了指针里节省了内存。但当对象占用的内存指针无法存放的时候就变成了普通对象的存储方式。
怎么存储在指针上的?
之前存储的对象的地址变成了真实的值和一些附加信息。
使用更简单
直接转换如调用字符串的intValue方法直接将字符串转成int类型数据,而不用走对象的消息机制调用方法。
判断是否使用了tagged pointer
Mac平台上指针的最低有效位是1,使用了tagged pointer。
iOS平台上指针的最高有效位是1,使用了tagged pointer。
看代码:
NSString * str1 = [NSString stringWithFormat:@"111"];
NSString * str2 = [NSString stringWithFormat:@"111abcdefg"];
NSLog(@"%p %p", str1, str2);
NSLog(@"%@ %@", [str1 class], [str2 class]);
看打印:
0.题2[6586:91677] 0xf68e3873a4cf4987 0x60000337f640
0.题2[6586:91677] NSTaggedPointerString __NSCFString
通过打印类型我们可以直接看出str1是tagged pointer类型,str2是NSString类型,我们再分析下地址
0xf68e3873a4cf4987 :
16位16进制相当于64位2进制,最高位0xf = 0b1111,最高有效位是1.
0x60000337f640:
用0补齐64位->0x000060000337f640,最高有效位是0.
也可以通过这个方法来判断是堆地址还是栈地址,一般的OC对象都在堆上。
看例子
@property (nonatomic, copy) NSString * string;
--
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 1000; i ++) {
dispatch_async(queue, ^{
self.string = [NSString stringWithFormat:@"111"];
});
}
for (NSInteger i = 0; i < 1000; i ++) {
dispatch_async(queue, ^{
self.string = [NSString stringWithFormat:@"111abcdefg"];
});
}
看看使用了tagged pointer和没使用有什么区别。如果是使用了代码是没有任何问题的,如果是没有使用就会报错:
Thread 5: EXC_BAD_ACCESS (code=1, address=0x30b91657c5c0)
坏内存访问,这是因为多个线程调用了string的set方法,真正的set方法内部代码:
- (void)setString:(NSString *)string{
if (_string != string) {
[_string release];
_string = [string copy];
}
}
可能两个线程同时调用了release方法,但在第二个线程调用的时候string对象已经释放掉了,所以就出现了访问坏内存的报错。
解决方法:
1.使用atomic修饰string
2.在外部修改给string赋值的时候加锁