TaggedPointer
先看一段代碼: - (void)taggedPointerDemo {
self.queue = dispatch_queue_create("com.tudou.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"tudou"]; //
NSLog(@"%@",self.nameStr);
});
}
}
複製代碼
調用這個方法運行發現能夠正常打印以下圖: 此時再添加一個方法 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"tudou_好好學習每天向上"];
NSLog(@"%@",self.nameStr);
});
}
}
複製代碼
點擊屏幕發現崩潰了 其實崩潰反而更好理解,咱們知道在賦值的過程當中調用set
方法是須要對新值的retain
和舊值的release
,可是此時是多線程的而且也沒有加鎖作線程安全,因此就會出現多個線程同時訪問這個變量的狀況,同時賦值同時release
舊值就形成了過分釋放的問題因此崩潰,可是第一種狀況就奇怪了沒有崩潰,此時分別下斷點查看兩種賦值狀況下nameStr
的類型 發現第一種狀況nameStr
的類型是NSTaggedPointerString
也就是小對象類型,而第二種狀況是NSCFString
也就是字符串類型。
這裏就引伸出來TaggedPointer
小對象類型。那麼一樣是經過stringWithFormat
方法建立字符串爲何第一種狀況是小對象類型而第二種狀況不是呢,又爲何第一種狀況不會崩潰,第二種狀況會崩潰呢?帶着問題咱們能夠先來了解一下什麼是小對象類型。
- 源碼探索小對象類型 首先建立一個小對象類型打印它的地址以下圖: 發現這個小對象的地址是
0x8b168fada8723f5a
,按照內存五大區文章中的內存分段發現不知道是歸屬於那一塊
在看_read_images
函數中有個initializeTaggedPointerObfuscator
函數,查看該函數的源碼發現是初始化小對象類型混淆器,這裏的作法就是先與上_OBJC_TAG_MASK
(ios12之後) 而後全局搜索objc_debug_taggedpointer_obfuscator
發現以下代碼 發現了小對象類型的編解碼函數,得知底層混淆小對象是進行了異或操做,編碼是使用objc_debug_taggedpointer_obfuscator
異或小對象,解碼時是用小對象異或解碼時objc_debug_taggedpointer_obfuscator
因此上文中打印的小對象的地址是編碼後的地址,獲得真實小對象的地址則須要解碼以下圖獲得解碼後的地址: 發現是0xa000000000000611
發現61
恰好就是a
的ASCII
碼,不知道是否是應爲湊巧咱們能夠多試幾個小對象類型: 發現地址中就存在着值那麼0xa
、0xb
又是什麼呢,此時咱們查看一下判斷小對象類型的源碼也就是查看_objc_isTaggedPointer
函數的源碼發現 發現是經過最高位是不是1來判斷是不是小對象類型0xa
、0xb
轉爲二進制分別是10十、1011
,都是1因此都是小對象類型,後三位主要是用來標記tagType
的0xa
的後三位轉成二進制是2,0xb
的後三位轉二進制是3,此時咱們再看_objc_makeTaggedPointer
的源碼 發現入參就有一個tag
,查看這個tag
的枚舉類型 發現2就是字符串的小對象類型,3就是NSNumber
的小對象類型
- 小對象類型不會出現過分釋放崩潰的緣由 上文中發現小對象類型的值其實就在地址中,並非存在堆區而是常量區,因此小對象類型的釋放是系統處理的,也能夠查看
retain
和release
源碼發現 若是是小對象類型直接返回對象了,因此set方法中不存在舊值的釋放也就不會存在過分釋放崩潰
- 狀況1是小對象的緣由 應爲狀況一複製的是a內存暫用太小,oc優化處理變成小對象類型,佔用多大內存是小對象類型,多大又是oc對象了呢以下表:
- 小對象總結
- 小對象並非個真正的對象不存在堆區,是存在常量區的
- 小對象類型不會進入
retain
和release
方法中
- 小對象類型的64位地址中,前4位表明類型,後4位主要適用於系統作一些處理,中間56位用於存儲值
- 小對象的有點:應爲不存儲在堆區因此節省了空間,能夠直接進行讀取,在內存讀取上有着3倍的效率,建立時⽐之前快106倍。