我的Object-C

iOS底层原理 - Block

2019-11-08  本文已影响0人  _曾梦想仗剑走天涯

关于block的问题

1.block的原理是怎样的?本质是什么?
2.__block的作用是什么?有什么使用注意点?
3.block的属性修饰词为什么是copy?使用block有哪些使用注意?
4.block在修改NSMutableArray,需不需要添加__block?

我们可以通过得到 xcrun -sdk iphonos clang -arch arm64 -rewrite-objc main.m 得到源码 block函数调用的本质

int age = 20;
void (^block)(int, int) =  ^(int a , int b){
   NSLog(@"this is a block! -- %d", age);
};
   block(10, 10);

//block的底层实现
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 _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
//定义block变量
        void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
//通过block调用FuncPtr
        ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
    }
    return 0;
}

block的本质

1.block本质上也是一个OC对象,它内部也有个isa指针
2.block是封装了函数调用以及函数调用环境的OC对象
bl右图所示

image.png

block的变量捕获(capture)

image.png

block的类型

image.png
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )

void test() {
    // Global:没有访问auto变量
    void (^block1)(void) = ^{
        NSLog(@"block1---------");
    };
    
    // Stack:访问了auto变量
    int age = 10;
    void (^block2)(void) = ^{
        NSLog(@"block2---------%d", age);
    };
    
    // Malloc:__NSStackBlock__调用了copy
    NSLog(@"%p", [block2 copy]);
    
}
image.png

block的copy

在_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

在_Block_object_dispose函数中会自动释放引用的auto变量
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->person, 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*);
} 

当block内部访问了对象类型的auto变量时
如果block是在栈上,将不会对auto变量产生强引用

如果block被拷贝到堆上
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

如果block从堆上移除
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(release)

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
block作为函数返回值时
将block赋值给__strong指针时
block作为Cocoa API中方法名含有usingBlock的方法参数时
block作为GCD API的方法参数时

MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);

ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

__block修饰符

我们都知道只要在block里面修改临时变量就会直接报错,稍微有点开发经验的就直接直接加__block来修饰变量就直接达到目的,那到底为什么呢??? 

typedef void (^CCBlock)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        CCBlock block = ^{
            age = 20;//Variable is not assignable (missing __block type specifier)
            NSLog(@"age is %d", age);
        };
        block();
    }
    return 0;
}

struct __Block_byref_weakPerson_0 {
  void *__isa; // 8
__Block_byref_weakPerson_0 *__forwarding; // 8
 int __flags; // 4
 int __size; // 4
 void (*__Block_byref_id_object_copy)(void*, void*); // 8
 void (*__Block_byref_id_object_dispose)(void*); // 8
 MJPerson *__weak weakPerson;
};

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

__block可以用于解决block内部无法修改auto变量值的问
__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象
从源码里面我们进行具体的分析

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // 我们的age变量会变成结构体age
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref
  NSObject *p = __cself->p; // bound by copy
///1.block内部存在一个指针指向age结构体
///2.通过指针找到结构体的内存地址,把age结构体的age的值修改完成
  (age->__forwarding->age) = 20;
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_0, p);
        }

__block的内存管理

当block在栈上时,并不会对__block变量产生强引用

当block被copy到堆时
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会对__block变量形成强引用(retain)


image.png

当block从堆中移除时
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的__block变量(release)

上一篇下一篇

猜你喜欢

热点阅读