block底层实现

2018-05-18  本文已影响208人  GemShi

最近读书,关于block的底层实现,有以下思考和总结

一、block是什么

1.首先写一个简单的block

#import <stdio.h>

int main(void) {
    
    void (^block)(void) = ^{
        printf("hello world!");
    };
    
    block();
    
    return 0;
}

2.将main.m 编译后 clang -rewite-objc main.m 生成 .cpp 文件

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
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;
  }
/**
3.参数fp即传入的参数__main_block_func_0函数的地址,赋值给结构体实例 impl 的属性 FuncPtr
4.参数desc即传入的 &__main_block_desc_0_DATA结构体取地址赋值给了结构体指针 Desc
5.结构体实例 impl 的 isa 指针存放了 _NSConcreteStackBlock类的地址
6._NSConcreteStackBlock是在将block作为OC对象处理时,该类的信息放置于_NSConcreteStackBlock 中,
由此可见,block的实质是OC对象
*/
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {


        printf("hello world!");
    }

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)};
int main(void) {

    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
/**
对应代码
void (^block)(void) = ^{
    printf("hello world!");
};
1.等号左边 void (*block)(void)是一个无参无返的函数指针(是一个指针,指向函数)
2.等号右边 __main_block_impl_0 首先,c++结构体包含自己的属性,构造方法,成员方法,
所以 &__main_block_impl_0()是对结构体构造函数取地址,
函数的参数是 (void *)__main_block_func_0 和 &__main_block_desc_0_DATA
*/

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
/**
对应代码 block();
7.前半部分((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)
访问结构体 __block_impl自己的成员变量FuncPtr,
FuncPtr在步骤3被赋值了 __main_block_func_0(block执行代码块)函数的地址
8.后半部分((__block_impl *)block),将block自身作为参数传递
*/

    return 0;
}

3.引入变量

int main(void) {
    
    int a = 10;
    void (^block)(void) = ^{
        printf("%d\n",a);
    };
    block();
    
    return 0;
}
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
/**
2.在c++中 a(_a) 表示 _a 这个形参赋值给 a 这个实参
*/
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
/**
3.定义了一个新的变量,接收结构体成员变量a的值,__cself->a 表示结构体访问自己的属性。
*/
        printf("%d\n",a);
    }

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)};
int main(void) {

    int a = 10;
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
/**
1.将变量a的值传入,并赋值给了结构体成员变量
*/

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

4.__block变量

int main(void) {
    
    __block int a = 10;
    void (^block)(void) = ^{
        a += 10;
        printf("%d\n",a);
    };
    
    block();
    
    return 0;
}
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;  //由步骤2得出指向的结构体本身
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
/**
4.将传入的结构体指针的成员变量__forwarding的值(地址),赋值给当前结构体成员变量结构体指针a,保存了__block变量地址
*/
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref
/**
5.访问结构体成员变量a,赋值给新的结构体指针
*/
        (a->__forwarding->a) += 10;
/**
6.a->__forwarding首先找到结构体指针的指向,a->__forwarding->a获取到有效成员变量a的值并进行修改
*/
        printf("%d\n",(a->__forwarding->a));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
/**
7.栈copy到堆时调用,因为结构体成员变量包含所截获的变量或者__block变量结构体指针,所以copy的时候也一起copy了
*/
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
/**
8.堆上的block废弃时调用
*/
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};
int main(void) {

    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
/**
1.变量a不再单纯的是一个int类型,变成了__Block_byref_a_0结构体类型,并分别传入的参数进行赋值
2.第二个参数(__Block_byref_a_0 *)&a,结构体指针,值是a的地址,a又是结构体__Block_byref_a_0,
所以__Block_byref_a_0中的结构体指针__forwarding指向自身
3.C++中,__attribute__ 表示可以设置函数属性。
理解一下字面意思大概知道内部发生了什么
byref 是把内存地址告诉程序,所以改变的直接就是内存中的数值。按地址传递参数使过程用变量的内存地址去访问实际变量的内容。结果,将变量传递给过程时,通过过程可永远改变变量值。
byval 是把内存数值的拷贝给程序,所以改变的只是拷贝,内存原来的值是不会改变的。按值传递参数时,传递的只是变量的副本。如果过程改变了这个值,则所作变动只影响副本而不会影响变量本身。
*/

    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

📍结论:
1.block是函数指针,指向的是block代码块编译时生成的函数地址。执行block相当于找到指向block的指针。
2.block对象是结构体
(1)有isa指针(impl->isa)指向自己的类(_NSConcreteStackBlock)
(2)有desc结构体描述block的信息
(3)__forwarding((__Block_byref_a_0 *)a -> __forwarding)指向自己或堆上自己的地址
(4)当block对象截获变量,变量也会出现在block结构体中(int a)
(5)指向block代码块的函数指针(impl.FuncPtr = fp)。
(6)block结构体的构造函数的参数,包括函数指针,描述block的结构体,自动截获的变量(全局变量不用截获),引用到的__block变量。(__block对象也会转变成结构体)
3.block代码块在编译的时候会生成一个函数,函数的参数是block对象结构体指针。执行block,相当于通过block的地址找到__block变量结构体指针,再找到变量值,进行操作。

二、block的类

1.block在编译中,会被当成结构体处理,block实际结构

structBlock_literal_1{
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    intflags;
    intreserved;
    void(*invoke)(void *,...);    //block执行时调用的函数指针,block定义时内部的执行代码
    structBlock_descriptor_1{
        unsigned long intreserved;         // NULL
        unsigned long intsize;         // sizeof(struct Block_literal_1)
        // optional helper functions
         void(*copy_helper)(void *dst, void *src);     // IFF (1<<25)
         void(*dispose_helper)(void *src);             // IFF (1<<25)
         // required ABI.2010.3.16
         const char *signature;                         // IFF (1<<30)
     } *descriptor;
     // imported variables
};

2.block的类有三种

block的类 设置对象的存储域
_NSConcreteGlobalBlock 程序的数据区域(.data区)
_NSConcreteStackBlock
_NSConcreteMallocBlock
应用程序的内存分配

(1)_NSConcreteGlobalBlock

#import <Foundation/Foundation.h>

void (^block)(void) = ^{
    
};

int main(void) {
    
    void (^block1)(void) = ^{
        
    };
    
    NSLog(@"%s %p",object_getClassName(block),block);
    NSLog(@"%s %p",object_getClassName(block1),block1);
    
    return 0;
}

//输出
//__NSGlobalBlock__ 0x100001058
//__NSGlobalBlock__ 0x100001098

(2)_NSConcreteStackBlock
当block引入变量,变量内存地址会发生如下变化

#import <Foundation/Foundation.h>

int main(void) {
    
    int a = 10;
    void (^block)(void) = ^{
        printf("%d\n",a);
    };
    
    NSLog(@"%s %p",object_getClassName(block),block);
    NSLog(@"%@",^{
        printf("%d\n",a);
    });
    
    return 0;
}
//输出
//__NSMallocBlock__ 0x100443c80
//<__NSStackBlock__: 0x7fff5fbff6b0>

(3)_NSConcreteMallocBlock
从上一步可以看出,block在被赋值后,从栈来到了堆,这段代码是从栈copy到堆的过程

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    ...
    aBlock = (struct Block_layout *)arg;
    ...
    // Its a stack block.  Make a copy.
    if (!isGC) {
        // 申请block的堆内存
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        // 拷贝栈中block到刚申请的堆内存中
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 1;
        // 改变isa指向_NSConcreteMallocBlock,即堆block类型
        result->isa = _NSConcreteMallocBlock;
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
    else {
        ...
    }
}

block是默认分配在栈上的唯一OC对象,因为编译器更倾向于在栈上分配空间,因为执行效率较高,但是只能是在已知实际大小的情况下去分配,只有简单的值(比如指针)可以被分配在栈上,block的大小是确定的,你创建了一个给定的block就不能修改, 在整个执行过程中block不变, 它是需要快速执行的代码段,因此它是栈的最佳候选。

📍结论:
1.定义在函数外的block和定义在函数内部且没有捕获自动变量的block是全局block。
2.ARC下并且有变量捕获的情况下,对block自动执行了copy,将block由栈---->copy---->堆
3.copy的过程中,主要实现,通过memmove函数将block内容进行copy,并且将 isa 指针指向了_NSConcreteMallocBlock

三、block的copy

1.自身的copy
(1)栈Block
栈block拷贝复制了内容,重置了isa指针指向,重置了flags参数

struct Block_layout *result = malloc(aBlock->descriptor->size);
      if (!result) return (void *)0;
      memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
      // reset refcount
      result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
      result->flags |= BLOCK_NEEDS_FREE | 1;
      result->isa = _NSConcreteMallocBlock;
      if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
          //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
          (*aBlock->descriptor->copy)(result, aBlock); // do fixup
      }
      return result;

(2)堆Block
改变引用计数

if (aBlock->flags & BLOCK_NEEDS_FREE) {
      // latches on high
      latching_incr_int(&aBlock->flags);
      return aBlock;
  }

(3)全局Block
直接返回了传入的block

else if (aBlock->flags & BLOCK_IS_GLOBAL) {
      return aBlock;
  }

2.__block变量的copy
(1)__block修饰的基本数据类型变量
会生成__Block_byref_a_0结构体,__block将基本数据类型包装成对象,并且只在最初block拷贝时复制一次,后面的拷贝只会增加这个捕获变量的引用计数。
(2)没有用__block修饰的对象变量

    NSObject *obj = [[NSObject alloc] init];
    void(^block)(void) = ^{
        NSLog(@"%@",obj);
    };
    block();
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *obj;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSObject *obj = __cself->obj; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_jctrsbb11gb_vzv3gzmszy1h0000gn_T_main_8ef076_mi_0,obj);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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};
int main(void) {
    NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

并没有生成__Block_byref_a_0结构体,在_Block_object_assign中对应的判断代码:

else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
    _Block_retain_object(object);
    _Block_assign((void *)object, destAddr);
}

通过以上代码可以看出,对对象进行了retain,操作了对象的引用计数
(3)用__block修饰的对象变量

    __block NSObject *obj = [[NSObject alloc] init];
    void(^block)(void) = ^{
        NSLog(@"%@",obj);
    };
    block();
struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_obj_0 *obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_obj_0 *obj = __cself->obj; // bound by ref

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_jctrsbb11gb_vzv3gzmszy1h0000gn_T_main_ae30ca_mi_0,(obj->__forwarding->obj));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 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};
int main(void) {
    __attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

生成了__Block_byref_obj_0结构体,并且结构体中多了两个成员函数,void (*__Block_byref_id_object_copy)(void*, void*);void (*__Block_byref_id_object_dispose)(void*); 用来实现对对象的内存管理。copy/dispose可以理解为retain(copy)/release

四、block的循环引用
#import "Person.h"

typedef void(^block)(void);

@implementation Person
{
    block _blk;
}

-(void)test
{
    _blk = ^{
        NSLog(@"%@",self);  //⚠️warning - retainCycle
    };
}

@end

这段代码会报warning⚠️,因为造成循环引用retainCycle


相互持有

解决
1.__weak一端变为弱引用(MRC下无效)

    __weak typeof(self)wself = self;
    _blk = ^{
        NSLog(@"%@",wself);
    };
__weak弱引用

2.引入__block变量(ARC下无效)
当block由栈copy到堆,若block使用的变量为附有__block说明符的id类型或对象类型的自动变量,不会被retain
这段代码在ARC下不会走dealloc方法

    __block id obj = self;
    _blk = ^{
        NSLog(@"%@",obj);
    };
引入__block变量

以上是我个人分析,有不合理的地方,欢迎指正

上一篇下一篇

猜你喜欢

热点阅读