__weak、__strong、__block修飾Block的內部實現原理

關于block的上一篇文章Block內部實現

先貼一段代碼,接下來根據這段代碼逐步分析__weak、__strong、__block這些關鍵字的作用

一. 查看__weak、__strong、__block在這些修飾符下的源碼

main函數實現
typedef void (^Block) (void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int no = 20;
        __block int age = 10;
        NSObject *object = [[NSObject alloc] init];
        __weak NSObject *weakObject = object;
        
        Block block = ^{
            age = 20;
            
            NSLog(@"%d", no);
            NSLog(@"%d", age);
            NSLog(@"%p", weakObject);
        };
        
        struct __main_block_impl_0* blockImpl = (__bridge struct __main_block_impl_0*)block;
        block();
      
        age = 30;
    }
    return 0;
}
以下代碼是將OC代碼轉換為C/C++代碼的結果
1. 使用__block修飾以后age變成了__Block_byref_age_0這種數據結構,是一個OC對象,含有isa指針,__forwarding指針指向自己,結構體大小__size,真正用來存儲age數值的變量。
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};
2. __main_block_impl_0這種數據結構比最簡單的block多了三個參數,分別是int no;NSObject *__weak weakObject;__Block_byref_age_0 *age;,no值傳遞,age默認使用強引用。weakObject使用__weak修飾是因為外部使用了弱引用。如果外部不使用__weak修飾,默認會使用__strong修飾,也就是強引用。
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int no;
  NSObject *__weak weakObject;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _no, NSObject *__weak _weakObject, __Block_byref_age_0 *_age, int flags=0) : no(_no), weakObject(_weakObject), age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
3. __main_block_func_0這個函數主要就是調用block需要執行的具體函數。可以看到訪問age的時候使用了(age->__forwarding->age) = 20;這種方式,而不是直接使用age->age,這是為了保證block復制到堆上以后無論是在棧上還是在堆上的block都可以訪問到同一份block捕獲的堆上的變量。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref
    int no = __cself->no; // bound by copy
    NSObject *__weak weakObject = __cself->weakObject; // bound by copy
    
    (age->__forwarding->age) = 20;
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d47e2d_mi_0, no);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d47e2d_mi_1, (age->__forwarding->age));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d47e2d_mi_2, weakObject);
}
4. __main_block_copy_0、_Block_object_assign在捕獲對象的時候進行對象的內存管理。如果是__block修飾的類型就是強引用,如果是對象類型根據外部的修飾類型而定,如果外部使用__strong修飾就是強引用,如果是使用__weak這里邊就使用弱引用。可以看到這里BLOCK_FIELD_IS_BYREFBLOCK_FIELD_IS_OBJECT修飾類型是不同的。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->weakObject, (void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
5. __main_block_desc_0 這里比最簡單的block多了兩個函數指針,分別是copydispose。函數實現就是上邊4的兩個函數。
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
6. main函數刪除了多余的部分如下所示。
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int no = 20;

        __Block_byref_age_0 age = {(void*)0,&age, 0, sizeof(__Block_byref_age_0), 10};

        NSObject *object = objc_msgSend(objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        __attribute__((objc_ownership(weak))) NSObject *weakObject = object;

        Block block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, no, weakObject, &age, 570425344);
        block->FuncPtr(block);

        (age.__forwarding->age) = 30;
    }
    return 0;
}

如上可以看到__block int age = 10;變成了 __Block_byref_age_0 age = {(void*)0,&age, 0, sizeof(__Block_byref_age_0), 10};
age = 30;變成了(age.__forwarding->age) = 30;
這里就是在棧上訪問age最終存儲的地方,為了保持和block內部訪問到的是同一個地方所以使用了age.__forwarding先找到堆上的age這個結構體的地址,然后在訪問堆上該結構體的變量age

二. 設置斷點查看age具體存放的內存地址

將以下這些結構體放在main函數上邊,用來獲取成員變量的內存地址的時候使用。

struct __Block_byref_age_0 {
    void *__isa;
    struct __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
};

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(void);
    void (*dispose)(void);
};

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    struct __Block_byref_age_0 *age;
};

由下邊可知直接獲取age的地址和使用&(blockImpl->age->__forwarding->age)獲取的地址是一樣的,也就是說直接使用age就是訪問的copy到堆上的__Block_byref_age_0結構體里邊的age

(lldb) p/x &age
(int *) $0 = 0x000000010065a598
(lldb) p/x &(blockImpl->age)
(__Block_byref_age_0 **) $1 = 0x000000010065a560
(lldb) p/x &(blockImpl->age->age)
(int *) $2 = 0x000000010065a598
(lldb) p/x &(blockImpl->age->__forwarding->age)
(int *) $3 = 0x000000010065a598

三. __block修飾符

  1. __block可以用于解決block內部無法修改auto變量值的問題
  2. __block不能修飾全局變量、靜態變量(static)
  3. 編譯器會將__block變量包裝成一個對象
__block修飾符

四. __block的內存管理

  1. 當block在棧上時,并不會對__block變量產生強引用

  2. 當block被copy到堆時
    a) 會調用block內部的copy函數
    b) copy函數內部會調用_Block_object_assign函數
    c) _Block_object_assign函數會對__block變量形成強引用(retain)


    __block的內存管理-引用
  3. 當block從堆中移除時
    a) 會調用block內部的dispose函數
    b) dispose函數內部會調用_Block_object_dispose函數
    c) _Block_object_dispose函數會自動釋放引用的__block變量(release)

__block的內存管理-釋放

四. __block的__forwarding指針

__block的__forwarding指針

五. 對象類型的auto變量、__block變量

  1. 當block在棧上時,對它們都不會產生強引用

  2. 當block拷貝到堆上時,都會通過copy函數來處理它們
    __block變量(假設變量名叫做a)
    a) _Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);

  3. 對象類型的auto變量(假設變量名叫做p)
    a) _Block_object_assign((void)&dst->p, (void)src->p, 3/BLOCK_FIELD_IS_OBJECT/);

  4. 當block從堆上移除時,都會通過dispose函數來釋放它們
    __block變量(假設變量名叫做a)
    a) _Block_object_dispose((void)src->a, 8/BLOCK_FIELD_IS_BYREF*/);

  5. 對象類型的auto變量(假設變量名叫做p)
    a) _Block_object_dispose((void)src->p, 3/BLOCK_FIELD_IS_OBJECT*/);

六. 被__block修飾的對象類型

  1. 當__block變量在棧上時,不會對指向的對象產生強引用

  2. 當__block變量被copy到堆時
    a) 會調用__block變量內部的copy函數
    b) copy函數內部會調用_Block_object_assign函數
    c) _Block_object_assign函數會根據所指向對象的修飾符(__strong、__weak、__unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用(注意:這里僅限于ARC時會retain,MRC時不會retain)

  3. 如果__block變量從堆上移除
    a) 會調用__block變量內部的dispose函數
    b) dispose函數內部會調用_Block_object_dispose函數
    c) _Block_object_dispose函數會自動釋放指向的對象(release)

下一篇 Block 解決循環引用(ARC、MRC)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,791評論 6 545
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,795評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,943評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,057評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,773評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,106評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,082評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,282評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,793評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,507評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,741評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,220評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,929評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,325評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,661評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,482評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,702評論 2 380

推薦閱讀更多精彩內容