1. 程式人生 > >iOS之__block、__weak、Block迴圈引用、__weak typeof(self) weakSelf = self;

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;     // 適用

ARC模式


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