iOS之__block、__weak、Block迴圈引用、__weak typeof(self) weakSelf = self;
在介紹block迴圈引用前我們先了解一下typeof。我們經常可以看到這樣的程式碼:__weak typeof(self) weakSelf = self;、
block對於其變數都會形成強引用(retain),對於self也會形成強引用(retain) ,而如果self本身對block也是強引用的話,就會形成 強引用 迴圈,無法釋放——造成記憶體洩露。,所以我們會使用
__block typeof(self) bself = self; // 適用MRC模式,當修飾變數時,表示這個變數值能在block中被修改
__weaktypeof(self) weakself = self; // 適用
typeof是什麼???
通俗的說就是:可以根據typeof()括號裡面的變數,自動識別變數型別並返回該型別。
typeof 是一個一元運算,放在一個運算數之前,運算數可以是任意型別。 它返回值是一個字串,該字串說明運算數的型別。
一、對於數字型別的運算元而言,typeof返回的值是number。比如說:typeof(1),返回的值就是number。 上面是舉的常規數字,對於非常規的數字型別而言,其結果返回的也是number。比如typeof(NaN),NaN在 JavaScript中代表的是特殊非數字值,雖然它本身是一個數字型別。
二、對於字串型別,typeof返回的值是string。比如typeof("123")返回的值是string。
三、對於布林型別,typeof返回的值是boolean.比如typeof(true)返回的值是boolean。
四、對於物件、陣列、null 返回的值是 object 。比如typeof(window),typeof(document),typeof(null)返回的值都是object。
五、對於函式型別,返回的值是 function。比如:typeof(eval),typeof(Date)返回的值都是function。
六、如果運算數是沒有定義的(比如說不存在的變數、函式或者undefined),將返回undefined。比如:typeof(sss)、typeof(undefined)都返回undefined。
為什麼要用弱引用???
block在ARC中使用strong、copy這兩種的效果是一樣的。既然用到strong,copy當然就要考慮到強引用問題:
self有一個屬性Block,然而這個Block在主函式體又引用了self的其他成員變數,那麼就會對這個變數本身產生強引用,那麼變數本身和它自己的Block屬性就形成了迴圈引用。因此我們需要對其進行處理進行弱引用。
怎麼實現弱引用???
下面是簡單的程式碼:
__weak typeof(self) weakSelf = self; self.Block = ^ { if (weakSelf.people) { weakSelf.people.name = @"hello"; } };
什麼是Block迴圈引用???
兩個物件相互持有,這樣就會造成迴圈引用,如下圖所示
物件A持有物件B,物件B持有物件A,相互持有,最終導致兩個物件都不能釋放。
1、block在主函式體用到了self / self.變數 / [self 方法],意味著:block對self 進行持有操作,
2、self聲明瞭屬性變數block,block用copy來修飾,意味著:self對block進行持有操作,會造成迴圈引用。如下,在類.m中
typedef void(^block)();
@property (copy, nonatomic) block myBlock; // 2
@property (copy, nonatomic) NSString *blockString;
- (void)testBlock {
self.myBlock = ^() {
//其實註釋中的程式碼,同樣會造成迴圈引用
NSString *localString = self.blockString; // 1
//NSString *localString = _blockString;
//[self doSomething];
};
}
注:以下呼叫註釋掉的程式碼同樣會造成迴圈引用,因為不管是通過self.blockString還是_blockString,或是函式呼叫[self doSomething],因為只要 block中用到了物件的屬性或者函式,block就會持有該物件而不是該物件中的某個屬性或者函式。
解決方法:
__weak typeof(self) weakSelf = self;
self.myBlock = ^() {
NSString *localString = weakSelf.blockString;
};
使用__weak打破迴圈的方法只在ARC下才有效,在MRC下應該使用__block
或者,
在block執行完後,將block置nil,這樣也可以打破迴圈引用
這樣做的缺點是,block只會執行一次,因為block被置nil了,要再次使用的話,需要重新賦值。
關於——Block在MRC和ARC模式的區別
1)__block在MRC下有兩個作用
允許在Block中訪問和修改區域性變數
禁止Block對所引用的物件進行隱式retain操作
2)__block在ARC下只有一個作用
允許在Block中訪問和修改區域性變數
在MRC中解決迴圈引用的辦法即在變數前使用下劃線下劃線block修飾,禁止Block對所引用的物件進行retain操作
__block MyViewController *myController = [[MyViewController alloc] init];
// ...
myController.completionHandler = ^(NSInteger result) {
[myController dismissViewControllerAnimated:YES completion:nil];
};
[self presentViewController:myController animated:YES completion:^{
[myController release];
}];
什麼時候在 block 中不需要使用 weakSelf???
一些不會造成迴圈引用的block
在開發工程中,發現一些同學並沒有完全理解迴圈引用,以為只要有block的地方就會要用__weak來修飾物件,這樣完全沒有必要,以下幾種block是不會造成迴圈引用的。
1)大部分GCD方法
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething];
});
因為self並沒有對GCD的block進行持有,沒有形成迴圈引用。目前我還沒碰到使用GCD導致迴圈引用的場景,如果某種場景self對GCD的block進行了持有,則才有可能造成迴圈引用。
2)大部分動畫效果。
當 block 本身不被 self 持有,而被別的物件持有,同時不產生迴圈引用的時候,就不需要使用 weak self 了。最常見的程式碼就是 UIView 的動畫程式碼:
[UIView animateWithDuration:0.2 animations:^{
self.alpha = 1;
}];
當動畫結束時,UIView 會結束持有這個 block,block 物件就會釋放掉,從而 block 會釋放掉對於 self 的持有。整個記憶體引用關係被解除。3)block並不是物件的屬性 / 變數,而是方法的引數 / 臨時變數
- (void) doSomething {
[self testWithBlock:^{
[self test];
}];
}
- (void) testWithBlock:(void(^)())block {
block();
}
- (void) test {
NSLog(@"test");
}
這裡因為block只是一個臨時變數,self並沒有對其持有,所以沒有造成迴圈引用
以下情況也不需要:雖然Block主體函式用到了MyObject,但MyObject物件沒有block屬性(block屬於Person物件),沒有構成互相持有
@interface Person : NSObject
@property (nonatomic, copy) void(^myBlock)();
@end
@implementation Person
- (void)viewDidLoad
{
MyObject *p = [[MyObject alloc] init];
void(^myBlock)() = ^{
NSLog(@"------%@", p);
};
myBlock();
// MyObject物件在這裡可以正常被釋放
}
@end
Block錯誤使用:常見錯誤使用是,開發者擔心迴圈引用錯誤(如上所述不會出現迴圈引用的情況),使用__weak。比如
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf doSomething];
});
因為將block作為引數傳給dispatch_async時,系統會將block拷貝到堆上,而且block會持有block中用到的物件,因為dispatch_async並不知道block中物件會在什麼時候被釋放,為了確保系統排程執行block中的任務時其物件沒有被意外釋放掉,dispatch_async必須自己retain一次物件(即self),任務完成後再release物件(即self)。但這裡使用__weak,使dispatch_async沒有增加self的引用計數,這使得在系統在排程執行block之前,self可能已被銷燬,但系統並不知道這個情況,導致block執行時訪問已經被釋放的self,而達不到預期的結果。
什麼時候需要strongSelf???
在 doSomething 內,weakSelf 不會被釋放。
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[weakSelf doSomething];
});
但,下面的情況除外:
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[weakSelf doSomething];
[weakSelf doOtherThing];
});
在 doSomething 中,weakSelf 不會變成 nil,不過在 doSomething 執行完成,呼叫第二個方法 doOtherThing 的時候,weakSelf 有可能被釋放,於是,strongSelf 就派上用場了:
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong __typeof(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doOtherThing];
});
__strong 確保在 Block 內,strongSelf 不會被釋放。
總結
1 在 Block 內如果需要訪問 self 的方法、變數,建議使用 weakSelf。
2 如果在 Block 內需要多次 訪問 self,則需要使用 strongSelf。
參考:http://www.jianshu.com/p/14efa33b3562?utm_campaign=maleskine&utm_content=note&utm_medium=reader_share&utm_source=weibo