一、iOS程序的内存布局
二、Tagged Pointer
- 从64bit开始,iOS引入了
Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
- 在没有使用
Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
- 使用
Tagged Pointer之后,NSNumber指针里面存储的数据变成了: Tag + Data,也就是将数据直接存储在了指针中
- 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
NSNumber *num1 = [NSNumber numberWithInt:1];
NSNumber *num2 = @(2);
NSNumber *num3 = @3;
NSNumber *num4 = @(1233221132133211233);
NSLog(@"%p", num1);
NSLog(@"%p", num2);
NSLog(@"%p", num3);
NSLog(@"%p", num4);
- objc_msgSend能识别
Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销
NSNumber *num1 = [NSNumber numberWithInt:1];
[num1 intValue];
- 如何判断一个指针是否为
Tagged Pointer?
- iOS平台,最高有效位是1(第64bit)
- Mac平台,最低有效位是1
- 判断一个指针是否是
Tagged Pointer的源码使用的是_objc_isTaggedPointer函数
static inline bool _objc_isTaggedPointer(const void * _Nullable ptr) {
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
三、面试题
1、下面这段代码执行后, 会发生什么
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghijklmn"];
});
}
}
@end
- 运行程序, 可以看到崩溃在了
objc_release中
- 这主要是因为在
-setName:方法中, 实际的实现如下
- (void)setName:(NSString *)name
{
if (_name != name) {
[_name release];
_name = [name copy];
}
}
- 因为使用多线程赋值, 所以会有多个线程同时调用
[_name release], 所以才发触发上面的崩溃
- 解决的方式就是加锁, 可以使用
atomic, 或者其他的锁
@property (atomic, copy) NSString *name;
2、下面的代码为什么可以正常运行, 不会崩溃
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}
}
@end
- 运行程序, 上面的代码确实不会发生崩溃
- 这是因为
[NSString stringWithFormat:@"abc"]是一个Tagged Pointer, 在调用-setName:方法时, 底层使用的是objc_msgSend(self, @selector(setName:)
- 此时就会在底层调用
_objc_isTaggedPointer函数判断是否是Tagged Pointer, 如果是, 就会直接将地址赋值给_name, 没有release和copy的操作