1、iOS程序的內存佈局
2、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;
}
複製代碼
3、面試題
一、下面這段代碼執行後, 會發生什麼
#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;
複製代碼
二、下面的代碼爲何能夠正常運行, 不會崩潰
#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
的操做