關(guān)于OC里面的block循環(huán)引用


??現(xiàn)在block是越來越多人在用晓锻,幾乎代替了delegate专执,你可以發(fā)現(xiàn)現(xiàn)在apple新出的API奈偏,以及升級的API坞嘀,都是block去實現(xiàn)的,這里優(yōu)缺點(diǎn)不說了惊来,只說其中一個最重要的注意點(diǎn):循環(huán)引用丽涩。
??block+循環(huán)引用,這里我們將這個問題拆開來,逐一解決矢渊,從此媽媽再也不擔(dān)心我使用block了继准。

block

??最簡單的解釋:代碼塊(內(nèi)聯(lián)函數(shù)),可以內(nèi)嵌在方法體中矮男。
??上面的字面解釋移必,相信是大多數(shù)初學(xué)者的認(rèn)知。下面是我個人的見解毡鉴,希望可以幫到你崔泵,上代碼

??先看一個最簡單的block,沒有用外部對象self的猪瞬,如下:
typedef void(^Ice)(NSString *name);

@implementation IceCream

- (instancetype)init {
    if (self = [super init]) {
        NSString *string = @"123";
        Ice iceBlock = ^(NSString *name) {
            NSLog(@"%@",name);
        };
        iceBlock(string);
    }
    return self;
}

@end

??這里我們看到的是OC代碼憎瘸,簡單的聲明了一個局部變量iceBlock,光從OC角度來看撑螺,并不能看出來什么含思,那么我們往深了看,讓我們看一下clang編譯器將他變成c++的樣子甘晤,看看最后編譯器將他解釋成一個什么含潘。(下面只是截取我認(rèn)為有用的部分代碼)

struct __block_impl {
  void *isa;//相信知道OC對象的,應(yīng)該也知道isa指針吧线婚,原來block也是可以看做對象的
  int Flags;//
  int Reserved;
  void *FuncPtr;//方法體
};

struct __IceCream__init_block_impl_0 {
  struct __block_impl impl;
  struct __IceCream__init_block_desc_0* Desc;
  __IceCream__init_block_impl_0(void *fp, struct __IceCream__init_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

??這里我們看到兩個結(jié)構(gòu)體遏弱,一個__block_impl(block最最基本的機(jī)構(gòu)),一個__IceCream__init_block_impl_0塞弊。
??第二個結(jié)構(gòu)體里面有一個指針impl指向了上面的結(jié)構(gòu)體漱逸,也就是block最基本的內(nèi)容,至于這個結(jié)構(gòu)體名字為什么叫__IceCream__init_block_impl_0游沿,應(yīng)該是指iceBlock外面的init方法(個人猜測)饰抒,這個結(jié)構(gòu)體里面的__IceCream__init_block_impl_0方法,并且有入?yún)⒕魇颍椒ɡ锩媸菍?code>__block_impl的賦值袋坑,以及對其他指針的賦值(這里看到的是__IceCream__init_block_desc_0* Desc)其實是對自身的一個解釋賦值,里面有flags眯勾,desc枣宫,最重要的是isaFuncPtr(個人建議:如果你把C++的結(jié)構(gòu)體看做OC的對象吃环,可能會更好理解一點(diǎn))這只是簡單的一個局部變量也颤,并且里面是沒有用到外部任何變量的情況。

??下面我們看一個引用了self外部對象參數(shù)的block
typedef void(^Person)(void);

@interface ViewController ()

@property (nonatomic, copy) Person personBlock;

@property (nonatomic, strong) NSString *name;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.name = @"VenpleD";
    
    self.personBlock = ^{
        self.name = @"HelloWorld";
    };
}

再來將他編譯成c++文件

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  ViewController *self;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, ViewController *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
/***block方法實現(xiàn)的imp*****/
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
  ViewController *self = __cself->self; // bound by copy

        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_mm_hb3kj211641625fw0pq0xlkrkk1cf__T_ViewController_1108c8_mi_1);
    }

/***viewdidload***/
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_mm_hb3kj211641625fw0pq0xlkrkk1cf__T_ViewController_1108c8_mi_0);
/**調(diào)用personBlock**/
    ((void (*)(id, SEL, Person))(void *)objc_msgSend)((id)self, sel_registerName("setPersonBlock:"), ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, self, 570425344)));
}

??對比兩個block的c++形式郁轻,我們發(fā)現(xiàn)翅娶,在__ViewController__viewDidLoad_block_impl_0中多了一個類型指針ViewController *,在他的賦值方法__ViewController__viewDidLoad_block_impl_0中也多了一個入?yún)?code>ViewController *_self
??我們再看_I_ViewController_viewDidLoad方法中是怎么調(diào)用personBlock的,是向self發(fā)送了setPersonBlock消息故觅,并且把selfpersonBlock地址以及personBlock內(nèi)部方法的實現(xiàn)地址傳過去厂庇。
再看personBlock方法實現(xiàn)__ViewController__viewDidLoad_block_func_0里面渠啊,ViewController *self = __cself->self;這里__cself指的是personBlock输吏,__cself->self指的就是ViewController了,然后向這里的self發(fā)送setName消息替蛉。
??personBlock里面有強(qiáng)引用viewcontroller贯溅,viewController又強(qiáng)引用personBlock,這樣就造成循環(huán)引用了躲查。

循環(huán)引用

來看一張相當(dāng)簡陋的圖


圖一.png

在這張圖里面介紹的兩種情況它浅,想想除了這兩種情況還有其他的么?這里我不說了镣煮,只是幫你理解姐霍,幫你分析,怎么找到這個環(huán)典唇。

解決方法

這個解決方法不用說镊折,大家也知道,weakstrong介衔,爛大街了都恨胚,為什么用了這兩個就好了呢?你不想看看用了weakstrong炎咖,編譯后C++代碼發(fā)生了什么變化赃泡?
Come On

- (void)viewDidLoad {
    [super viewDidLoad];
    self.name = @"VenpleD";
    __weak typeof(ViewController) *weakSelf = self;
    self.personBlock = ^{
        __strong typeof(ViewController) *strongSelf = weakSelf;
        strongSelf.name = @"HelloWorld";
    };
}
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  typeof(ViewController) *__weak weakSelf;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, typeof(ViewController) *__weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
/**personBlock方法實現(xiàn)**/
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
  typeof(ViewController) *__weak weakSelf = __cself->weakSelf; // bound by copy

        __attribute__((objc_ownership(strong))) typeof(ViewController) *strongSelf = weakSelf;
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)strongSelf, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_mm_hb3kj211641625fw0pq0xlkrkk1cf__T_ViewController_b406fe_mi_1);
    }
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_mm_hb3kj211641625fw0pq0xlkrkk1cf__T_ViewController_b406fe_mi_0);
/**轉(zhuǎn)換weak指針**/
    __attribute__((objc_ownership(weak))) typeof(ViewController) *weakSelf = self;
/**調(diào)用personBlock**/
    ((void (*)(id, SEL, Person))(void *)objc_msgSend)((id)self, sel_registerName("setPersonBlock:"), ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, weakSelf, 570425344)));
}

??對比上面沒有用weak和strong的,我們發(fā)現(xiàn)self的指針修飾變成了typeof(ViewController) *__weak乘盼,居然變成weak類型的(我們知道默認(rèn)不加是strong類型的)升熊,這樣我們的圖就變成了下面這個樣子,也就解決了循環(huán)引用的問題了绸栅。
??到了這里可能大家還有關(guān)于為什么block內(nèi)部要再用strong指針強(qiáng)引用一次外部weak指針级野。好,我解釋一下
??我們回頭看personBlock實現(xiàn)__ViewController__viewDidLoad_block_func_0中阴幌,objc_msgSend方法的對象和消息分別是strongSelfsetName勺阐,我們知道,strong一次矛双,就會對這個對象增加一次引用計數(shù)渊抽,這樣做,保證了方法正常執(zhí)行议忽。在這個方法里面懒闷,strongSelf是個局部變量,生命周期是函數(shù)末尾,也就是執(zhí)行完objc_msgSeng以后就會將這個對象(指ViewController)的引用計數(shù)減1愤估,帮辟,如果執(zhí)行完personBlockpersonBlock引用計數(shù)為0正常釋放玩焰,自然他也不會對ViewController強(qiáng)引用由驹,弱引用指針自動斷開,viewController也就很好的釋放了昔园。

圖二.png

看到這里蔓榄,理解了么?
??這只是我的個人理解默刚,如果大家有什么見解甥郑,可以一起探討,一起學(xué)習(xí)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荤西,一起剝皮案震驚了整個濱河市澜搅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌邪锌,老刑警劉巖勉躺,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異秃流,居然都是意外死亡赂蕴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門舶胀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來概说,“玉大人,你說我怎么就攤上這事嚣伐√桥猓” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵轩端,是天一觀的道長放典。 經(jīng)常有香客問我,道長基茵,這世上最難降的妖魔是什么奋构? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮拱层,結(jié)果婚禮上弥臼,老公的妹妹穿的比我還像新娘。我一直安慰自己根灯,他們只是感情好径缅,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布掺栅。 她就那樣靜靜地躺著,像睡著了一般纳猪。 火紅的嫁衣襯著肌膚如雪氧卧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天氏堤,我揣著相機(jī)與錄音沙绝,去河邊找鬼。 笑死丽猬,一個胖子當(dāng)著我的面吹牛宿饱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脚祟,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼强饮!你這毒婦竟也來了由桌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤邮丰,失蹤者是張志新(化名)和其女友劉穎行您,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剪廉,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡娃循,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了斗蒋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捌斧。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖泉沾,靈堂內(nèi)的尸體忽然破棺而出捞蚂,到底是詐尸還是另有隱情,我是刑警寧澤跷究,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布姓迅,位于F島的核電站,受9級特大地震影響俊马,放射性物質(zhì)發(fā)生泄漏丁存。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一柴我、第九天 我趴在偏房一處隱蔽的房頂上張望解寝。 院中可真熱鬧,春花似錦屯换、人聲如沸编丘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘉抓。三九已至索守,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抑片,已是汗流浹背卵佛。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敞斋,地道東北人截汪。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像植捎,于是被迫代替她去往敵國和親衙解。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

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