请问以下代码执行结果是什么:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000; i ++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghijklmn"];
});
}
NSLog(@"end");
运行结果:崩溃(坏内存访问)
坏内存访问.png
原因分析:
因为setter方法中,对strong修饰的属性会有一个retain和release的操作。在并发多线程的赋值操作中,都是对_name指针进行的操作,可能在_name刚刚被release后进行赋值操作,这个时候_name指向的内存地址是已经被释放了,所以造成了坏内存访问崩溃
- (void)setName:(NSString *)name
{
[name retain];
[_name relase];
_name = name;
}
验证分析:
堆栈信息.png
解决办法:
- 将并发执行的任务改为串行执行
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
- 将异步执行改为同步执行
dispatch_sync(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghijklmn"];
});
- 将属性改为atomic
属性原子性
- 加锁
NSLock *lock = [[NSLock alloc] init];
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000; i ++) {
dispatch_async(queue, ^{
[lock lock];
self.name = [NSString stringWithFormat:@"abcdefghijklmn"];
[lock unlock];
});
}
NSLog(@"end");
再请问以下代码执行结果是什么:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000; i ++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}
NSLog(@"end");
为什么不崩溃了?因为没有用到引用计数的内存管理方法,使用的是TaggedPointer
TaggedPointer
- 从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象存储
- 在没有使用Tagged Pointer之前,NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
- 使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中,Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。
- 在内存读取上有着3倍的效率,创建时比以前快106倍。不但减少了64位机器下程序的内存占用,还提高了运行效率。完美地解决了小内存对象在存储和访问效率上的问题。
- 这是一个特别的指针,不指向任何一个地址
- 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
查看类型
NSString *str1 = [NSString stringWithFormat:@"abcdefghijk"];
NSString *str2 = [NSString stringWithFormat:@"abc"];
NSLog(@"%@ %@", [str1 class], [str2 class]);
查看内存地址:
NSNumber *number1 = @1;
NSNumber *number2 = @2;
NSNumber *number3 = @3;
NSLog(@"number1 pointer is %p", number1);
NSLog(@"number2 pointer is %p", number2);
NSLog(@"number3 pointer is %p", number3);
/*
2017-03-10 12:07:50.731726 TaggedPoint[1690:50438] number1 pointer is 0x127
2017-03-10 12:07:50.731992 TaggedPoint[1690:50438] number2 pointer is 0x227
2017-03-10 12:07:50.732011 TaggedPoint[1690:50438] number3 pointer is 0x327
*/
上面的代码的执行结果以前是直接可以查看到结果的(注释部分),但现在的地址有些特殊,在10_14之后苹果对TaggedPointer进行了混淆,文件objc-runtime-new.m里写到:
static void
initializeTaggedPointerObfuscator(void)
{
if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
DisableTaggedPointerObfuscation) {
objc_debug_taggedpointer_obfuscator = 0;
} else {
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
}
}
混淆原理:使用objc_debug_taggedpointer_obfuscator对真正的内存地址异或操作
objc_debug_taggedpointer_obfuscator这个值是一个随机值,获取这个值有两种方法
方法一:通过断点LLDB指令获取:
(lldb) p/x objc_debug_taggedpointer_obfuscator
(void *) $0 = 0x4fd2047445c09ecd
方法二:看runtime的源码知道objc_debug_taggedpointer_obfuscator是个全局变量,只要在我们用的地方申明一下即可
extern uintptr_t objc_debug_taggedpointer_obfuscator;
通过nslog打印就可以
NSLog(@"%lx",objc_debug_taggedpointer_obfuscator);
方便查看,写一个方法用来解开混淆:
extern uintptr_t objc_debug_taggedpointer_obfuscator;
uintptr_t _objc_decodeTaggedPointer_(id ptr) {
NSString *p = [NSString stringWithFormat:@"%ld", ptr];
return [p longLongValue] ^ objc_debug_taggedpointer_obfuscator;
}
真实地址:
NSNumber *number1 = @1;
NSNumber *number2 = @2;
NSNumber *number3 = @3;
NSLog(@"number1 pointer is %p---真实地址:==0x%lx", number1,_objc_decodeTaggedPointer_(number1));
NSLog(@"number2 pointer is %p---真实地址:==0x%lx", number2,_objc_decodeTaggedPointer_(number2));
NSLog(@"number3 pointer is %p---真实地址:==0x%lx", number3,_objc_decodeTaggedPointer_(number3));
NSString *str3 = [NSString stringWithFormat:@"a"];
NSString *str4 = [NSString stringWithFormat:@"b"];
NSLog(@"str3 pointer is %p---真实地址:==0x%lx", str3,_objc_decodeTaggedPointer_(str3));
NSLog(@"str4 pointer is %p---真实地址:==0x%lx", str4,_objc_decodeTaggedPointer_(str4));
打印结果:
2019-10-16 15:29:23.965970+0800 taggedpointer[64119:3260975] number1 pointer is 0xd206e93cb39459f2---真实地址:==0xb000000000000012
2019-10-16 15:29:23.966067+0800 taggedpointer[64119:3260975] number2 pointer is 0xd206e93cb39459c2---真实地址:==0xb000000000000022
2019-10-16 15:29:23.966152+0800 taggedpointer[64119:3260975] number3 pointer is 0xd206e93cb39459d2---真实地址:==0xb000000000000032
2019-10-16 15:29:23.966245+0800 taggedpointer[64119:3260975] str3 pointer is 0xc206e93cb3945ff1---真实地址:==0xa000000000000611
2019-10-16 15:29:23.966328+0800 taggedpointer[64119:3260975] str4 pointer is 0xc206e93cb3945fc1---真实地址:==0xa000000000000621
内存地址对比:
// 0x8df5fe7e8dbc4702
// 0x3df5fe7e8dbc4710
// 0xb000000000000012
由此可见简单数字直接存储,字母存储为ASCII码
ASCII码打印:
NSLog(@"%lx,%lx",'a','b');
ASSCII.png
验证大数据存储至堆空间:
NSMutableString *string2 = [NSMutableString stringWithString:@"1"];
for( int i = 0; i < 14; i++){
NSString *strFor = [[string2 mutableCopy] copy];
NSLog(@"%@: %p---%p==%@", [strFor class], strFor, &strFor, strFor);
[string2 appendString:@"1"];
}
网友评论