Objc Block實現分析

Objc Block實現分析

Block在iOS開發中使用的頻率是很高的,使用的場景包括接口異步數據的回調(AFN)、UI事件的回調(BlockKits)、鏈式調用(Masonry)、容器數據的遍歷回調(NSArray、NSDictionary),具體的用法以及使用Block的一些坑這里就不一一贅述了,本文會從源代碼的層面分析我們常用的Block的底層實現原理,做到知其然知其所以然。

本文會從如下幾個主題切入,層層遞進分析Block的底層原理,還原Block本來的面目

  • 無參數無返回值,不使用變量的Block分析
  • 使用自動變量的Block分析
  • 使用__block修飾的變量的Block分析
  • Block引用分析

無參數無返回值,不使用變量的Block分析

有如下的源代碼,創建一個簡單的block,在block做的處理是打印一個字符串,然后執行這個block。接下來會從源碼入手對此進行分析:block是如何執行的

// 無參數無返回值的Block分析
int main(int argc, const char * argv[]) {
    void (^blk) (void) = ^{
        printf("block invoke");
    };
    blk();
    return 0;
}

// 輸出:
block invoke

使用clang -rewrite-objc main.m命令重寫為C++語法的源文件。從重寫后的代碼中看到,一個簡單block定義和調用的代碼變成了大幾十行,不過該源代碼還算是相對簡單的,我們從main函數入口逐步的進行分析:

main函數中創建__main_block_impl_0類型的實例

__main_block_impl_0結構體是什么鬼呢?我們看__main_block_impl_0結構體的實現,包含了__block_impl結構體和__main_block_desc_0結構體指針,為了方便,現在頁先不用管__main_block_desc_0結構體,目前他還沒有真正的使用到,后面講到的__block修飾自動變量以及Block對對象的引用關系,設計到內存的拷貝和釋放的時候會使用到該變量。__block_impl結構體包含了4個成員,為了簡單分析,我們只關注其中的FuncPtr成員的FuncPtr成員,這個也是最重要的成員,其它的先忽略不計。

__main_block_impl_0實例的初始化參數

第一個是__main_block_func_0函數的地址,__main_block_func_0函數是什么呢,其實就是Block執行的方法,可以看到里面的實現就是一個簡單的打印而已,和我們定義在block中的實現一樣的,該參數用于初始化impl成員的。至于第二個參數先不管,在這個例子木有用到。

__main_block_impl_0實例的調用

創建了__main_block_impl_0結構體類型的實例之后,接下來就是獲取到里面的FuncPtr指針指向的方法進行調用,參數是__main_block_impl_0結構體類型的實例本身,上一步創建步驟可知,FuncPtr指針是一個函數指針指向__main_block_func_0函數的地址,所以這里本質上就是__main_block_func_0函數的調用,結構是打印字符串"block invoke",一個簡單的block執行孫然在代碼量上增加了,其實也不算復雜。

注意點:(__block_impl *)blk這里做了一個強制轉換,blk是__main_block_impl_0類型的實例的指針,根據結構體的內存布局規則,該結構體的第一個成員為__block_impl 類型,所以可以強制轉換為__block_impl *類型。

由上面的分析,可以得出如下的結論:使用變量的Block調用本質上是使用函數指針調用函數


// 使用`clang -rewrite-objc main.m`命令重寫為C++語法的源文件,對結果分析如下

// __block_impl結構體
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// `__main_block_impl_0`是block的結構體,包含了兩個成員__block_impl類型的impl成員以及__main_block_desc_0類型的Desc成員;一個構造方法__main_block_impl_0,構造方法中初始化impl成員和Desc成員
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// Block執行的方法
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("block invoke");
}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

// 重寫后的入口函數main
int main(int argc, const char * argv[]) {
    // 創建__main_block_impl_0類型的實例,名稱為blk,這里把改實例強制轉換為`void(funcPtr *)(void)`型的方法指針,不懂為何,因為在下一步會把該方法指針強制轉換為對應的結構體,也就是說`void(funcPtr *)(void)`型的方法指針并沒有真實的使用到。
    void (*blk) (void) =
    ((void (*)())&__main_block_impl_0(
                                      (void *)__main_block_func_0,
                                      &__main_block_desc_0_DATA));
    // blk是`__main_block_impl_0`類型的實例的指針,根絕結構體的內存布局規則,該結構體的第一個成員為`__block_impl` 類型,可以強制轉換為`__block_impl *`類型,獲取FuncPtr函數指針,強制轉換為`(void (*)(__block_impl *))`類型的函數指針,然后執行這個函數指針對應的函數,參數為blk
    // 由上面的步驟可知,Block的調用本質上是使用函數指針調用函數
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

使用自動變量的Block分析

有如下的源代碼,創建一個簡單的block,在block做的處理是打印一個字符串,使用到外部的自動變量,然后執行這個block。接下來會從源碼入手對此進行分析:block是如何使用外部的自動變量的

// 使用變量的Block分析
// 源代碼
int main(int argc, const char * argv[]) {
    int age = 100;
    const char *name = "zyt";
    void (^blk) (void) = ^{
        printf("block invoke age = %d age = %s", age, name);
    };
    age = 101;
    blk();
    return 0;
}

// 輸出:
block invoke age = 100 age = zyt

使用clang -rewrite-objc main.m命令重寫為C++語法的源文件。對比上一個的數據結構方法和調用步驟大體上是一樣的,只針對變化的地方進行分析

__main_block_impl_0結構體的變化

增加了兩個成員:int類型的age成員、char*類型的name成員,用于保存外部變量的值,在初始化的時候就會使用自動變量的值初始化這兩個成員的值

調用的變化

__main_block_func_0函數的參數struct __main_block_impl_0 *__cself在這個例子有使用到了,因為兩個自動變量對應的值被保存在__main_block_impl_0結構體中了,方法中有使用到這兩個變量,直接從__main_block_impl_0結構體中獲取這兩個值,但是這兩個值是獨立于自動變量的存在了

由上面的分析,可以得出如下的結論:使用變量的Block調用本質上是使用函數指針調用函數,參數是保存在block的結構體中的,并且保存的值而不是引用


// 使用`clang -rewrite-objc main.m`命令重寫為C++語法的源文件,對結果分析如下

// __block_impl結構體
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// __main_block_impl_0,包含了四個成員__block_impl類型的impl成員、__main_block_desc_0類型的Desc成員、int類型的age成員、char*類型的name成員;一個構造方法__main_block_impl_0,構造方法中初始化impl成員、Desc成員、age成員和name成員,比起上一個改結構體多了兩個成員,用于保存外部變量的值
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  const char *name;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, const char *_name, int flags=0) : age(_age), name(_name) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// Block執行的方法
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  const char *name = __cself->name; // bound by copy

        printf("block invoke age = %d age = %s", age, name);
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

// 重寫后的入口函數main
// 由上面的步驟可知,使用變量的Block調用本質上是使用函數指針調用函數,參數是保存在block的結構體中的,并且保存的值而不是引用
int main(int argc, const char * argv[]) {
    int age = 100;
    const char *name = "zyt";
    void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, name));
    age = 101;
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

使用__block修飾的變量的Block分析

有如下的源代碼,有個__block修飾的int類型的自動變量age,在block和變量age的作用域中分別作了修改,從輸入的結果看看是兩次都生效了,接下來會從源碼入手對此進行分析:block中是如何處理__block修飾的自動變量,該自動變量的內存是如何變化的

// 源代碼
int main(int argc, const char * argv[]) {
    __block int age = 100;
    const char *name = "zyt";
    void (^blk) (void) = ^{
        age += 2;
        printf("block invoke age = %d age = %s", age, name);
    };
    age += 1;
    blk();
}

// 輸出 :
// block invoke age = 103 age = zyt

使用clang -rewrite-objc main.m命令重寫為C++語法的源文件,對結果分析如下的注釋,該轉換后的代碼做了些許的調整,包括代碼的縮進和添加了結構體聲明,使得該代碼可以直接運行


// clang改寫后的代碼如下,以下代碼是經過調整可以直接運行的

struct __main_block_desc_0;

// __block_impl結構體
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

// `__block`修飾的變量對應的結構體,里面包含了該變量的原始值也就是age成員,另外還有一個奇怪的`__forwarding`成員,稍后我們會分析它的用處  
struct __Block_byref_age_0 {
    void *__isa;
    __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
};

// `__main_block_impl_0`結構體包含了四個成員`__block_impl`類型的impl成員、`__main_block_desc_0`類型的Desc成員、`__Block_byref_age_0 *`類型的age成員、char*類型的name成員;一個構造方法`__main_block_impl_0`,構造方法中初始化impl成員、Desc成員、age成員和name成員,比起上一個結構體的變化是age的類型變為了包裝自動變量的結構體了
struct __main_block_impl_0 {
    __block_impl impl;
    __main_block_desc_0* Desc;
    const char *name;
    __Block_byref_age_0 *age; // by ref
    __main_block_impl_0(void *fp,
                        struct __main_block_desc_0 *desc,
                        const char *_name,
                        __Block_byref_age_0 *_age,
                        int flags=0) :
    name(_name),
    age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// Block執行的方法
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_age_0 *age = __cself->age; // bound by ref
    const char *name = __cself->name; // bound by copy
    
    (age->__forwarding->age) += 2;
    printf("block invoke age = %d age = %s", (age->__forwarding->age), name);
}

// Block拷貝函數
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
}

// Block銷毀函數
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

// 重寫后的入口函數main
int main(int argc, const char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {
        (void*)0,
        (__Block_byref_age_0 *)&age,
        0,
        sizeof(__Block_byref_age_0),
        100};
    const char *name = "zyt";
    struct __main_block_impl_0 blockImpl =
    __main_block_impl_0((void *)__main_block_func_0,
                        &__main_block_desc_0_DATA,
                        name,
                        (__Block_byref_age_0 *)&age,
                        570425344);
    void (*blk) (void) = ((void (*)())&blockImpl);
    (age.__forwarding->age) += 1;
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}

__block修飾自動變量轉換為C++代碼的結構體關系圖:

__block修飾自動變量轉換為C++代碼的結構體關系圖

該重寫的代碼大部分和上面分析過的例子代碼是類似,發現增加了兩個處理方法Block拷貝函數__main_block_copy_0和Block銷毀函數__main_block_dispose_0,這兩個函數會保存在__main_block_desc_0結構體的copydispose成員中;另外添加了一個__Block_byref_age_0結構體類型用戶處理__block修飾的自動變量。以下針對這兩點從源代碼的角度進行一個分析

__main_block_copy_0方法中調用到的_Block_object_assign可以在 runtime.c 這里找到 ,主要看下_Block_object_assign方法里面的處理邏輯

  • flags參數值為8,是BLOCK_FIELD_IS_BYREF枚舉對應的值,會走到_Block_byref_assign_copy方法的調用步驟
  • _Block_byref_assign_copy方法會在在堆上創建Block_byref對象,也就是Block對象,并且把棧上和堆上的Block對象的forwarding屬性值都修改為指向堆上的Block對象,這樣使用兩個對象的修改值都會修改為同一個地方

棧上的__block自動變量__forwarding指向關系以及拷貝到堆上之后__forwarding指向關系如下圖所示

棧上的`__block`自動變量`__forwarding`指向關系以及拷貝到堆上之后`__forwarding`指向關系如下圖所示

具體使用到的代碼和對應的注釋如下:

/*
 * When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
 * to do the assignment.
 */
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
            _Block_assign_weak(object, destAddr);
        }
        else {
            // do *not* retain or *copy* __block variables whatever they are
            _Block_assign((void *)object, destAddr);
        }
    }
    // 代碼會走到這個分支中,調用方法`_Block_byref_assign_copy`
    else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
        // copying a __block reference from the stack Block to the heap
        // flags will indicate if it holds a __weak reference and needs a special isa
        _Block_byref_assign_copy(destAddr, object, flags);
    }
    // (this test must be before next one)
    else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
        // copying a Block declared variable from the stack Block to the heap
        _Block_assign(_Block_copy_internal(object, flags), destAddr);
    }
    // (this test must be after previous one)
    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        //printf("retaining object at %p\n", object);
        _Block_retain_object(object);
        //printf("done retaining object at %p\n", object);
        _Block_assign((void *)object, destAddr);
    }
}


/*
 * Runtime entry points for maintaining the sharing knowledge of byref data blocks.
 *
 * A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data
 * Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr.
 * We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment it.
 * Otherwise we need to copy it and update the stack forwarding pointer
 * XXX We need to account for weak/nonretained read-write barriers.
 */

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    struct Block_byref **destp = (struct Block_byref **)dest;
    struct Block_byref *src = (struct Block_byref *)arg;
        
    //printf("_Block_byref_assign_copy called, byref destp %p, src %p, flags %x\n", destp, src, flags);
    //printf("src dump: %s\n", _Block_byref_dump(src));
    if (src->forwarding->flags & BLOCK_IS_GC) {
        ;   // don't need to do any more work
    }
    else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        //printf("making copy\n");
        // src points to stack
        bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
        // if its weak ask for an object (only matters under GC)
        struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
        copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
        // copy是拷貝到堆上的Block_byref類型對象,scr是原來的Block_byref類型對象,兩者的forwarding成員都指向到堆上的Block_byref類型對象也就是copy,這樣不管是在棧上修改__block修飾的變量(age.age = 102調用)還是在堆上修改__block修飾的變量()
        copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
        if (isWeak) {
            copy->isa = &_NSConcreteWeakBlockVariable;  // mark isa field so it gets weak scanning
        }
        if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            copy->byref_keep = src->byref_keep;
            copy->byref_destroy = src->byref_destroy;
            (*src->byref_keep)(copy, src);
        }
        else {
            // just bits.  Blast 'em using _Block_memmove in case they're __strong
            _Block_memmove(
                (void *)&copy->byref_keep,
                (void *)&src->byref_keep,
                src->size - sizeof(struct Block_byref_header));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    // assign byref data block pointer into new Block
    _Block_assign(src->forwarding, (void **)destp);
}

Block引用分析

Block強引用分析

// 定義類YTObject
@interface YTObject : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void (^blk)(void);
- (void)testReferenceSelf;
@end

@implementation YTObject

- (void)testReferenceSelf {
    self.blk = ^ {
        // 這里不管是使用self.name還是_name,從clang重寫的代碼上看,處理方式是一樣的
        printf("self.name = %s", self.name.UTF8String);
    };
    self.blk();
}

- (void)dealloc {
    NSLog(@"==dealloc==");
}

@end

// 使用YTObject
int main(int argc, const char * argv[]) {
    YTObject *obj = [YTObject new];
    obj.name = @"hello";
    [obj testReferenceSelf];
    
    return 0;
}

// 輸出 :
// self.name = hello

使用clang -rewrite-objc main.m命令重寫為C++語法的源文件如下


static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[6];
} _OBJC_$_INSTANCE_METHODS_YTObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    6,
    {{(struct objc_selector *)"testReferenceSelf", "v16@0:8", (void *)_I_YTObject_testReferenceSelf},
    {(struct objc_selector *)"dealloc", "v16@0:8", (void *)_I_YTObject_dealloc},
    {(struct objc_selector *)"name", "@16@0:8", (void *)_I_YTObject_name},
    {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_YTObject_setName_},
    {(struct objc_selector *)"blk", "@?16@0:8", (void *)_I_YTObject_blk},
    {(struct objc_selector *)"setBlk:", "v24@0:8@?16", (void *)_I_YTObject_setBlk_}}
};


struct __YTObject__testReferenceSelf_block_impl_0 {
  struct __block_impl impl;
  struct __YTObject__testReferenceSelf_block_desc_0* Desc;
  YTObject *self;
  __YTObject__testReferenceSelf_block_impl_0(void *fp, struct __YTObject__testReferenceSelf_block_desc_0 *desc, YTObject *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __YTObject__testReferenceSelf_block_func_0(struct __YTObject__testReferenceSelf_block_impl_0 *__cself) {
    YTObject *self = __cself->self; // bound by copy
    printf("self.name = %s", ((const char * _Nullable (*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")), sel_registerName("UTF8String")));
}
static void __YTObject__testReferenceSelf_block_copy_0(struct __YTObject__testReferenceSelf_block_impl_0*dst, struct __YTObject__testReferenceSelf_block_impl_0*src) {
    _Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __YTObject__testReferenceSelf_block_dispose_0(struct __YTObject__testReferenceSelf_block_impl_0*src) {
    _Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __YTObject__testReferenceSelf_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __YTObject__testReferenceSelf_block_impl_0*, struct __YTObject__testReferenceSelf_block_impl_0*);
  void (*dispose)(struct __YTObject__testReferenceSelf_block_impl_0*);
} __YTObject__testReferenceSelf_block_desc_0_DATA = { 0, sizeof(struct __YTObject__testReferenceSelf_block_impl_0), __YTObject__testReferenceSelf_block_copy_0, __YTObject__testReferenceSelf_block_dispose_0};


static void _I_YTObject_testReferenceSelf(YTObject * self, SEL _cmd) {
    ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlk:"), ((void (*)())&__YTObject__testReferenceSelf_block_impl_0((void *)__YTObject__testReferenceSelf_block_func_0, &__YTObject__testReferenceSelf_block_desc_0_DATA, self, 570425344)));
    ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("blk"))();
}

int main(int argc, const char * argv[]) {
    YTObject *obj = ((YTObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YTObject"), sel_registerName("new"));
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)obj, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_fk_19cr58zj0f7f19001k_mxzlm0000gp_T_main_21b52d_mii_1);
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("testReferenceSelf"));

    return 0;
}

Block引用對象轉換為C++代碼的結構體關系圖:

Block引用對象轉換為C++代碼的結構體關系圖

從類圖上可以明顯的看到__YTObject__testReferenceSelf_block_impl_0YTObject之間有循環依賴的關系,這樣NSLog(@"==dealloc==");這段代碼最終是沒有調用到,也就是這里會出現Block循環引用導致內存泄漏問題

Block弱引用分析

Block的循環引用問題其中一種解決方案是可以使用weakself來解除這種強引用關系,防止內存的泄漏,代碼的改造如下

@interface YTObject : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void (^blk)(void);
- (void)testReferenceSelf;
@end

@implementation YTObject

- (void)testReferenceSelf {
    __weak typeof(self) weakself = self;
    self.blk = ^ {
        __strong typeof(self) strongself = weakself;
        // 這里不管是使用self.name還是_name,從clang重寫的代碼上看,處理方式是一樣的
        printf("self.name = %s\n", strongself.name.UTF8String);
    };
    self.blk();
}

- (void)dealloc {
    printf("==dealloc==");
}

@end


// 使用YTObject
int main(int argc, const char * argv[]) {

    YTObject *obj = [YTObject new];
    obj.name = @"hello";
    [obj testReferenceSelf];
    
    return 0;
}

添加了weak之后需要使用clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.14 main.mm這個命令才能夠重寫為C++語言對應的代碼,重寫后的代碼如下

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[6];
} _OBJC_$_INSTANCE_METHODS_YTObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    6,
    {{(struct objc_selector *)"testReferenceSelf", "v16@0:8", (void *)_I_YTObject_testReferenceSelf},
        {(struct objc_selector *)"dealloc", "v16@0:8", (void *)_I_YTObject_dealloc},
        {(struct objc_selector *)"name", "@16@0:8", (void *)_I_YTObject_name},
        {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_YTObject_setName_},
        {(struct objc_selector *)"blk", "@?16@0:8", (void *)_I_YTObject_blk},
        {(struct objc_selector *)"setBlk:", "v24@0:8@?16", (void *)_I_YTObject_setBlk_}}
};

struct __YTObject__testReferenceSelf_block_impl_0 {
    struct __block_impl impl;
    struct __YTObject__testReferenceSelf_block_desc_0* Desc;
    YTObject *const __weak weakself;
    __YTObject__testReferenceSelf_block_impl_0(void *fp, struct __YTObject__testReferenceSelf_block_desc_0 *desc, YTObject *const __weak _weakself, int flags=0) : weakself(_weakself) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __YTObject__testReferenceSelf_block_func_0(struct __YTObject__testReferenceSelf_block_impl_0 *__cself) {
    YTObject *const __weak weakself = __cself->weakself; // bound by copy
    __attribute__((objc_ownership(strong))) typeof(self) strongself = weakself;
    printf("self.name = %s\n", ((const char * _Nullable (*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)strongself, sel_registerName("name")), sel_registerName("UTF8String")));
}

static void __YTObject__testReferenceSelf_block_copy_0(struct __YTObject__testReferenceSelf_block_impl_0*dst, struct __YTObject__testReferenceSelf_block_impl_0*src) {
    _Block_object_assign((void*)&dst->weakself, (void*)src->weakself, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __YTObject__testReferenceSelf_block_dispose_0(struct __YTObject__testReferenceSelf_block_impl_0*src) {
    _Block_object_dispose((void*)src->weakself, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __YTObject__testReferenceSelf_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __YTObject__testReferenceSelf_block_impl_0*, struct __YTObject__testReferenceSelf_block_impl_0*);
    void (*dispose)(struct __YTObject__testReferenceSelf_block_impl_0*);
} __YTObject__testReferenceSelf_block_desc_0_DATA = { 0, sizeof(struct __YTObject__testReferenceSelf_block_impl_0), __YTObject__testReferenceSelf_block_copy_0, __YTObject__testReferenceSelf_block_dispose_0};

static void _I_YTObject_testReferenceSelf(YTObject * self, SEL _cmd) {
    __attribute__((objc_ownership(weak))) typeof(self) weakself = self;
    __YTObject__testReferenceSelf_block_impl_0 blockImpl =
    __YTObject__testReferenceSelf_block_impl_0((void *)__YTObject__testReferenceSelf_block_func_0,
                                               &__YTObject__testReferenceSelf_block_desc_0_DATA,
                                               weakself,
                                               570425344)
    ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, s
                                                          el_registerName("setBlk:"),
                                                          ((void (*)())&blockImpl));
    ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("blk"))();
}

int main(int argc, const char * argv[]) {
    YTObject *obj = ((YTObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YTObject"), sel_registerName("new"));
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)obj, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_fk_19cr58zj0f7f19001k_mxzlm0000gp_T_main_6f39dd_mii_0);
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("testReferenceSelf"));
    
    return 0;
}

從上面的代碼中可以看到__YTObject__testReferenceSelf_block_impl_0結構體中weakself成員是一個__weak修飾的YTObject類型對象,也就是說__YTObject__testReferenceSelf_block_impl_0YTObject的依賴是弱依賴。weak修飾變量是在runtime中進行處理的,在YTObject對象的Dealloc方法中會調用weak引用的處理方法,從weak_table中尋找弱引用的依賴對象,進行清除處理,可以查看Runtime源碼中objc_object::clearDeallocating該方法的處理邏輯,另外關于__weak修飾的變量的詳細處理可以查看Runtime相關的知識

Block弱引用對象轉換為C++代碼的結構體關系圖:

Block弱引用對象轉換為C++代碼的結構體關系圖

關于具體的weakSelf和strongSelf可以參考這篇文章深入研究Block用weakSelf、strongSelf、@weakify、@strongify解決循環引用中的描述

weakSelf 是為了block不持有self,避免Retain Circle循環引用。在 Block 內如果需要訪問 self 的方法、變量,建議使用 weakSelf。
strongSelf的目的是因為一旦進入block執行,假設不允許self在這個執行過程中釋放,就需要加入strongSelf。block執行完后這個strongSelf 會自動釋放,沒有不會存在循環引用問題。如果在 Block 內需要多次 訪問 self,則需要使用 strongSelf。

結束

以上就是關于Block底層實現的一些分析,不妥之處敬請指教

參考

深入研究Block用weakSelf、strongSelf、@weakify、@strongify解決循環引用

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

推薦閱讀更多精彩內容