iOS-Block 中 為何使用 weakSelf 和 strongSelf

目錄

本篇幅代碼會有些多,需要讀者耐心看完。

一 block 循環引用講解

詳細請移步至 iOS-底層原理(11)-block-循環引用詳解,該文章詳細講解了有關 block 引用的原理及解決思路。

原理就是相互引用

image.png
二 為何使用 weakSelf 可以解決循環引用
  • __weak:不會產生強引用,指向的對象銷毀時,會自動讓指針置為nil
  • __unsafe_unretained:不會產生強引用,不安全,指向的對象銷毀時,指針存儲的地址值不變
image.png

代碼佐證 - 在 MRC 環境下

  • 1 直接在 block里面使用 self
- (void)test1 {
    // 引用計數
    NSLog(@"self1 內存地址:%p 引用計數:%lu",self,(unsigned long)[self retainCount]);
    
    // 循環引用
    self.myBlock = ^{
        [self test];
    };
    self.myBlock();
}

- (void)test {
    NSLog(@"test 方法 self 內存地址:%p 引用計數:%lu",self,(unsigned long)[self retainCount]);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesBegan self = %lu",(unsigned long)[self retainCount]);
}
  • 運行結果
2019-06-09 14:44:32.975002+0800 WeakStrongSelfDemo[6897:172037] self1 內存地址:0x7fc69ef14cc0 引用計數:9
2019-06-09 14:44:32.975150+0800 WeakStrongSelfDemo[6897:172037] test方法 self 內存地址:0x7fc69ef14cc0 引用計數:10
2019-06-09 14:44:34.638030+0800 WeakStrongSelfDemo[6897:172037] touchesBegan self 內存地址:0x7fc69ef14cc0 引用計數:5

總結:直接在 block 中使用 self,發現其引用計數+1,執行完touchesBegan方法后,引用計數變成5.

  • 2 在 block 中使用 weakSelf
typedef void(^MyBlock)(void);

@interface TestViewController ()
/** myBlock */
@property(nonatomic, copy)MyBlock myBlock;
@end

- (void)test2 {
    // 引用計數
    NSLog(@"self1 內存地址:%p 引用計數:%lu",self,(unsigned long)[self retainCount]);
    
    __weak typeof(self) weakSelf = self;
    NSLog(@"weakSelf 內存地址:%p 引用計數:%lu",weakSelf,(unsigned long)[weakSelf retainCount]);
    
    NSLog(@"self2 內存地址:%p 引用計數:%lu",self,(unsigned long)[self retainCount]);
    
    // 循環引用
    self.myBlock = ^{
        [weakSelf test];
    };
    self.myBlock();
}
  • 運行結果
2019-06-09 14:49:46.690693+0800 WeakStrongSelfDemo[6952:174797] self1 內存地址:0x7ffd7dc1e520 引用計數:9
2019-06-09 14:49:46.690852+0800 WeakStrongSelfDemo[6952:174797] weakSelf 內存地址:0x7ffd7dc1e520 引用計數:11
2019-06-09 14:49:46.690931+0800 WeakStrongSelfDemo[6952:174797] self2 內存地址:0x7ffd7dc1e520 引用計數:11
2019-06-09 14:49:46.691016+0800 WeakStrongSelfDemo[6952:174797] test方法 self 內存地址:0x7ffd7dc1e520 引用計數:12
2019-06-09 14:49:49.165380+0800 WeakStrongSelfDemo[6952:174797] touchesBegan self 內存地址:0x7ffd7dc1e520 引用計數:4

總結:selfweakSelf的內存地址相同,在沒有執行 block之前,他兩的引用計數值相同。執行完 block 后,執行touchesBegan屏幕點擊,發現引用計數變為4。注意前面直接使用self的時候,引用計數是5。即得出結論

  1. 直接在block中使用self,會造成循環引用, block 執行完后,self的引用計數不正常(應該為4的,但實際上是 5,多 1是因為被block引用著)
  2. 如果使用weakSelf不會造成循環引用,self的引用計數正常(至于為啥是 4,應該是系統其它變量引用著 self)。
三 為何使用 strongSelf

在 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];
});
  • 代碼例子佐證
- (void)test3 {
    // 引用計數
    NSLog(@"self1 內存地址:%p 引用計數:%lu",self,(unsigned long)[self retainCount]);
    
    __weak typeof(self) weakSelf = self;
    NSLog(@"weakSelf 內存地址:%p 引用計數:%lu",weakSelf,(unsigned long)[weakSelf retainCount]);
    
    NSLog(@"self2 內存地址:%p 引用計數:%lu",self,(unsigned long)[self retainCount]);
    
    NSLog(@"------------------1---------------------");
    
    // 循環引用
    self.myBlock = ^{
        NSLog(@"block self = %lu",(unsigned long)[self retainCount]);
        
        NSLog(@"block weakSelf = %lu",(unsigned long)[weakSelf retainCount]);
        
        NSLog(@"---------------------2------------------");
        
        __strong typeof (weakSelf) strongSelf = weakSelf;
        
        NSLog(@"block self = %lu",(unsigned long)[self retainCount]);
        NSLog(@"block weakSelf = %lu",(unsigned long)[weakSelf retainCount]);
        NSLog(@"block strongSelf = %lu",(unsigned long)[strongSelf retainCount]);;
        
        NSLog(@"-----------------3----------------------");
        
        for (int i = 0; i < 5; i++) {
            NSLog(@"block self = %lu",(unsigned long)[self retainCount]);
        }
        
        for (int i = 0; i < 5; i++) {
            NSLog(@"block weakSelf = %lu",(unsigned long)[weakSelf retainCount]);
        }
        
        for (int i = 0; i < 5; i++) {
            NSLog(@"block strongSelf = %lu",(unsigned long)[strongSelf retainCount]);
        }
    };
    self.myBlock();
}
  • 運行結果
2019-06-09 15:02:38.065125+0800 WeakStrongSelfDemo[7175:182681] self1 內存地址:0x7ff4ddc41390 引用計數:9
2019-06-09 15:02:38.065280+0800 WeakStrongSelfDemo[7175:182681] weakSelf 內存地址:0x7ff4ddc41390 引用計數:11
2019-06-09 15:02:38.065368+0800 WeakStrongSelfDemo[7175:182681] self2 內存地址:0x7ff4ddc41390 引用計數:11
2019-06-09 15:02:38.065441+0800 WeakStrongSelfDemo[7175:182681] ------------------1---------------------
2019-06-09 15:02:38.065530+0800 WeakStrongSelfDemo[7175:182681] block self = 12
2019-06-09 15:02:38.065604+0800 WeakStrongSelfDemo[7175:182681] block weakSelf = 13
2019-06-09 15:02:38.065681+0800 WeakStrongSelfDemo[7175:182681] ---------------------2------------------
2019-06-09 15:02:38.065848+0800 WeakStrongSelfDemo[7175:182681] block self = 13
2019-06-09 15:02:38.066290+0800 WeakStrongSelfDemo[7175:182681] block weakSelf = 14
2019-06-09 15:02:38.066566+0800 WeakStrongSelfDemo[7175:182681] block strongSelf = 15
2019-06-09 15:02:38.066844+0800 WeakStrongSelfDemo[7175:182681] -----------------3----------------------
2019-06-09 15:02:38.067101+0800 WeakStrongSelfDemo[7175:182681] block self = 15
2019-06-09 15:02:38.067378+0800 WeakStrongSelfDemo[7175:182681] block self = 15
2019-06-09 15:02:38.067701+0800 WeakStrongSelfDemo[7175:182681] block self = 15
2019-06-09 15:02:38.067955+0800 WeakStrongSelfDemo[7175:182681] block self = 15
2019-06-09 15:02:38.068178+0800 WeakStrongSelfDemo[7175:182681] block self = 15
2019-06-09 15:02:38.068521+0800 WeakStrongSelfDemo[7175:182681] block weakSelf = 16
2019-06-09 15:02:38.068815+0800 WeakStrongSelfDemo[7175:182681] block weakSelf = 17
2019-06-09 15:02:38.069058+0800 WeakStrongSelfDemo[7175:182681] block weakSelf = 18
2019-06-09 15:02:38.069359+0800 WeakStrongSelfDemo[7175:182681] block weakSelf = 19
2019-06-09 15:02:38.069751+0800 WeakStrongSelfDemo[7175:182681] block weakSelf = 20
2019-06-09 15:02:38.070052+0800 WeakStrongSelfDemo[7175:182681] block strongSelf = 21
2019-06-09 15:02:38.084354+0800 WeakStrongSelfDemo[7175:182681] block strongSelf = 22
2019-06-09 15:02:38.084437+0800 WeakStrongSelfDemo[7175:182681] block strongSelf = 23
2019-06-09 15:02:38.084485+0800 WeakStrongSelfDemo[7175:182681] block strongSelf = 24
2019-06-09 15:02:38.084528+0800 WeakStrongSelfDemo[7175:182681] block strongSelf = 25
2019-06-09 15:06:01.899710+0800 WeakStrongSelfDemo[7175:182681] touchesBegan self 內存地址:0x7ff4ddc41390 引用計數:5
  • 接下來對運行結果分段進行分析
// 引用計數
for (int i = 0; i < 3; i++) {
    NSLog(@"self1 內存地址:%p 引用計數:%lu",self,(unsigned long)[self retainCount]);
}

__weak typeof(self) weakSelf = self;
for (int i = 0; i < 3; i++) {
    NSLog(@"weakSelf 內存地址:%p",weakSelf);
}
NSLog(@"引用計數:%lu",(unsigned long)[weakSelf retainCount]);
NSLog(@"----------------0--------------");

for (int i = 0; i < 3; i++) {
    NSLog(@"引用計數:%lu",(unsigned long)[weakSelf retainCount]);
}

NSLog(@"self2 內存地址:%p 引用計數:%lu",self,(unsigned long)[self retainCount]);
NSLog(@"weakSelf 內存地址:%p 引用計數:%lu",weakSelf,(unsigned long)[weakSelf retainCount]);
  • 運行結果
2019-06-09 15:13:07.694736+0800 WeakStrongSelfDemo[7373:189509] self1 內存地址:0x7f93c9f065f0 引用計數:9
2019-06-09 15:13:07.694883+0800 WeakStrongSelfDemo[7373:189509] self1 內存地址:0x7f93c9f065f0 引用計數:9
2019-06-09 15:13:07.694978+0800 WeakStrongSelfDemo[7373:189509] self1 內存地址:0x7f93c9f065f0 引用計數:9
2019-06-09 15:13:07.695075+0800 WeakStrongSelfDemo[7373:189509] weakSelf 內存地址:0x7f93c9f065f0
2019-06-09 15:13:07.695166+0800 WeakStrongSelfDemo[7373:189509] weakSelf 內存地址:0x7f93c9f065f0
2019-06-09 15:13:07.695252+0800 WeakStrongSelfDemo[7373:189509] weakSelf 內存地址:0x7f93c9f065f0
2019-06-09 15:13:07.695336+0800 WeakStrongSelfDemo[7373:189509] 引用計數:13
2019-06-09 15:13:07.695410+0800 WeakStrongSelfDemo[7373:189509] ----------------0--------------
2019-06-09 15:13:07.695494+0800 WeakStrongSelfDemo[7373:189509] 引用計數:14
2019-06-09 15:13:07.695577+0800 WeakStrongSelfDemo[7373:189509] 引用計數:15
2019-06-09 15:13:07.695651+0800 WeakStrongSelfDemo[7373:189509] 引用計數:16
2019-06-09 15:13:07.695821+0800 WeakStrongSelfDemo[7373:189509] self2 內存地址:0x7f93c9f065f0 引用計數:16
2019-06-09 15:13:07.695990+0800 WeakStrongSelfDemo[7373:189509] weakSelf 內存地址:0x7f93c9f065f0 引用計數:18

通過運行結果可知,每次訪問變量weakSelf(比如打印其內存地址,讀取其引用計數[weakSelf retainCount]),都會使其引用計數+1。但是self不會。

  • 運行結果段 2 分析
weakSelf 內存地址:0x7f8e05e22ae0 引用計數:11
self2 內存地址:0x7f8e05e22ae0 引用計數:11  // 因為 self 和 weakSelf 指向同一塊內存,所以他們的引用計數值相同
------------------1---------------------
block self = 12  // 因為 block 也強引用了 self,所以 self 的引用計數+1
block weakSelf = 13  // 訪問了 [weakSelf retainCount],引用計數+1
---------------------2------------------
block self = 13  // 即weakSelf的引用計數 13
block weakSelf = 14  // 訪問了 [weakSelf retainCount],引用計數+1
block strongSelf = 15  // 原理同weakSelf一樣
  • 運行結果段 3 分析
2019-06-09 15:20:03.540679+0800 WeakStrongSelfDemo[7446:193548] block self = 15
2019-06-09 15:20:03.541845+0800 WeakStrongSelfDemo[7446:193548] block self = 15
2019-06-09 15:20:03.542679+0800 WeakStrongSelfDemo[7446:193548] block self = 15
2019-06-09 15:20:03.543429+0800 WeakStrongSelfDemo[7446:193548] block self = 15
2019-06-09 15:20:03.544483+0800 WeakStrongSelfDemo[7446:193548] block self = 15
2019-06-09 15:20:03.544844+0800 WeakStrongSelfDemo[7446:193548] block weakSelf = 16
2019-06-09 15:20:03.545132+0800 WeakStrongSelfDemo[7446:193548] block weakSelf = 17
2019-06-09 15:20:03.545381+0800 WeakStrongSelfDemo[7446:193548] block weakSelf = 18
2019-06-09 15:20:03.545757+0800 WeakStrongSelfDemo[7446:193548] block weakSelf = 19
2019-06-09 15:20:03.546010+0800 WeakStrongSelfDemo[7446:193548] block weakSelf = 20
2019-06-09 15:20:03.546266+0800 WeakStrongSelfDemo[7446:193548] block strongSelf = 21
2019-06-09 15:20:03.546596+0800 WeakStrongSelfDemo[7446:193548] block strongSelf = 22
2019-06-09 15:20:03.567575+0800 WeakStrongSelfDemo[7446:193548] block strongSelf = 23
2019-06-09 15:20:03.567672+0800 WeakStrongSelfDemo[7446:193548] block strongSelf = 24
2019-06-09 15:20:03.567725+0800 WeakStrongSelfDemo[7446:193548] block strongSelf = 25

原因在結果段 2 已經分析過了

前面都是從代碼例子去佐證,接下來我們從底層原理角度去分析它

四 從底層原理角度分析
4.1 weakSelf
image.png
image.png
- (void)test5 {
    Student *stu = [[Student alloc] init];
    __weak Student *weakStu = stu;
    
    self.myBlock = ^{
        NSLog(@"%p",stu);
        NSLog(@"%p",weakStu);
    };
    self.myBlock();
}

// 在 block 內部存儲數據格式
struct __main_block_impl_0 {
    NSObject *__weak weakStu;
}

block底層結構分析可知,一個被__weak修飾的局部變量,最終會被block捕獲為自己的屬性NSObject *__weak weakStu;

所以對weakSelf直白的理解就是結構體__main_block_impl_0weakSelf這個局部變量捕獲作為了自己的屬性。結構體直接引用了weakSelf內存空間,想使用weakself中保存的self指針,而這個weakSelf并沒有增加self的引用計數,所以self依然可以釋放。

4.2 strongSelf
__strong typeof (weakSelf) strongSelf = weakSelf;

最終轉換為底層代碼是

TestViewController *strongSelf = weakSelf;

并且執行block里面的代碼,本質是執行一個函數__TestViewController__test_block_func_0。所以TestViewController *strongSelf = weakSelf;賦值得到的是self(由 weakSelf 傳過來),這使得在函數體運行期間,self不會被釋放。

既然是函數,那么大括號走完其函數體重的局部變量就會被釋放,也就是說函數執行完以后,strongSelf作為局部變量正常釋放了,strongSelf也不屬于block結構體的屬性,weakSelfstrongSelf一內一外,意義是不一樣的。

借鑒網上的圖


image
image

__strong 確保在 Block 內,strongSelf 不會被釋放。

五 總結
__weak __typeof(self)weakSelf = self;    //1

[self.context performBlock:^{      
    [weakSelf doSomething];          //2
     __strong __typeof(weakSelf)strongSelf = weakSelf;  //3
    [strongSelf doAnotherSomething];        
}];
1.使用__weak __typeof是在編譯的時候,另外創建一個局部變量weak對象來操作self,引用計數不變。block 會將這個局部變量捕獲為自己的屬性,
訪問這個屬性,從而達到訪問 self 的效果,因為他們的內存地址都是一樣的。

2.因為weakSelf和self是兩個變量,doSomething有可能就直接對self自身引用計數減到0了.
  所以在[weakSelf doSomething]的時候,你很難控制這里self是否就會被釋放了.weakSelf只能看著.

3.__strong __typeof在編譯的時候,實際是對weakSelf的強引用.
  指針連帶關系self的引用計數會增加.但是你這個是在block里面,生命周期也只在當前block的作用域.
  所以,當這個block結束, strongSelf隨之也就被釋放了.不會影響block外部的self的生命周期.

總結
在 Block 內如果需要訪問 self 的方法、變量,建議使用 weakSelf。
如果在 Block 內需要多次 訪問 self,則需要使用 strongSelf。


相關文章參考
透徹理解block中weakSelf和strongSelf
到底什么時候才需要在ObjC的Block中使用weakSelf/strongSelf


項目鏈接地址 - WeakStrongSelfDemo

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容