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