學習筆記:@weakify(self) 和 @strongify(self) 和 __weak、__strong等

一酵紫、修飾符

ARC 環(huán)境下扒最,所有的修飾符有以下4種

  • __strong修飾符
  • __weak修飾符
  • __unsafe_unretained修飾符
  • __autoreleasing 修飾符
  • 底層都是被函數(shù)objc_ownership(xxx)修飾丑勤,不同的修飾符,入?yún)xx不同吧趣。
    __strong ---> objc_ownership(strong)
    __weak ---> objc_ownership(weak)
    __unsafe_unretained ---> objc_ownership(none)
    __autoreleasing ---> objc_ownership(autoreleasing)

1.1法竞、以__weak為例

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    NSString *str = @"helloworld";
    id __weak objc = str;
    NSLog(@"%@", objc);
    return 0;
}

注意: clang -rewrite-objc main.m 會報錯,需要用以下替換强挫。
clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations weak.m

int main(int argc, const char * argv[]) {
    NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_jf_zkvr_3r17rl3q1fw6zhz6jr40000gn_T_weak_cfe516_mi_0;
    
    // id __weak objc = str;
    id __attribute__((objc_ownership(weak))) objc = str;
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_jf_zkvr_3r17rl3q1fw6zhz6jr40000gn_T_weak_cfe516_mi_1, objc);
    return 0;
}

二岔霸、__weak__strong 的原理

2.1、__weak原理

1俯渤、基礎(chǔ)知識
  • 最大作用是防止循環(huán)引用呆细,當屬性對象被釋放的時候,weak屬性會自動置為nil八匠。
2絮爷、源碼解析
  • __weak 底層會調(diào)用函數(shù)objc_initWeak() ,函數(shù)內(nèi)部調(diào)用 objc_storeWeak()梨树。

在以下代碼處斷點坑夯,開啟匯編窗口(Debug -> Debug Wokflow -> Always Show Disassembly ):

id __weak obj = str;
image.png

發(fā)現(xiàn)底層調(diào)用了objc_initWeak函數(shù),查找源碼劝萤,其實現(xiàn)如下

/** 
 * Initialize a fresh weak pointer to some object location. 
 * It would be used for code like: 
 *
 * (The nil case) 
 * __weak id weakPtr;
 * (The non-nil case) 
 * NSObject *o = ...;
 * __weak id weakPtr = o;
 * 
 * This function IS NOT thread-safe with respect to concurrent 
 * modifications to the weak variable. (Concurrent weak clear is safe.)
 *
 * @param location Address of __weak ptr.   // __weak指針 的地址 渊涝,存儲指針的地址。
 * @param newObj Object ptr.  // 所引用的對象,即例子中的obj 跨释。
 */
id objc_initWeak(id *location, id newObj) {
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

注意:實現(xiàn)原理在于理解 objc_storeWeak()函數(shù)的實現(xiàn)胸私,詳細可見 iOS底層原理:weak的實現(xiàn)原理

5鳖谈、理解weak實現(xiàn)原理岁疼,重點理解內(nèi)部的存儲結(jié)構(gòu):
struct SideTable {
    spinlock_t slock; // 內(nèi)部使用自鎖鞋保證線程安全
    RefcountMap refcnts;
    weak_table_t weak_table; // 核心數(shù)據(jù)結(jié)構(gòu),存儲對象弱引用指針的hash表
}

struct weak_table_t {
    weak_entry_t *weak_entries; // hash數(shù)組缆娃,捷绒,用來存儲弱引用對象的相關(guān)信息weak_entry_t
    size_t    num_entries; // hash數(shù)組中的元素個數(shù)
    
    // 以下兩個與hash值計算和哈希沖突有關(guān)
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

// 一個hash結(jié)構(gòu),其存儲的元素是弱引用對象指針的指針
// 通過操作指針的指針贯要,就可以使得weak 引用的指針在對象析構(gòu)后暖侨,指向nil。
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    // 聯(lián)合體
    union {
        struct {
            // 動態(tài)數(shù)組
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // 定長數(shù)組
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT]; 
        };
    };
    
    // 用來判斷采用哪種存儲方式(定長數(shù)組 或 動態(tài)數(shù)組)
   // 當弱引用該對象的指針數(shù)目小于等于WEAK_INLINE_COUNT時崇渗,使用定長數(shù)組字逗。
   // 當超過WEAK_INLINE_COUNT時,會將定長數(shù)組中的元素轉(zhuǎn)移到動態(tài)數(shù)組中宅广,并之后都是用動態(tài)數(shù)組存儲葫掉。
    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    // 初始化 弱引用對象 數(shù)組
    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

// weak_referrer_t類型:DisguisedPtr泛型類,這里面類型是 objc_object
typedef DisguisedPtr<objc_object *> weak_referrer_t; 

借用別人整理的圖如下:


image.png
image.png
6跟狱、weak 原理總結(jié)

0俭厚、首先,編譯器在處理 __weak的時候驶臊,底層調(diào)用objc_initWeak() 函數(shù)挪挤,內(nèi)部調(diào)用objc_storeWeak()函數(shù),最終調(diào)用核心函數(shù)storeWeak()关翎。

1电禀、然后,創(chuàng)建或者獲取弱引用表笤休;
其中尖飞,弱引用表的結(jié)構(gòu)是一個hash結(jié)構(gòu)的表,Key是所指對象的地址店雅,Value是weak指針的地址(這個地址的值是所指對象的地址)數(shù)組政基。

通過 對象地址 取弱引用表 table = &SideTable()[object],其中SideTable()返回的是StripedMap類型的全局SideTablesMap)闹啦,其數(shù)組值為 SideTable類型沮明,內(nèi)部維護weak_table_t weak_table;

weak_table內(nèi)部核心是持有weak_entry_t *weak_entries是一個動態(tài)數(shù)組,用來存儲weak_entry_t類型的元素窍奋,這些元素實際上就是OC對象的弱引用信息荐健。

weak_entry_t的結(jié)構(gòu)也是一個hash結(jié)構(gòu)酱畅,其存儲的元素是 弱引用對象指針的指針(即weak_referrer_t *referrers 或者 weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];);

這樣就可以通過操作指針的指針江场,就可以使得 weak 引用的指針在對象析構(gòu)后纺酸,指向nil。

因此址否,整個的數(shù)據(jù)結(jié)構(gòu)如下:

  // 關(guān)于 StripedMap有興趣的可以繼續(xù)閱讀餐蔬,不在深入
  static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;
  static StripedMap<SideTable>& SideTables() {
    return SideTablesMap.get();
  }

   value = &SideTable()[newObj]
   value->weak_table->weak_entries[key_index].referrers[index] == weak_pointer;
   
    // 其中 key_index ,計算算法如下:
   while (weak_table->weak_entries[key_index].referent != referent) { 
        ... 
        key_index = (key_index+1) & weak_table->mask; 
        ...
    }

舉個例子:

Person * newObj = [Person alloc];
id __weak p1 = newObj;

則 弱引用哈希表的 key == 實例對象user的地址
value->weak_table->weak_entries[key_index].referrers[index] == p1;

2佑附、注冊__weak的時候樊诺,按照上面的數(shù)據(jù)結(jié)構(gòu),將weak指針 和 對象 建立聯(lián)系 并進行存儲音同;

3词爬、對象newObj釋放的時候,根據(jù)上面的數(shù)據(jù)結(jié)構(gòu)权均,將數(shù)組里面的取出 weak指針缸夹,依次設(shè)置為nil;

4螺句、內(nèi)部采用自旋鎖來保證線程安全。

2.2橡类、__strong

其修飾的對象引用計數(shù)器會+1蛇尚,避免在block執(zhí)行的時候,對象被提前釋放了顾画。

2.3取劫、__weak__strong 的應(yīng)用

  • block外使用:__weak typeof(self) weakSelf = self;
    block捕獲的是weakSelf,而其會在對象dealoc的時候研侣,自動被釋放置為nil谱邪,因此打破循環(huán)引用。

  • block內(nèi)使用:__strong typeof(weakSelf) strongSelf = weakSelf;
    1庶诡、block 內(nèi)的代碼在__MyObject__test_block_func_0函數(shù)內(nèi)惦银,當使用strongSelf時,會先取出__weak修飾的成員變量self末誓。
    2扯俱、然后再生成一個__strong修飾的局部變量,這時候 self 的引用計數(shù) +1喇澡。
    3迅栅、這樣的目的是在 block 內(nèi)的代碼塊執(zhí)行完之前避免 selfdealloc掉。當 block 執(zhí)行完畢之后晴玖,局部變量 strongSelf 被釋放读存,self 的引用計數(shù) -1为流。

三、關(guān)于RAC的奇妙宏@weakify(self) 和 @strongify(self)

宏的使用過程很精妙让簿,有興趣可以查看
深入研究 Block 用 weakSelf敬察、strongSelf、@weakify拜英、@strongify 解決循環(huán)引用
Reactive Cocoa中的@weakify静汤、@strongify是如何裝逼

  • 兩個宏的作用等同于:
@weakify(self) = @autoreleasepool{} __weak __typeof__ (self) self_weak_ = self;
@strongify(self) = @autoreleasepool{} __strong __typeof__(self) self = self_weak_;
5.1、@weakify(self) 和 @strongify(self) 的原理分析
#import <Foundation/Foundation.h>

@interface MyObject : NSObject {
    NSString *_age;
}
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) dispatch_block_t actionBlock;
@end

@implementation MyObject
- (void)dealloc {
    NSLog(@"MyObject Dealloc");
}

- (void)test {
    self.name = @"n";
    _age = @"10";

    @autoreleasepool{} __weak __typeof__ (self) self_weak_ = self; // @weakify(self)
    void(^textBlock)(void) = ^{
        @autoreleasepool{} __strong __typeof__(self) self = self_weak_; // @strongify(self)
        _age = @"11"; // 會導致循環(huán)引用

        //self.name = @"a"; // 不會導致循環(huán)引用
        //self->_age = @"23"; // 不會導致循環(huán)引用
    };

    textBlock();
    self.actionBlock = textBlock;
}
@end
  • clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.14 filename 查看底層代碼居凶。
struct __MyObject__test_block_impl_0 {
  struct __block_impl impl;
  struct __MyObject__test_block_desc_0* Desc;

  MyObject *const __weak self_weak_; // 捕獲的是self_weak_

  __MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *const __weak _self_weak_, int flags=0) : self_weak_(_self_weak_) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
  MyObject *const __weak self_weak_ = __cself->self_weak_; // bound by copy

    /* @autoreleasepool */{ __AtAutoreleasePool __autoreleasepool; } __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_;
    (*(NSString *__strong *)((char *)self + OBJC_IVAR_$_MyObject$_age)) = (NSString *)&__NSConstantStringImpl__var_folders_jf_zkvr_3r17rl3q1fw6zhz6jr40000gn_T_person_a08aad_mi_3;
}
  • block默認捕獲self是強引用(MyObject *const __strong self;)虫给;而使用上面修飾后@weakify(self),block內(nèi)部捕獲的是MyObject *const __weak self_weak_;侠碧,因此打破了循環(huán)引用抹估。

  • 注意1:block內(nèi)部如果使用_name方式引用,也會導致循環(huán)引用弄兜;必須通過self.name 或者 self->name才能打破循環(huán)引用药蜻。
    具體原因,網(wǎng)上有人是說對self和self_weak_都進行了捕獲替饿,可是clang之后的代碼沒有發(fā)現(xiàn)顯示引用语泽,有待研究。

  • 注意2:@weakify@strongify必須都使用视卢。
    測試發(fā)現(xiàn)如果只使用@weakify踱卵,而不調(diào)@ strongify一樣會導致循環(huán)引用。原因是這種情況下捕獲的還是self据过,而不是self_weak_惋砂。

struct __MyObject__test_block_impl_0 {
  struct __block_impl impl;
  struct __MyObject__test_block_desc_0* Desc;
  MyObject *const __strong self;
  __MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
  MyObject *const __strong self = __cself->self; // bound by copy
  ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_jf_zkvr_3r17rl3q1fw6zhz6jr40000gn_T_person_2fca58_mi_3);
}

參考

iOS 底層解析weak的實現(xiàn)原理
iOS weak 底層實現(xiàn)原理:StripedMap

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市绳锅,隨后出現(xiàn)的幾起案子西饵,更是在濱河造成了極大的恐慌,老刑警劉巖鳞芙,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眷柔,死亡現(xiàn)場離奇詭異,居然都是意外死亡原朝,警方通過查閱死者的電腦和手機闯割,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竿拆,“玉大人宙拉,你說我怎么就攤上這事”瘢” “怎么了谢澈?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵煌贴,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么纵顾? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮淹朋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钉答。我一直安慰自己础芍,他們只是感情好,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布数尿。 她就那樣靜靜地躺著仑性,像睡著了一般。 火紅的嫁衣襯著肌膚如雪右蹦。 梳的紋絲不亂的頭發(fā)上诊杆,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機與錄音何陆,去河邊找鬼晨汹。 笑死,一個胖子當著我的面吹牛贷盲,可吹牛的內(nèi)容都是我干的淘这。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼晃洒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了朦乏?” 一聲冷哼從身側(cè)響起球及,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎呻疹,沒想到半個月后吃引,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡刽锤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年镊尺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片并思。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡庐氮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宋彼,到底是詐尸還是另有隱情弄砍,我是刑警寧澤仙畦,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站音婶,受9級特大地震影響慨畸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜衣式,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一寸士、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碴卧,春花似錦弱卡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至界弧,卻和暖如春凡蜻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背垢箕。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工划栓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人条获。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓忠荞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親帅掘。 傳聞我的和親對象是個殘疾皇子委煤,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容