一、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
的操作