iOSSharing #6 | 2019-04-28

Adrenine發表於2019-04-28

目錄

1. block的迴圈引用是如何造成的?

2. 使用UIAnimation的block回撥時,需不需要使用__weak避免迴圈引用?為什麼?

3. block屬性是否可以用strong修飾?

4. 什麼場景下才需要對變數使用__block?

5. 執行以下GCD多執行緒程式碼,控制檯將列印什麼?

1. block的迴圈引用是如何造成的?

------------Light.h------------
#import <Foundation/Foundation.h>
@interface Light : NSObject
@property (nonatomic, copy) NSString *color;
@property (nonatomic, copy) void (^block)(void);
@end

------------Light.m------------
#import "Light.h"
@implementation Light
@end

------------main.m------------
#import "Light.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        Light *loveLight = [Light alloc] init];
        loveLight.color = @"green";
        
        loveLight.block = ^{
            NSLog(@"%@",loveLight.color);
        };
        
        loveLight.block();
    }
}
複製程式碼

我們在上面的程式碼中建立了一個Light(光)類,並宣告兩個屬性color(顏色)及block。然後我們例項化一個物件loveLight並對其屬性賦值,實現並呼叫block,造成迴圈引用。 然後我們通過clang程式碼,瞭解這段程式碼內部的部分實現:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  RMPerson *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Light *__strong _loveLight, int flags=0) : loveLight(_loveLight) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Light *__strong loveLight = __cself->loveLight; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_0xn052bn6dgb9z7pfk8bbg740000gn_T_main_d61985_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)loveLight, sel_registerName("color")));
        }

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {

_Block_object_assign((void*)&dst->loveLight, (void*)src->loveLight, 3/*BLOCK_FIELD_IS_OBJECT*/);

}

...後省略
複製程式碼

通過clang後的原始碼我們可以知道:

  • 物件的建立本身就是強引用(預設strong修飾)。
  • 物件對block屬性賦值,在ARC下,block作為返回值時或者賦值給一個strong/copy修飾的物件會自動呼叫copy,loveLight強引用block
  • 物件的block在其內部捕獲了物件本身,block在自動呼叫copy的時候,_Block_object_assign(clang原始碼最後一行)會根據捕獲變數的所有權修飾符,來對變數的引用計數進行操作。此處loveLight本身是strong修飾,則引用計數+1,block強引用loveLight物件
  • 所以雙方互相引用,造成了迴圈引用。

同時面試中面試官還可能會詢問你如何檢測到記憶體洩漏,我們可以通過Instruments中的Leaks進行檢測,也可以選擇facebook釋出的FBRetainCycleDetector記憶體洩漏檢測工具。


2. 使用UIView Animation的block回撥時,是否需要考慮迴圈引用的問題?為什麼?

首先UIView Animation使用時,不需要考慮迴圈引用的問題。

UIKit將動畫直接整合到UIView的類中,當內部的一些屬性發生改變時,UIView將為這些改變提供動畫支援,並以類方法的形式提供介面。

而block造成迴圈引用的主要原因是物件與block的相互持有,UIView Animation的block本身處於類方法中,在使用時並不屬於呼叫控制器。同時控制器也無法強引用一個類,所以不會造成迴圈引用的問題。


3. block屬性是否可以用strong修飾?

block屬性可以使用strong屬性修飾符修飾,但是不推薦,會有記憶體洩漏的隱患。

首先,ARC中block用copy屬性修飾符修飾是MRC時代延續的產物,提醒開發者可能存在的記憶體問題。同時copy的確是可以用strong來替代的。

我們都知道block在OC中有三種型別:

  • _NSConcreateGlobalBlock 全域性的靜態block,不會訪問任何外部變數。
  • _NSConcreateStackBlock 棧區的block,當函式返回時會被銷燬。
  • _NSConcreateMallocBlock 堆區的block,當引用計數為0時被銷燬。

block在MRC下可以存在於全域性區、棧區和堆區,而在ARC下,block會自動從棧區拷貝到堆區(除了裸寫block實現塊),所以只存在於全域性區和堆區。 所以對於棧區block,MRC下處於棧區,想在作用域外呼叫就得copy到堆區;ARC則自動copy堆區。

那麼這個時候問題就來了,strong屬性修飾符並不能拷貝,就會有野指標錯區的可能,造成Crash。這種情況很少見,但是不代表不可能發生,所以最好還是使用copy屬性修飾符。


4. 什麼場景下才需要對變數使用__block?

賦值場景下使用__block,使用場景下不需要。 我們來對比下賦值場景和使用場景

//賦值場景
NSMutableArray *__block array = nil;

void(^Block)(void) = ^{
    array = [NSMutableArray array];
};

Block();
    
//使用場景
NSMultableArray *array = [NSMultableArray array];

void(^Block)() = ^{
    [array addObject:@"1"];
};

Block();
複製程式碼

5. 執行以下GCD多執行緒程式碼,控制檯將列印什麼?

dispatch_queue_t gQueue= dispatch_get_global_queue(0, 0);
    
NSLog(@"1");
dispatch_sync(gQueue, ^{
    NSLog(@"2");
    dispatch_sync(gQueue, ^{
        NSLog(@"3");
     });
    NSLog(@"4");
    });
NSLog(@"5");
複製程式碼

答案:12345

首先列印1,全域性佇列本質是併發佇列也就是併發同步,同步任務不開起執行緒,在主執行緒執行列印2。 然後全域性佇列執行同步任務,依舊不開啟執行緒,在主執行緒執行列印3。 同步任務完成,依舊存在於全域性佇列同步執行,列印4. 同步任務完成,列印5。

相關文章