iOSSharing #6 | 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;
  Light *loveLight;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Light *_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 *loveLight = __cself->loveLight; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_yf_3v_c713x5898zz9d49t0rd140000gn_T_main_81cd17_mi_1,((NSString *(*)(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屬性修飾符修飾,可是不推薦,會有內存泄漏的隱患。ui

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

咱們都知道block在OC中有三種類型:
- _NSConcreateGlobalBlock 全局的靜態block,不會訪問任何外部變量。 - _NSConcreateStackBlock 棧區的block,當函數返回時會被銷燬。 - _NSConcreateMallocBlock 堆區的block,當引用計數爲0時被銷燬。spa

block在MRC下能夠存在於全局區、棧區和堆區,而在ARC下,block會自動從棧區拷貝到堆區(除了裸寫block實現塊),因此只存在於全局區和堆區。 因此對於棧區block,MRC下處於棧區,想在做用域外調用就得copy到堆區;ARC則自動copy堆區。線程

那麼這個時候問題就來了,strong屬性修飾符並不能拷貝,就會有野指針錯區的可能,形成Crash。這種狀況不多見,可是不表明不可能發生,因此最好仍是使用copy屬性修飾符。

此處感謝@buaacyg的評論反饋,同時也對撰文的不嚴謹深表歉意。

針對Block屬性修飾符的問題在撰寫的時候的確沒有考慮周全,咱們將在如下予以更正和解答。 首先,在如下情形中block會自動從棧拷貝到堆:

  • 一、當 block 調用 copy 方法時,若是 block 在棧上,會被拷貝到堆上;
  • 二、當 block 做爲函數返回值時,編譯器自動將 block 做爲 _Block_copy 函數,效果等同於直接調用 copy 方法;
  • 三、當 block 被賦值給 __strong id 類型的對象或 block 的成員變量時,編譯器自動將 block 做爲 _Block_copy 函數,效果等同於直接調用 copy 方法;
  • 四、當 block 做爲參數被傳入方法名帶有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 時。這些方法會在內部對傳遞進來的 block 調用 copy 或 _Block_copy 進行拷貝;

那針對上述自動拷貝的狀況咱們作一個實驗:

ARC下strong修飾block,且不引用外部變量,block類型爲__NSGlobalBlock

ARC下strong修飾block,引入外部變量,block類型爲__NSMallocBlock

因此由此就能夠理解爲ARC下strong修飾的block並無處於棧區的可能,也就不存在做用域結束棧區內容銷燬野指針的問題了。 可是爲了保證修飾符和block特性的一致性,使用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。

相關文章
相關標籤/搜索