Objc Block實現(xiàn)分析

Objc Block實現(xiàn)分析

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

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

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

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

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

// 無參數(shù)無返回值的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定義和調(diào)用的代碼變成了大幾十行,不過該源代碼還算是相對簡單的,我們從main函數(shù)入口逐步的進行分析:

main函數(shù)中創(chuàng)建__main_block_impl_0類型的實例

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

__main_block_impl_0實例的初始化參數(shù)

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

__main_block_impl_0實例的調(diào)用

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

注意點:(__block_impl *)blk這里做了一個強制轉(zhuǎn)換,blk是__main_block_impl_0類型的實例的指針,根據(jù)結(jié)構(gòu)體的內(nèi)存布局規(guī)則,該結(jié)構(gòu)體的第一個成員為__block_impl 類型,所以可以強制轉(zhuǎn)換為__block_impl *類型。

由上面的分析,可以得出如下的結(jié)論:使用變量的Block調(diào)用本質(zhì)上是使用函數(shù)指針調(diào)用函數(shù)


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

// __block_impl結(jié)構(gòu)體
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// `__main_block_impl_0`是block的結(jié)構(gòu)體,包含了兩個成員__block_impl類型的impl成員以及__main_block_desc_0類型的Desc成員;一個構(gòu)造方法__main_block_impl_0,構(gòu)造方法中初始化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執(zhí)行的方法
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)};

// 重寫后的入口函數(shù)main
int main(int argc, const char * argv[]) {
    // 創(chuàng)建__main_block_impl_0類型的實例,名稱為blk,這里把改實例強制轉(zhuǎn)換為`void(funcPtr *)(void)`型的方法指針,不懂為何,因為在下一步會把該方法指針強制轉(zhuǎn)換為對應(yīng)的結(jié)構(gòu)體,也就是說`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`類型的實例的指針,根絕結(jié)構(gòu)體的內(nèi)存布局規(guī)則,該結(jié)構(gòu)體的第一個成員為`__block_impl` 類型,可以強制轉(zhuǎn)換為`__block_impl *`類型,獲取FuncPtr函數(shù)指針,強制轉(zhuǎn)換為`(void (*)(__block_impl *))`類型的函數(shù)指針,然后執(zhí)行這個函數(shù)指針對應(yīng)的函數(shù),參數(shù)為blk
    // 由上面的步驟可知,Block的調(diào)用本質(zhì)上是使用函數(shù)指針調(diào)用函數(shù)
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

使用自動變量的Block分析

有如下的源代碼,創(chuàng)建一個簡單的block,在block做的處理是打印一個字符串,使用到外部的自動變量,然后執(zhí)行這個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++語法的源文件。對比上一個的數(shù)據(jù)結(jié)構(gòu)方法和調(diào)用步驟大體上是一樣的,只針對變化的地方進行分析

__main_block_impl_0結(jié)構(gòu)體的變化

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

調(diào)用的變化

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

由上面的分析,可以得出如下的結(jié)論:使用變量的Block調(diào)用本質(zhì)上是使用函數(shù)指針調(diào)用函數(shù),參數(shù)是保存在block的結(jié)構(gòu)體中的,并且保存的值而不是引用


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

// __block_impl結(jié)構(gòu)體
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成員;一個構(gòu)造方法__main_block_impl_0,構(gòu)造方法中初始化impl成員、Desc成員、age成員和name成員,比起上一個改結(jié)構(gòu)體多了兩個成員,用于保存外部變量的值
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執(zhí)行的方法
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)};

// 重寫后的入口函數(shù)main
// 由上面的步驟可知,使用變量的Block調(diào)用本質(zhì)上是使用函數(shù)指針調(diào)用函數(shù),參數(shù)是保存在block的結(jié)構(gòu)體中的,并且保存的值而不是引用
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的作用域中分別作了修改,從輸入的結(jié)果看看是兩次都生效了,接下來會從源碼入手對此進行分析:block中是如何處理__block修飾的自動變量,該自動變量的內(nèi)存是如何變化的

// 源代碼
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++語法的源文件,對結(jié)果分析如下的注釋,該轉(zhuǎn)換后的代碼做了些許的調(diào)整,包括代碼的縮進和添加了結(jié)構(gòu)體聲明,使得該代碼可以直接運行


// clang改寫后的代碼如下,以下代碼是經(jīng)過調(diào)整可以直接運行的

struct __main_block_desc_0;

// __block_impl結(jié)構(gòu)體
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

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

// `__main_block_impl_0`結(jié)構(gòu)體包含了四個成員`__block_impl`類型的impl成員、`__main_block_desc_0`類型的Desc成員、`__Block_byref_age_0 *`類型的age成員、char*類型的name成員;一個構(gòu)造方法`__main_block_impl_0`,構(gòu)造方法中初始化impl成員、Desc成員、age成員和name成員,比起上一個結(jié)構(gòu)體的變化是age的類型變?yōu)榱税b自動變量的結(jié)構(gòu)體了
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執(zhí)行的方法
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拷貝函數(shù)
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銷毀函數(shù)
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};

// 重寫后的入口函數(shù)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修飾自動變量轉(zhuǎn)換為C++代碼的結(jié)構(gòu)體關(guān)系圖:

__block修飾自動變量轉(zhuǎn)換為C++代碼的結(jié)構(gòu)體關(guān)系圖

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

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

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

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

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

具體使用到的代碼和對應(yīng)的注釋如下:

/*
 * 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);
        }
    }
    // 代碼會走到這個分支中,調(diào)用方法`_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調(diào)用)還是在堆上修改__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引用對象轉(zhuǎn)換為C++代碼的結(jié)構(gòu)體關(guān)系圖:

Block引用對象轉(zhuǎn)換為C++代碼的結(jié)構(gòu)體關(guān)系圖

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

Block弱引用分析

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

@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++語言對應(yīng)的代碼,重寫后的代碼如下

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結(jié)構(gòu)體中weakself成員是一個__weak修飾的YTObject類型對象,也就是說__YTObject__testReferenceSelf_block_impl_0YTObject的依賴是弱依賴。weak修飾變量是在runtime中進行處理的,在YTObject對象的Dealloc方法中會調(diào)用weak引用的處理方法,從weak_table中尋找弱引用的依賴對象,進行清除處理,可以查看Runtime源碼中objc_object::clearDeallocating該方法的處理邏輯,另外關(guān)于__weak修飾的變量的詳細(xì)處理可以查看Runtime相關(guān)的知識

Block弱引用對象轉(zhuǎn)換為C++代碼的結(jié)構(gòu)體關(guān)系圖:

Block弱引用對象轉(zhuǎn)換為C++代碼的結(jié)構(gòu)體關(guān)系圖

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

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

結(jié)束

以上就是關(guān)于Block底層實現(xiàn)的一些分析,不妥之處敬請指教

參考

深入研究Block用weakSelf、strongSelf、@weakify、@strongify解決循環(huán)引用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容