iOS基础:Block底层实现及理解

本文用于记录近期学习block底层后的理解。
本文的参考博文:
Block技巧与底层解析
谈Objective-C block的实现

一、Block编译转换 OC->C++

通过使用命令clang -rewrite-objc实现。
1.首先,新建一个main.m文件。
2.打开终端,cd到main.m文件所在目录。
3.输入 clang -rewrite-objc main.m命令进行转换。
4.最后main.m文件所在的目录下,新生成一个main.cpp文件。

二、Block类型

1. NSConcreteGlobalBlock

以下两种情况下,block为NSConcreteGlobalBlock
a.记述全局变量的地方有block语法时。
b.block语法的表达式中不使用任何外部变量时。

更新(11.03 9:00):

这里有个快速判断的方法。如果Block的body里使用到了外部的非全局变量和非static静态变量,那么这个Block就会在栈上创建即_NSConcreteStackBlock。反之如果没有引用变量或者仅引用了全局变量或者static静态变量则是全局Block_NSConcreteGlobalBlock
来自:漫谈Block

举例1:

#include 

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

int main()
{
    globalBlock();
    return 0;
}

转换后的C++代码:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __globalBlock_block_impl_0 {
  struct __block_impl impl;
  struct __globalBlock_block_desc_0* Desc;
  __globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

结构体__block_implisa指针指向NSConcreteGlobalBlock

举例2:
我尝试了在main函数中创建一个block,并且block不去截获变量,但是通过clang转换,发现isa指针指向的却是NSConcreteStackBlock。这一点很奇怪。
我在《oc高级编程》中看到这样一句话:

即使在函数内而不在记述广域变量的地方使用Block语法时,只要Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区域。

2. NSConcreteStackBlock

保存在栈中的 block,当函数返回时会被销毁。

#include 
int main() {
    int a = 100;
    void (^block2)(void) = ^{
        a;
    };
    return 0;
}

转换后的C++代码:

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) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

结构体__block_implisa指针指向NSConcreteStackBlock

3. NSConcreteMallocBlock

保存在堆中的 block,当引用计数为 0 时会被销毁。
但是NSConcreteMallocBlock 类型的 block 通常不会在源码中直接出现,当[block copy]的时候,会被复制到堆中。
(以下代码来自:谈Objective-C block的实现)

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
    // 1
    if (!arg) return NULL;
    // 2
    aBlock = (struct Block_layout *)arg;
    // 3
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 4
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    // 5
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;
    // 6
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    // 7
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;
    // 8
    result->isa = _NSConcreteMallocBlock;
    // 9
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }
    return result;
}

当block被copy的时候,会调用_Block_copy_internal方法,在内部result->isa = _NSConcreteMallocBlock;

更新(11.03 9:45):

感谢 啊哈呵 ,在他给的源码中找到以下代码:

void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

可以很显然的看到是通过结构体中的flags来判断是否copy。

下面举一个copy的例子,在ARC下,当Block作为函数返回值时也会拷贝成为NSConcreteMallocBlock 类型,本质是调用copy方法。
如下代码:

typedef void (^Block)();

Block getBlock() {
  char c = 'YQ';
  void (^block)() = ^{
    printf("%c", e);
  };
  return block;
}

void main {
  Block block = getBlock();
  block();
}

当在ARC环境下,能正常运行不会奔溃,是因为系统会在创建的时候调用objc_retainBlock方法,而objc_retainBlock方法实际上就是Block_copy方法。(来自runtime/objc-arr.mm)
因此本质上,以上代码的系统实现流程就变成了:在栈上创建block结构体对象,然后再通过Block_copy复制到堆上,然后把堆上的对象注册到自动释放池中,同时返回这个堆上的对象。
但是在MRC环境下,就要奔溃了,因为系统不会自动拷贝。所以需要手动拷贝:[block copy]

三、Block的拷贝

�三种类型的Block拷贝:
NSConcreteGlobalBlock拷贝后,什么也不做。
NSConcreteStackBlock拷贝后,从栈复制到堆中。
NSConcreteMallocBlock拷贝后,引用计数加一。

四、__block变量的拷贝

int main()
{
    __block int i = 0;
    void (^block)(void) = ^{
        i = 1;
    };
    return 0;
}
struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref

        (i->__forwarding->i) = 1;
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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()
{
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
    return 0;
}

当使用__block修饰符时,�基本数据类型 i 被转换成了__Block_byref_i_0结构体。__Block_byref_i_0结构体中带有 isa指针,说明它也是一个对象。
当block修改变量时,会调用下面代码:

 __Block_byref_i_0 *i = __cself->i; // bound by ref
(i->__forwarding->i) = 1;

发现绕来绕去,下面理一下。
最让人无法理解的是__forwarding指针。__forwarding指针始终指向自己。
__block int i在栈中的时候,__forwarding指向的是栈中的自己。
当 i 拷贝到堆中时候,__forwarding指向的是堆中的自己。

正如《oc高级编程》所说:

__block修饰变量用结构体成员变量__forwarding可以实现无论 __block变量配置在栈上还是堆上都能够正确的访问 __block变量。

五、Block拷贝对__block变量的影响

影响

如果Block使用了__block变量,当Block从栈拷贝到堆中,
a.栈中的__block变量会拷贝到堆中并被Block持有。
b.堆中的__block变量被Block持有。

这个和OC的引用计数内存管理相同。
下面有BlockA、BlockB 、 __block a、 __block b。
如果BlockA使用了栈上的a和b,当[BlockA copy]拷贝到堆上,a,b也会同时拷贝到堆上,并且堆上的BlockA持有堆上的a,b。
同理,当BlockA,BlockB都使用了栈上的a,当[BlockA copy],[BlockB copy]拷贝到堆上,BlockA和BlockB会同时持有堆上的a。

现在再回过头去看第四点的__block int i,就能理解为什么要有__forwarding指针了。
现有以下代码:

int main()
{
    __block int i = 0;
    void (^block)(void) = [^{
        ++i;
    } copy];
    ++i;
    block();
    printf("%d", i);
    return 0;
}

把上面代码转换为C++后:

// block中的++i实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_i_0 *i = __cself->i; // bound by ref

        ++(i->__forwarding->i);
    }
// main函数中的++i
++(i.__forwarding->i);

可以发现都是++(i.__forwarding->i);,也就是说指向的都是堆中的i。
因此输出为2。

六、一些题目,判断ARC和MRC环境下能否运行

1 都能运行

void exampleA() {
  char a = 'A';
  ^{
    printf("%cn", a);
  }();
}

2 ARC能运行

void exampleB_addBlockToArray(NSMutableArray *array) {
  char b = 'B';
  [array addObject:^{
    printf("%cn", b);
  }];
}

void exampleB() {
  NSMutableArray *array = [NSMutableArray array];
  exampleB_addBlockToArray(array);
  void (^block)() = [array objectAtIndex:0];
  block();
}

3 都能运行

void exampleC_addBlockToArray(NSMutableArray *array) {
  [array addObject:^{
    printf("Cn");
  }];
}

void exampleC() {
  NSMutableArray *array = [NSMutableArray array];
  exampleC_addBlockToArray(array);
  void (^block)() = [array objectAtIndex:0];
  block();
}

4 ARC能运行

typedef void (^dBlock)();

dBlock exampleD_getBlock() {
  char d = 'D';
  return ^{
    printf("%cn", d);
  };
}

void exampleD() {
  exampleD_getBlock()();
}

5 ARC能运行

typedef void (^eBlock)();

eBlock exampleE_getBlock() {
  char e = 'E';
  void (^block)() = ^{
    printf("%cn", e);
  };
  return block;
}

void exampleE() {
  eBlock block = exampleE_getBlock();
  block();
}

七、更新(11.03 12:00)block对各种变量的处理

在看了漫谈Block后半部分后,很想把这一部分整理一下。
首先!如果捕获的变量为id, NSObject, __attribute__((NSObject)), block类型变量和__block修饰的变量都会调用_Block_object_assign方法。我就把_Block_object_assign方法写在最上面!

void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        *dest = _Block_copy(object);
        break;
      
      ...
      case BLOCK_FIELD_IS_BYREF:
        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        *dest = object;
        break;
      ...
      default:
        break;
    }
}

1.变量为基本数据类型 无__block

#include 
int main() {
    int a = 100;
    void (^block2)(void) = ^{
        a;
    };
    return 0;
}

只会在block的结构体内部中添加一个int a变量。不能做修改。

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) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

2.变量为基本数据类型 有__block

int main()
{
    __block int i = 0;
    void (^block)(void) = ^{
        i = 1;
    };
    return 0;
}

有__block修饰的基本数据类型会转换成__Block_byref_i_0结构体。

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

然后当他被拷贝后,会进入_Block_object_assign方法的BLOCK_FIELD_IS_BYREFcase

      case BLOCK_FIELD_IS_BYREF:
        *dest = _Block_byref_copy(object);
        break;
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 points to stack
        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
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;

        if (src->flags & BLOCK_BYREF_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
            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;

            ...

            (*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));
        }
    }
    ...
    return src->forwarding;
}

通过代码发现,在堆上创建了一个copy对象。然后通过

        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy

让其始终指向堆上的自己。

3.变量为对象 无__block

因此会进入到BLOCK_FIELD_IS_OBJECT

case BLOCK_FIELD_IS_OBJECT:
        _Block_retain_object(object);
        *dest = object;
        break;
static void _Block_retain_object_default(const void *ptr __unused) { }
// 默认_Block_retain_object被赋值为_Block_retain_object_default,即什么都不做
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;

// Called from CF to indicate MRR. Newer version uses a versioned structure, so we can add more functions
// without defining a new entry point.
void _Block_use_RR2(const Block_callbacks_RR *callbacks) {
    _Block_retain_object = callbacks->retain;
    _Block_release_object = callbacks->release;
    _Block_destructInstance = callbacks->destructInstance;
}

默认_Block_retain_object被赋值为_Block_retain_object_default,即什么都不做。也就是说,在ARC环境下,Block不会在这里持有对象。(ARC环境有了更完善的内存管理,如果外部变量由__strong、copy、strong修饰时,Block会把捕获的变量用__strong来修饰进而达到持有的目的。)在MRR环境下,Block会通过_Block_retain_object方法持有id, NSObject, __attribute__((NSObject))类型变量。

4.变量为对象 有__block

因此会进入到BLOCK_FIELD_IS_OBJECT

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        *dest = object;
        break;

直接赋值。
所以通过__block修饰可以避免调用到_Block_retain_object方法,也就是在MRR环境下我们可以通过__block来避免Block强持有变量,进而避免循环引用。

你可能感兴趣的:(iOS基础:Block底层实现及理解)