小碼哥iOS學習筆記第二十四天: Tagged Pointer

1、iOS程序的內存佈局

2、Tagged Pointer

  • 從64bit開始,iOS引入了Tagged Pointer技術,用於優化NSNumberNSDateNSString等小對象的存儲
  • 在沒有使用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,好比NSNumberintValue方法,直接從指針提取數據,節省了之前的調用開銷
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, 沒有releasecopy的操做
相關文章
相關標籤/搜索