重學OC第二十三篇:block

SofunNiu發表於2020-11-14

一、Block是什麼?

int main(int argc, const char * argv[]) {
	int a = 0;
	void (^aBlock)(void) = ^{
   		NSLog(@"%d", a);
	};
	aBlock();
}

用clang轉為cpp
在這裡插入圖片描述

  • Block是帶有自動變數(區域性變數)的匿名函式,在OC中Block本質上就是一個物件(__main_block_impl_0結構體類比objc_object結構體)。
  • Block截獲自動變數是Block語法表示式所使用的自動變數值被儲存到了Block的結構體例項中。

1.1 __block結構

上面的Block中的變數a是不允許進行修改的,原因是__main_block_func_0中把a進行了值拷貝,此時修改的只是臨時變數a,而不是__cself->a,會引發歧義,所以不允許更改。那__block為什麼可以更改變數呢?把上面的int a = 0;用__block修飾,然後再來看clang後的程式碼。
在這裡插入圖片描述
可以看到加上__block後a變為了一個__Block_byref_a_0型別的變數,對它的操作也變成了a->__forwarding->a。具體可看下面1.2.2小節。

1.2 儲存域

建議直接去看《Obj-C高階程式設計》書中相關內容。

1.2.1 Block儲存域

將Block當作OC物件來看時,可看到它的isa指向_NSConcreteStackBlock,常用到的有3種:

設定物件的儲存域區分
_NSConcreteStackBlock截獲自動變數並且在該變數作用域內
_NSConcreteGlobalBlock程式的資料區域(.data區)記述全域性變數的地方有Block語法時(在定義全域性變數的地方定義Block);Block語法的表示式中不使用應截獲的自動變數
_NSConcreteMallocBlock當_NSConcreteStackBlock超出變數作用域ARC下大多數情況下編譯器進行適當判斷後呼叫_Block_copy拷貝到堆上

在ARC下編譯器大多數情況會適當地進行判斷然後自動從棧複製到堆,那編譯器在什麼情況下不能判斷需要手動複製呢?

  • 向方法或函式的引數中傳遞Block時
    但是如果在方法或函式中適當地複製了傳遞過來的引數,那麼就不必在呼叫該方法或函式前手動複製了。以下方法或函式不用手動複製
    • Cocoa框架的方法且方法名中含有usingBlock等時
    • GCD的API
      在這裡插入圖片描述

下面來具體看段程式碼來理解

typedef void (^blk_t)(void);
NSArray *getBlockArray() {
    int val = 10;
    //ARC不會自動複製,需手動複製
    return [[NSArray alloc] initWithObjects:^{NSLog(@"blk0: %d", val);}, ^{NSLog(@"blk1: %d", val);}, nil];
//    return [[NSArray alloc] initWithObjects:[^{NSLog(@"blk0: %d", val);} copy], [^{NSLog(@"blk1: %d", val);} copy],nil];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        void (^globalBlock)(void) = ^{ };
        //__NSGlobalBlock__
        NSLog(@"GlobalBlock is %@", globalBlock);
        
        __block int a = 10;
        void (^stackBlock)(void) = ^void { a++; };
        //MRC    __NSStackBlock__
        NSLog(@"StackBlock is %@", stackBlock);
        //ARC   __NSMallocBlock__
        NSLog(@"MallocBlock is %@", stackBlock);
        
        NSArray *array = getBlockArray();
        blk_t blk = (blk_t)[array objectAtIndex:0];
        blk(); //如果沒有手動複製,崩潰。因getBlockArray()執行完後,棧上的Block被廢棄。
    }
    return 0;
}

1.2.2 __block變數儲存域

使用__block變數的Block從棧複製到堆上時,__block變數也會受到影響。

__block變數的配置儲存域Block從棧複製到堆時的影響
從棧複製到堆並被Block持有
被Block持有

在一個Block中使用__block變數
在這裡插入圖片描述
在多個Block中使用__block變數
在這裡插入圖片描述
堆上Block被廢棄,它所使用的__block變數也就被釋放。
在這裡插入圖片描述

  • 那麼__block變數用結構體成員變數__forwarding的原因是什麼?
    通過Block的複製,__block變數也從棧複製到堆,此時可同時訪問棧上的__block變數和堆上的__block變數。原始碼如下:
__block int val = 0;
void (^blk)(void) = [^{++val;} copy];
++val;
blk();
NSLog(@"%d", val);

用clang轉以後
在這裡插入圖片描述
可以看出都是++(val.__forwarding->val),但是棧上的__block變數用結構體例項在___block從棧複製到堆上時,會將成員變數__forwording的值替換為複製目標上的block變數用結構體例項的地址。
在這裡插入圖片描述
通過該功能,無論是在Block語法中、Block語法外使用__block變數,還是__block變數配置在棧上或堆上,都可以順利地訪問同一個__block變數。

1.3 捕獲物件

NSNumber *val = @(0);
void (^blk)(void) = ^{NSLog(@"%@", val);};
blk();

在這裡插入圖片描述
在OC中,C語言結構體不能含有附有__strong修飾符的變數,因為編譯器不知道應何時時行C語言結構體的初始化和廢棄操作,不能很好地管理記憶體。但是OC的執行時庫能準確把握從棧複製到堆以及堆上的Block被廢棄的時機,因此Block結構體中即使含有附有__strong修飾符或__weak修飾符的變數,也可以恰當地進行初始化和廢棄。為此需要使用在__main_block_desc_0結構體中增加的成員變數copy和dispose,以及作為指標賦值給該成員變數的__main_block_copy_0函式和__main_block_dispose_0函式。
呼叫copy函式和dispose函式的時機:

函式呼叫時機
copy函式棧上的Block複製到堆時
dispose函式堆上的Block被廢棄時

那麼什麼時候棧上的Block會複製到堆呢?

  • 呼叫Block的copy例項方法時
  • Block作為函式返回值返回時
  • 將Block賦值給附有__strong修飾符id型別的類或Block型別成員變數時
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時

那麼問題來了,在使用__block修飾的int a時也產生了copy、dispose方法,怎麼與物件區分的呢?
在這裡插入圖片描述

基本型別物件__block修飾基本型別__block修飾物件
Block結構體不加入copy、dispose成員變數加入copy、dispose成員變數,flag為BLOCK_FIELD_IS_OBJECT加入copy、dispose成員變數,flag為BLOCK_FIELD_IS_BYREF加入copy、dispose成員變數,flag為BLOCK_FIELD_IS_BYREF
__block變數結構體不加入copy、dispose成員變數加入copy、dispose成員變數

1.4 Block迴圈引用

typedef void (^block_t)(void);
@interface TestA : NSObject

@property(nonatomic, copy) block_t aBlock;
@property(nonatomic, copy) NSString *name;
- (void)test;

@end

@implementation TestA
- (void)test {
    self.name = @"TestA";
    //self->block->self    產生迴圈引用
    self.aBlock = ^{
        NSLog(@"name is %@", self.name);
    };
    //第一種解決方式:weak-strong-dance
    //__weak typeof(self) weakSelf = self;
    self.aBlock = ^{
    	  typeof(self) strongSelf = weakSelf;
    	  dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{		
    	  	//如果不進行強引用,aBlock釋放後weakSelf取不到值。
            //NSLog(@"name is %@", weakSelf.name);
            NSLog(@"name is %@", strongSelf.name);
        });
    };
    //第二種解決方式: 加臨時變數然後手動釋放
    __block TestA *tempA = self;
	self.aBlock = ^{
    	dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
       	 	NSLog(@"name is %@", tempA.name);
        	tempA = nil;
    	});
	};
	self.aBlock();
	//第三種解決方式:傳引數
	//這個需要把block_t定義改為typedef void(^block_t)(id)
	self.aBlock = ^(TestA *tempA){
        dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
            NSLog(@"name is %@", tempA.name);
        });
    };
    //從clang中可知呼叫的(aBlock->Funptr)(aBlock),就是普通的函式呼叫,aBlock作為引數被傳了進去
    self.aBlock(self);
}

- (void)dealloc
{
    NSLog(@"TestA dealloc");
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        TestA *a = [[TestA alloc] init];
        [a test];
    }
    while (1) {}  
    return 0;
}

打破迴圈引用的三種方式:

  • weak-strong-dance
  • 加臨時變數手動置nil
  • 用傳參的方式

1.5 Block的簽名

在block的呼叫前打上斷點,讀取暫存器中的block的指標,然後列印
在這裡插入圖片描述
Block簽名為@?,@代表物件,?代表是函式指標。

二、Block原始碼分析

2.1 Block結構

enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;
    BlockDisposeFunction dispose;
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
//Block結構體
struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

可以看到block資料結構體Block_layout,layout中只包含了Block_descriptor_1,並未包含Block_descriptor_2和Block_descriptor_3,這是因為在沒有引用外部變數或捕獲到不同型別變數時,編譯器會改變結構體的結構,按需新增Block_descriptor_2和Block_descriptor_3,所以才需要
當flags包含BLOCK_HAS_COPY_DISPOSE時,會加入Block_descriptor_2;當flags包含BLOCK_HAS_SIGNATURE時,會加入Block_descriptor_3。Block_descriptor_2和Block_descriptor_3是通過Block_descriptor_1的指標偏移來訪問的。

static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}

static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
        desc += sizeof(struct Block_descriptor_2);
    }
    return (struct Block_descriptor_3 *)desc;
}

早期的一張Block結構圖
在這裡插入圖片描述

2.2 __block變數引用結構體

struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};

enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};

2.3 Block的三次copy

  • Block在ARC下會進行適當地判斷來自動處理Block從棧複製到堆,此時呼叫的是_Block_copy函式;
  • 當捕獲到物件變數或__block基本型別變數時會在Block結構的描述中加入copy、dispose來管理記憶體;
  • 當要捕獲的變數是物件型別且被__block修飾時,就會在__block變數用結構體中加入copy、dispose來管理物件。

這樣算下來一共會進行三次copy,下面先來看_Block_copy函式。

void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;
    if (!arg) return NULL;
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        //Block在堆上,flags值+2,這裡+2應該是BLOCK_DEALLOCATING把1佔用了
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
    	//全域性Block, 直接返回
        return aBlock;
    }
    else {
        // 棧Block,進行copy
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount  重置引用計數, BLOCK_REFCOUNT_MASK = 0xfffe, BLOCK_DEALLOCATING = 0x0001, 它倆或上取反等於0x0;
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        //初始化flags = 0x1000002
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        //看看有沒有Block_descriptor_2,也就是copy、dispose,如果有呼叫copy方法
        _Block_call_copy_helper(result, aBlock);
        // 最後修改isa指向從_NSConcreteStackBlock變為_NSConcreteMallocBlock,以便記憶體分析工具可以看到完全初始化的物件
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
	//通過flags中是否有BLOCK_HAS_COPY_DISPOSE值來判斷是否需要copy、dispose
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    if (!desc) return;
    (*desc->copy)(result, aBlock); // do fixup
}

static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}

如果是堆Block就進行引用計數增加;如果是全域性Block直接返回;如果是棧Block進行copy,此時如果有_Block_descriptor_2結構,直接呼叫copy方法,從clang後的程式碼中可看到其實是呼叫的是_Block_object_assign()。

//對捕獲的變數進行操作
void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    //判斷flags中是否有BLOCK_FIELD_IS_OBJECT、BLOCK_FIELD_IS_BLOCK、BLOCK_FIELD_IS_BYREF、BLOCK_FIELD_IS_WEAK、BLOCK_BYREF_CALLER這幾個值
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/
        _Block_retain_object(object);
        *dest = object;   
        break;
      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/
        *dest = _Block_copy(object);
        break;
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/
        *dest = _Block_byref_copy(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/
        *dest = object;
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/
        *dest = object;
        break;
      default:
        break;
    }
}

上面的官方註釋已經作出了說明,接著主要來看下BLOCK_FIELD_IS_BYREF情況,也就是被__block修飾變數時,會呼叫_Block_byref_copy。

static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;
    
    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src指向棧
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        //這裡或上4是因為棧的forwarding通過下面的程式碼指向了堆
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        copy->forwarding = copy; // 堆上的forwarding指向堆自身
        src->forwarding = copy;  // 棧上的forwarding也指向堆
        copy->size = src->size;

        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
        	//有copy、dispose,通過src偏移一個struct Block_byref結構體大小拿到src2, 也就是包含copy和dispose成員變數的Block_byref_2結構體
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
            	//從src2偏移一個struct Block_byref_2大小拿到src3, 也就是包含layout成員變數的Block_byref_3結構體
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }
			//呼叫外部的__Block_byref_id_object_copy_131
            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}

從 copy->forwarding = copy; src->forwarding = copy; 這兩句程式碼可以看出,堆上的變數的forwarding指向了自己,而棧上的forwarding也指向了堆,這樣就實現了對同一變數的操作,也就是__block為什麼可以修改持有的外部變數的原因。
接下來,還可以看到(*src2->byref_keep)(copy, src);這句程式碼呼叫了byref_keep,而byref_keep為

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

又一次呼叫了_Block_object_assign, 而此時的偏移+40是因為val在__Block_byref_val_0中的地址偏移量為40,
在這裡插入圖片描述
131代表的是128+3, 128是BLOCK_BYREF_CALLER代表__block變數有copy/dispose的記憶體管理輔助函式,3是BLOCK_FIELD_IS_OBJECT代表變數是物件型別。

2.4 Block釋放

沒有copy、dispose結構的作用域結束就釋放掉了,而有copy、dispose結構的會通過呼叫_Block_object_dispose函式。

void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // 釋放__block相應的
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
  	  ......
    }
}

static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return false; // latched high
        }
        if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
            return false;   // underflow, latch low
        }
        int32_t new_value = old_value - 2;
        bool result = false;
        if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) {		//只有在flags值等於2時說明只剩下一個物件在持有,此時呼叫dispose方法可以釋放了
            new_value = old_value - 1;   
            result = true;
        }
        if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) {
            return result;
        }
    }
}

_Block_byref_release和_Block_release都通過latching_decr_int_should_deallocate方法中的(old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2來判斷是否釋放物件,只有在flags值等於2時說明只剩下一個物件在持有,此時才去呼叫相應的dispose去釋放相應的物件。

總結

  • Block是帶有自動變數(區域性變數)的匿名函式,在OC中Block本質上就是一個物件。

  • Block截獲自動變數是Block語法表示式所使用的自動變數值被儲存到了Block的結構體例項中。

  • Block的儲存域:
    在這裡插入圖片描述

  • __block變數儲存域會從隨著Block儲存域一起復制,棧Block複製到堆的時機:

    • 呼叫Block的copy例項方法時
    • Block作為函式返回值返回時
    • 將Block賦值給附有__strong修飾符id型別的類或Block型別成員變數時
    • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時
  • 打破迴圈引用的三種方式:

    • weak-strong-dance
    • 加臨時變數手動置nil
    • 用傳參的方式
  • Block的簽名型別為@?,@代表物件,?代表函式指標

  • Block的三次Copy

    • Block在ARC下會進行適當地判斷來自動處理Block從棧複製到堆,此時呼叫的是_Block_copy函式;
    • 當捕獲到物件變數或__block基本型別變數時會在Block結構的描述中加入copy、dispose來管理記憶體;
    • 當要捕獲的變數是物件型別且被__block修飾時,就會在__block變數用結構體中加入copy、dispose來管理物件。
  • 只有在flags值等於2時說明只剩下一個物件在持有,此時才去呼叫相應的dispose去釋放相應的物件。

參考文章:

原始碼地址:https://opensource.apple.com/source/libclosure/

文中部分內容摘自《Obj-C高階程式設計》書中

相關文章