ios -- block相關(guān)知識

所有的Block里面的self必須要weak一下?

很顯然答案不都是,有些情況下是可以直接使用self的,比如調(diào)用系統(tǒng)的方法:

[UIView?animateWithDuration:0.5?animations:^{?NSLog(@"%@",?self);

????}];

因?yàn)檫@個block存在于靜態(tài)方法中,雖然block對self強(qiáng)引用著,但是self卻不持有這個靜態(tài)方法,所以完全可以在block內(nèi)部使用self。

另外,來看一個Masonry代碼布局的例子,這里面的self會不會造成循環(huán)引用呢?

[self.headView?mas_makeConstraints:^(MASConstraintMaker?*make)?{

????make.centerY.equalTo(self.otherView.mas_centerY);

}];

并不是 block 就一定會造成循環(huán)引用,是不是循環(huán)引用要看是不是相互持有強(qiáng)引用。block 里用到了 self,那 block 會保持一個 self 的引用,但是 self 并沒有直接或者間接持有 block,所以不會造成循環(huán)引用??梢钥匆幌翸asonry的源代碼:

View+MASAdditions.m

-?(NSArray?*)mas_makeConstraints:(void(^)(MASConstraintMaker?*))block?{?self.translatesAutoresizingMaskIntoConstraints?=?NO;

????MASConstraintMaker?*constraintMaker?=?[[MASConstraintMaker?alloc]?initWithView:self];

????block(constraintMaker);?return?[constraintMaker?install];

}

Block引起的循環(huán)引用

一般來說我們總會在設(shè)置Block之后,在合適的時間回調(diào)Block,而不希望回調(diào)Block的時候Block已經(jīng)被釋放了,所以我們需要對Block進(jìn)行copy,copy到堆中,以便后用。

Block可能會導(dǎo)致循環(huán)引用問題,因?yàn)閎lock在拷貝到堆上的時候,會retain其引用的外部變量,那么如果block中如果引用了他的宿主對象,那很有可能引起循環(huán)引用

注意觀察,這個作為方法參數(shù)的Block體并沒有被任何方持有。因此,我們放心在Masonry中使用self.xxx 不會循環(huán)引用的。而且這個block里面用weakSelf還有可能會出問題,因?yàn)閙as_qeual如果得到一個nil參數(shù)的話應(yīng)該會導(dǎo)致程序崩潰。

因?yàn)閁IView未強(qiáng)持有block,所以這個block只是個棧block,而且構(gòu)不成循環(huán)引用的條件。棧block有個特性就是它執(zhí)行完畢之后就出棧,出棧了就會被釋放掉??磎as_makexxx的方法實(shí)現(xiàn)會發(fā)現(xiàn)這個block很快就被調(diào)用了,完事兒就出棧銷毀,構(gòu)不成循環(huán)引用,所以可以直接放心的使self。另外,這個與網(wǎng)絡(luò)請求里面使用self道理是一樣的。

Block與內(nèi)存管理

根據(jù)Block在內(nèi)存中的位置分為三種類型:

NSGlobalBlock是位于全局區(qū)的block,它是設(shè)置在程序的數(shù)據(jù)區(qū)域(.data區(qū))中。

NSStackBlock是位于棧區(qū),超出變量作用域,棧上的Block以及 __block變量都被銷毀。

NSMallocBlock是位于堆區(qū),在變量作用域結(jié)束時不受影響。

注意:在 ARC 開啟的情況下,將只會有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 類型的 block。

位于堆內(nèi)存:MallocBlock

堆中的block無法直接創(chuàng)建,其需要由_NSConcreteStackBlock類型的block拷貝而來(也就是說block需要執(zhí)行copy之后才能存放到堆中)。由于block的拷貝最終都會調(diào)用_Block_copy_internal函數(shù)。

截獲自動變量值

Block表達(dá)式可截獲所使用的自動變量的值。截獲:保存自動變量的瞬間值。因?yàn)槭恰八查g值”,所以聲明Block之后,即便在Block外修改自動變量的值,也不會對Block內(nèi)截獲的自動變量值產(chǎn)生影響。

inti=10;

void(^blk)(void)=^{

NSLog(@"In block, i = %d",i)

};

i=20;//Block外修改變量i,也不影響B(tài)lock內(nèi)的自動變量blk();

//i修改為20后才執(zhí)行,打印: In block, i = 10NSLog(@"i = %d",i);

//打?。篿 = 20

__block說明符號

自動變量截獲的值為Block聲明時刻的瞬間值,保存后就不能改寫該值,如需對自動變量進(jìn)行重新賦值,需要在變量聲明前附加__block說明符,這時該變量稱為__block變量。

__blockinti=10;//i為__block變量,可在block中重新賦值

void(^blk)(void)=^{

NSLog(@"In block, i = %d",i);

};

i=20;

blk();//打印: In block, i = 20

NSLog(@"i = %d",i);//打印:i = 20

自動變量值為一個對象情況

當(dāng)自動變量為一個類的對象,且沒有使用__block修飾時,雖然不可以在Block內(nèi)對該變量進(jìn)行重新賦值,但可以修改該對象的屬性。如果該對象是個Mutable的對象,例如NSMutableArray,則還可以在Block內(nèi)對NSMutableArray進(jìn)行元素的增刪:

NSMutableArray*array=[[NSMutableArrayalloc]initWithObjects:@"1",@"2",nil];

NSLog(@"Array Count:%ld",array.count);//打印Array Count:2

void(^blk)(void)=^{

[arrayremoveObjectAtIndex:0];//Ok

//array = [NSNSMutableArray new];//沒有__block修飾,編譯失?。?/p>

};

blk();

NSLog(@"Array Count:%ld",array.count);//打印Array Count:1

Block也是Objective-C中的對象。

根據(jù)Block對象創(chuàng)建時所處數(shù)據(jù)區(qū)不同而進(jìn)行區(qū)別:

_NSConcreteStackBlock:在棧上創(chuàng)建的Block對象

_NSConcreteMallocBlock:在堆上創(chuàng)建的Block對象

_NSConcreteGlobalBlock:全局?jǐn)?shù)據(jù)區(qū)的Block對象

使用__block發(fā)生了什么

Block捕獲的自動變量添加__block說明符,就可在Block內(nèi)讀和寫該變量,也可以在原來的棧上讀寫該變量。自動變量的截獲保證了棧上的自動變量被銷毀后,Block內(nèi)仍可使用該變量。__block保證了棧上和Block內(nèi)(通常在堆上)可以訪問和修改“同一個變量”,__block是如何實(shí)現(xiàn)這一功能的?

__block發(fā)揮作用的原理:將棧上用__block修飾的自動變量封裝成一個結(jié)構(gòu)體,讓其在堆上創(chuàng)建,以方便從棧上或堆上訪問和修改同一份數(shù)據(jù)。

Block循環(huán)引用原因:一個對象A有Block類型的屬性,從而持有這個Block,如果Block的代碼塊中使用到這個對象A,或者僅僅是用用到A對象的屬性,會使Block也持有A對象,導(dǎo)致兩者互相持有,不能在作用域結(jié)束后正常釋放。

全局塊存在于全局內(nèi)存中, 相當(dāng)于單例.

棧塊存在于棧內(nèi)存中, 超出其作用域則馬上被銷毀

堆塊存在于堆內(nèi)存中, 是一個帶引用計(jì)數(shù)的對象, 需要自行管理其內(nèi)存

簡而言之,存儲在棧中的Block就是棧塊、存儲在堆中的就是堆塊、既不在棧中也不在堆中的塊就是全局塊。

Block訪問外界變量

MRC?環(huán)境下:訪問外界變量的 Block 默認(rèn)存儲中。

ARC?環(huán)境下:訪問外界變量的 Block 默認(rèn)存儲在中(實(shí)際是放在棧區(qū),然后ARC情況下自動又拷貝到堆區(qū)),自動釋放。

Block的復(fù)制操作執(zhí)行的是copy實(shí)例方法。不同類型的Block使用copy方法的效果如下表:


三、Block與外界變量

1、截獲自動變量(局部變量)值

(1)默認(rèn)情況

對于 block 外的變量引用,block 默認(rèn)是將其復(fù)制到其數(shù)據(jù)結(jié)構(gòu)中來實(shí)現(xiàn)訪問的。也就是說block的自動變量截獲只針對block內(nèi)部使用的自動變量, 不使用則不截獲, 因?yàn)榻孬@的自動變量會存儲于block的結(jié)構(gòu)體內(nèi)部, 會導(dǎo)致block體積變大。特別要注意的是默認(rèn)情況下block只能訪問不能修改局部變量的值。

int age = 10;

myBlock block = ^{

? ? NSLog(@"age = %d", age);

};

age = 18;

block();

輸出結(jié)果

age = 10

?__block 修飾的外部變量

對于用 __block 修飾的外部變量引用,block 是復(fù)制其引用地址來實(shí)現(xiàn)訪問的。block可以修改__block 修飾的外部變量的值。

__block int age = 10;

myBlock block = ^{

? ? NSLog(@"age = %d", age);

};

age = 18;

block();

輸出為:

age = 18

為什么要用copy去修飾block呢

個人理解:默認(rèn)情況下,block會存檔在棧中(棧是吃了吐),所以block會在函數(shù)調(diào)用結(jié)束被銷毀,在調(diào)用會報(bào)空指針異常,如果用copy修飾的話,可以使其保存在堆區(qū)(堆是吃了拉) ,它的生命周期會隨著對象的銷毀而結(jié)束的。只要對象不銷毀,我們就可以調(diào)用在堆中的block。

在了解block為什么要用copy之前,我們要先了解block的三種類型

一NSGlobalBlock:全局的靜態(tài)block 沒有訪問外部變量 你的block類型就是這種類型(也就是說你的block沒有調(diào)用其他外部變量)

二NSStackBlock:保存在棧中的block,沒有用copy去修飾并且訪問了外部變量,你的block類型就是這種類型,會在函數(shù)調(diào)用結(jié)束被銷毀 (需要在MRC)

三NSMallocBlock 保存在堆中的block 此類型blcok是用copy修飾出來的block 它會隨著對象的銷毀而銷毀,只要對象不銷毀,我們就可以調(diào)用的到在堆中的block。

信有很多面試者被問到這樣的問題:block使用什么修飾,往往能夠答出是copy,很多面試官就會問到:為什么要使用copy,這時候就懵了。

我親身體驗(yàn)了一把,所以先總結(jié)一下。

block本身是像對象一樣可以retain,和release。但是,block在創(chuàng)建的時候,它的內(nèi)存是分配在棧上的,而不是在堆上。他本身的作于域是屬于創(chuàng)建時候的作用域,一旦在創(chuàng)建時候的作用域外面調(diào)用block將導(dǎo)致程序崩潰。因?yàn)闂^(qū)的特點(diǎn)就是創(chuàng)建的對象隨時可能被銷毀,一旦被銷毀后續(xù)再次調(diào)用空對象就可能會造成程序崩潰,在對block進(jìn)行copy后,block存放在堆區(qū).

使用retain也可以,但是block的retain行為默認(rèn)是用copy的行為實(shí)現(xiàn)的,

因?yàn)閎lock變量默認(rèn)是聲明為棧變量的,為了能夠在block的聲明域外使用,所以要把block拷貝(copy)到堆,所以說為了block屬性聲明和實(shí)際的操作一致,最好聲明為copy。

?block?會自動捕獲block?內(nèi)部使用的變量,并對其強(qiáng)引用

block是通過添加引用來訪問實(shí)例變量的,所以self會被retain一次,block也是一個強(qiáng)引用,會引起循環(huán)引用。

3、關(guān)于 __strong

- (void)viewDidLoad {? ? [super viewDidLoad];? ??

MyOBJ *mm = [[MyOBJ alloc]init];? ? mm.name = @"Lilei";??

? __weak typeof(student) weakSelf = mm;? ? ??

? mm.doBlock = ^{? ? ? ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{? ? ? ??

? ? NSLog(@"my name is = %@",weakSelf.name);? ? ??

? });? ?

?};??

? mm.doBlock();}

//輸出:my name is = (null)

在dispatch_after這個函數(shù)里面。在doBlock()的block結(jié)束之后,mm被自動釋放了。 又由于dispatch_after里面捕獲的__weak的mm,在原對象釋放之后,__weak對象就會變成nil,防止野指針。

那么我們怎么才能在weakSelf之后,block里面還能繼續(xù)使用weakSelf之后的對象呢?

究其根本原因就是weakSelf之后,無法控制什么時候會被釋放,為了保證在block內(nèi)不會被釋放,需要添加__strong。

weakSelf 是為了block不持有self,避免Retain Circle循環(huán)引用。

在 Block 內(nèi)如果需要訪問 self 的方法、變量,建議使用 weakSelf。

strongSelf的目的是因?yàn)橐坏┻M(jìn)入block執(zhí)行,假設(shè)不允許self在這個執(zhí)行過程中釋放,就需要加入strongSelf。block執(zhí)行完后這個strongSelf 會自動釋放,不會存在循環(huán)引用問題。

如果在 Block 內(nèi)需要多次 訪問 self,則需要使用 strongSelf。

關(guān)于 多層嵌套的block

- (void)setUpModel{

? ? XXModel *model = [XXModel new];

__weak typeof(self) weakSelf = self;

? ? model.dodoBlock = ^(NSString *title) {

? ? ? ? __strong typeof(self) strongSelf = weakSelf;//第一層

? ? ? ? strongSelf.titleLabel.text = title;


? ? ? ? __weak typeof(self) weakSelf2 = strongSelf;

? ? ? ? strongSelf.model.dodoBlock = ^(NSString *title2) {

? ? ? ? ? ? __strong typeof(self) strongSelf2 = weakSelf2;//第二層

? ? ? ? ? ? strongSelf2.titleLabel.text = title2;

? ? ? ? };

? ? };

? ? self.model = model;

}

這樣就避免的引用循環(huán),不管都多少個block嵌套,都可以按照這樣來做。

OS block嵌套block中weakify的使用

結(jié)論:嵌套中的block只需要寫strongify,不需要再寫一次weakify

只要持有block的變量和block中的變量不是同一個變量(可以指向同一個變量),就不會因此循環(huán)引用,導(dǎo)致memory leak。

所以,當(dāng)block嵌套block的時候,內(nèi)部的block不需要再次增加@weakify(self)。

https://juejin.im/post/5b9b24b05188255c372f437c

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容