所有的Block里面的self必須要weak一下?
很顯然答案不都是,有些情況下是可以直接使用self的,比如調用系統的方法:
[UIView?animateWithDuration:0.5?animations:^{?NSLog(@"%@",?self);
????}];
因為這個block存在于靜態方法中,雖然block對self強引用著,但是self卻不持有這個靜態方法,所以完全可以在block內部使用self。
另外,來看一個Masonry代碼布局的例子,這里面的self會不會造成循環引用呢?
[self.headView?mas_makeConstraints:^(MASConstraintMaker?*make)?{
????make.centerY.equalTo(self.otherView.mas_centerY);
}];
并不是 block 就一定會造成循環引用,是不是循環引用要看是不是相互持有強引用。block 里用到了 self,那 block 會保持一個 self 的引用,但是 self 并沒有直接或者間接持有 block,所以不會造成循環引用。可以看一下Masonry的源代碼:
View+MASAdditions.m
-?(NSArray?*)mas_makeConstraints:(void(^)(MASConstraintMaker?*))block?{?self.translatesAutoresizingMaskIntoConstraints?=?NO;
????MASConstraintMaker?*constraintMaker?=?[[MASConstraintMaker?alloc]?initWithView:self];
????block(constraintMaker);?return?[constraintMaker?install];
}
Block引起的循環引用
一般來說我們總會在設置Block之后,在合適的時間回調Block,而不希望回調Block的時候Block已經被釋放了,所以我們需要對Block進行copy,copy到堆中,以便后用。
Block可能會導致循環引用問題,因為block在拷貝到堆上的時候,會retain其引用的外部變量,那么如果block中如果引用了他的宿主對象,那很有可能引起循環引用
注意觀察,這個作為方法參數的Block體并沒有被任何方持有。因此,我們放心在Masonry中使用self.xxx 不會循環引用的。而且這個block里面用weakSelf還有可能會出問題,因為mas_qeual如果得到一個nil參數的話應該會導致程序崩潰。
因為UIView未強持有block,所以這個block只是個棧block,而且構不成循環引用的條件。棧block有個特性就是它執行完畢之后就出棧,出棧了就會被釋放掉。看mas_makexxx的方法實現會發現這個block很快就被調用了,完事兒就出棧銷毀,構不成循環引用,所以可以直接放心的使self。另外,這個與網絡請求里面使用self道理是一樣的。
Block與內存管理
根據Block在內存中的位置分為三種類型:
NSGlobalBlock是位于全局區的block,它是設置在程序的數據區域(.data區)中。
NSStackBlock是位于棧區,超出變量作用域,棧上的Block以及 __block變量都被銷毀。
NSMallocBlock是位于堆區,在變量作用域結束時不受影響。
注意:在 ARC 開啟的情況下,將只會有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 類型的 block。
位于堆內存:MallocBlock
堆中的block無法直接創建,其需要由_NSConcreteStackBlock類型的block拷貝而來(也就是說block需要執行copy之后才能存放到堆中)。由于block的拷貝最終都會調用_Block_copy_internal函數。
截獲自動變量值
Block表達式可截獲所使用的自動變量的值。截獲:保存自動變量的瞬間值。因為是“瞬間值”,所以聲明Block之后,即便在Block外修改自動變量的值,也不會對Block內截獲的自動變量值產生影響。
inti=10;
void(^blk)(void)=^{
NSLog(@"In block, i = %d",i)
};
i=20;//Block外修改變量i,也不影響Block內的自動變量blk();
//i修改為20后才執行,打印: In block, i = 10NSLog(@"i = %d",i);
//打印:i = 20
__block說明符號
自動變量截獲的值為Block聲明時刻的瞬間值,保存后就不能改寫該值,如需對自動變量進行重新賦值,需要在變量聲明前附加__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
自動變量值為一個對象情況
當自動變量為一個類的對象,且沒有使用__block修飾時,雖然不可以在Block內對該變量進行重新賦值,但可以修改該對象的屬性。如果該對象是個Mutable的對象,例如NSMutableArray,則還可以在Block內對NSMutableArray進行元素的增刪:
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修飾,編譯失敗!
};
blk();
NSLog(@"Array Count:%ld",array.count);//打印Array Count:1
Block也是Objective-C中的對象。
根據Block對象創建時所處數據區不同而進行區別:
_NSConcreteStackBlock:在棧上創建的Block對象
_NSConcreteMallocBlock:在堆上創建的Block對象
_NSConcreteGlobalBlock:全局數據區的Block對象
使用__block發生了什么
Block捕獲的自動變量添加__block說明符,就可在Block內讀和寫該變量,也可以在原來的棧上讀寫該變量。自動變量的截獲保證了棧上的自動變量被銷毀后,Block內仍可使用該變量。__block保證了棧上和Block內(通常在堆上)可以訪問和修改“同一個變量”,__block是如何實現這一功能的?
__block發揮作用的原理:將棧上用__block修飾的自動變量封裝成一個結構體,讓其在堆上創建,以方便從棧上或堆上訪問和修改同一份數據。
Block循環引用原因:一個對象A有Block類型的屬性,從而持有這個Block,如果Block的代碼塊中使用到這個對象A,或者僅僅是用用到A對象的屬性,會使Block也持有A對象,導致兩者互相持有,不能在作用域結束后正常釋放。
全局塊存在于全局內存中, 相當于單例.
棧塊存在于棧內存中, 超出其作用域則馬上被銷毀
堆塊存在于堆內存中, 是一個帶引用計數的對象, 需要自行管理其內存
簡而言之,存儲在棧中的Block就是棧塊、存儲在堆中的就是堆塊、既不在棧中也不在堆中的塊就是全局塊。
Block訪問外界變量
MRC?環境下:訪問外界變量的 Block 默認存儲棧中。
ARC?環境下:訪問外界變量的 Block 默認存儲在堆中(實際是放在棧區,然后ARC情況下自動又拷貝到堆區),自動釋放。
Block的復制操作執行的是copy實例方法。不同類型的Block使用copy方法的效果如下表:
三、Block與外界變量
1、截獲自動變量(局部變量)值
(1)默認情況
對于 block 外的變量引用,block 默認是將其復制到其數據結構中來實現訪問的。也就是說block的自動變量截獲只針對block內部使用的自動變量, 不使用則不截獲, 因為截獲的自動變量會存儲于block的結構體內部, 會導致block體積變大。特別要注意的是默認情況下block只能訪問不能修改局部變量的值。
int age = 10;
myBlock block = ^{
? ? NSLog(@"age = %d", age);
};
age = 18;
block();
輸出結果
age = 10
?__block 修飾的外部變量
對于用 __block 修飾的外部變量引用,block 是復制其引用地址來實現訪問的。block可以修改__block 修飾的外部變量的值。
__block int age = 10;
myBlock block = ^{
? ? NSLog(@"age = %d", age);
};
age = 18;
block();
輸出為:
age = 18
為什么要用copy去修飾block呢
個人理解:默認情況下,block會存檔在棧中(棧是吃了吐),所以block會在函數調用結束被銷毀,在調用會報空指針異常,如果用copy修飾的話,可以使其保存在堆區(堆是吃了拉) ,它的生命周期會隨著對象的銷毀而結束的。只要對象不銷毀,我們就可以調用在堆中的block。
在了解block為什么要用copy之前,我們要先了解block的三種類型
一NSGlobalBlock:全局的靜態block 沒有訪問外部變量 你的block類型就是這種類型(也就是說你的block沒有調用其他外部變量)
二NSStackBlock:保存在棧中的block,沒有用copy去修飾并且訪問了外部變量,你的block類型就是這種類型,會在函數調用結束被銷毀 (需要在MRC)
三NSMallocBlock 保存在堆中的block 此類型blcok是用copy修飾出來的block 它會隨著對象的銷毀而銷毀,只要對象不銷毀,我們就可以調用的到在堆中的block。
信有很多面試者被問到這樣的問題:block使用什么修飾,往往能夠答出是copy,很多面試官就會問到:為什么要使用copy,這時候就懵了。
我親身體驗了一把,所以先總結一下。
block本身是像對象一樣可以retain,和release。但是,block在創建的時候,它的內存是分配在棧上的,而不是在堆上。他本身的作于域是屬于創建時候的作用域,一旦在創建時候的作用域外面調用block將導致程序崩潰。因為棧區的特點就是創建的對象隨時可能被銷毀,一旦被銷毀后續再次調用空對象就可能會造成程序崩潰,在對block進行copy后,block存放在堆區.
使用retain也可以,但是block的retain行為默認是用copy的行為實現的,
因為block變量默認是聲明為棧變量的,為了能夠在block的聲明域外使用,所以要把block拷貝(copy)到堆,所以說為了block屬性聲明和實際的操作一致,最好聲明為copy。
?block?會自動捕獲block?內部使用的變量,并對其強引用
block是通過添加引用來訪問實例變量的,所以self會被retain一次,block也是一個強引用,會引起循環引用。
3、關于 __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這個函數里面。在doBlock()的block結束之后,mm被自動釋放了。 又由于dispatch_after里面捕獲的__weak的mm,在原對象釋放之后,__weak對象就會變成nil,防止野指針。
那么我們怎么才能在weakSelf之后,block里面還能繼續使用weakSelf之后的對象呢?
究其根本原因就是weakSelf之后,無法控制什么時候會被釋放,為了保證在block內不會被釋放,需要添加__strong。
weakSelf 是為了block不持有self,避免Retain Circle循環引用。
在 Block 內如果需要訪問 self 的方法、變量,建議使用 weakSelf。
strongSelf的目的是因為一旦進入block執行,假設不允許self在這個執行過程中釋放,就需要加入strongSelf。block執行完后這個strongSelf 會自動釋放,不會存在循環引用問題。
如果在 Block 內需要多次 訪問 self,則需要使用 strongSelf。
關于 多層嵌套的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;
}
這樣就避免的引用循環,不管都多少個block嵌套,都可以按照這樣來做。
OS block嵌套block中weakify的使用
結論:嵌套中的block只需要寫strongify,不需要再寫一次weakify
只要持有block的變量和block中的變量不是同一個變量(可以指向同一個變量),就不會因此循環引用,導致memory leak。
所以,當block嵌套block的時候,內部的block不需要再次增加@weakify(self)。