阅读 141

MJiOS底层笔记--Block

本文属笔记性质,主要针对自己理解不太透彻的地方进行记录。

推荐系统直接学习小码哥iOS底层原理班---MJ老师的课确实不错,强推一波。


基本认识

block实际上为一个代码块

只有在末尾执行()才会调用

^{
    NSLog(@"this is a block!");
    NSLog(@"this is a block!");
    NSLog(@"this is a block!");
    NSLog(@"this is a block!");
};
复制代码

承接一个block

// 不带参数的block。^(){可以简化成^{

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


// 带参数的block
void (^block)(int, int) =  ^(int a , int b){
    NSLog(@"a=%d,b=%d",a,b);
};
block(20, 10);
复制代码

对于void (^block)(int, int)的含义是返回值 (^名称)(参数1, 参数2)


block的本质

封装了函数调用以及调用环境的OC对象

1. block本质上也是OC对象,内部也存在isa指针

2. block内部封装了函数调用,以及函数调用所需的环境(参数)

具体通过cpp文件可知:

int age = 20;

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

};


block(10, 10);
复制代码

block最终将会被转化成如下格式的结构体(每个block的细节不同)

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc; //block描述信息(大小等)
  int age;  //封装了函数调用所需环境(内部定义了一个age变量)
  
  
  //c++的构造函数  age(_age)表示_age将会自动赋值给age
  __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 void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
    int age = __cself->age; // 将block当初捕获的变量,赋值给执行函数

    //函数调用
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
 
}


struct __block_impl {
  void *isa;  //表明block也属于OC对象
  int Flags;
  int Reserved;
  void *FuncPtr;
};
复制代码

其中函数调用会被单独封装成__main_block_func_0方法。在block定义时,传入block结构体。

void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
复制代码

而在block结构体中,会被赋值给impl.FuncPtr = fp;,将函数地址存储在block内部。

最终,在调用block时,获取FuncPtr,传入参数执行调用。

((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
复制代码

简化一下cpp的block语句

构建一个block结构体

void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

/**
去掉强转的语法之后

将函数地址,block描述(大小等)信息,需要被捕获的变量。
传入构建block的方法`__main_block_impl_0`中进行构建
最后将`__main_block_impl_0`block结构体返回,并将其持有
**/
void (*block)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age));
复制代码

调用block结构体

((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);


/**
去掉强转的语法之后

从结构体中取出函数地址,传入参数并调用。
传入blcok是因为每一个block对象内部所捕获的变量不同
**/
block->FuncPtr(block, 10, 10);

复制代码

变量捕获

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

auto变量

c语言中的局部变量,默认都为auto变量。所以auto代指局部变量

由于auto变量的生命超出作用域就会被销毁。为保证block能够正常执行,auto变量在被block捕获时,会将传递给block的构造函数。


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        auto int a = 10;
        void (^block)(void) = ^{
            NSLog(@"age is %d", a); //age is 10
        };
        a = 20;
        block();
    }
    return 0;
}



struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc; //block信息(大小等)
  int age;  //封装了函数调用所需环境(内部定义了一个age变量)
  
  
  //c++的构造函数  age(_age)表示_age将会自动赋值给age
  __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 void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
    int age = __cself->age; // 将block当初捕获的变量,赋值给执行函数

    //函数调用
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
 
}



// main中构建block。将a的值传递给构造函数
void (*block)() = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a));
复制代码

static变量

由于静态变量的生命常驻于内存,但使用仅限于作用域内部。所以静态变量在被block捕获时,只要将指向变量值的指针(地址)传递给构造函数,即能保证block正常执行。


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int b = 10;
        void (^block)(void) = ^{
            NSLog(@"height is %d", b); //height is 20
        };
        b = 20;
        block();
    }
    return 0;
}


struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  int *b; //b为指针类型
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int *_b, int flags=0) :  b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
    
    int *b = __cself->b; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, (*b));
}


// main中构建block.将b的指针传递给构造函数
void (*block)() = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, &b));

复制代码

全局变量

由于全局变量的生命常驻于内存,并且使用不受作用域限制。所以全局变量并不需要被捕获,在执行block的函数调用时,直接使用全局变量,即能保证block正常执行。

int age_ = 10;
static int height_ = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^{
            NSLog(@"age_ is %d ,height_ is %d", age_,height_);
        };
        age_ = 20;
        height_ = 20;
        block();
    }
    return 0;
}



int age_ = 10;
static int height_ = 10;

// block结构体内部,也并未声明对应的变量
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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    // 执行函数中,直接使用全局变量
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_tz_hcmmb5t57v1cr81ydm6s5s140000gn_T_main_6f323a_mi_0, age_,height_);
        }

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; 
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));  //在执行block构造函数时,并未将全局变量传递进去
        age_ = 20;
        height_ = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

复制代码

block的类型

block与class

通过class方法,也可以证明block的本质是OC对象

void test()
{
    void (^block)(void) = ^{
        NSLog(@"Hello");
    };
    
    NSLog(@"%@", [block class]);//__NSGlobalBlock__ (其他类型的block为__NSStackBlock__或__NSMallocBlock__)
    NSLog(@"%@", [[block class] superclass]);//__NSGlobalBlock
    NSLog(@"%@", [[[block class] superclass] superclass]);//NSBlock
    NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);//NSObject
}
复制代码

block的isa

在block结构体中,isa会被指定成_NSConcreteXXXBlock的值。这个值便是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;
  }
};
复制代码

三种block类型

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

  • NSGlobalBlock ( _NSConcreteGlobalBlock )

  • NSStackBlock ( _NSConcreteStackBlock )

  • NSMallocBlock ( _NSConcreteMallocBlock )

从上到下为:低地址 --》高地址 非ARC下的block来源

block与copy

在非ARC下

绝大部分block默认都是作为__NSStackBlock__存在于栈上(没有访问auto变量的block会存在于全局区)。

__NSStackBlock__在超出作用域后,block结构体有可能会被修改污染。

这时需要将__NSStackBlock__进行copy将其转移到堆中进行管理。

之后__NSStackBlock__将转变为__NSMallocBlock__

这也是为什么block作为属性都会声明为copy(不过ARC下声明为strong也会自动copy,文档上说是为了语义)

对于不同类型的block,调用copy会有不同操作

ARC下的block

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况

  1. block作为函数返回值时
MJBlock myblock()
{
    return ^{
        NSLog(@"---------");
    };
}
复制代码
  1. 将block赋值给__strong指针
MJBlock block = ^{
    NSLog(@"---------%d", age);
};
复制代码
  1. block作为Cocoa API中方法名含有usingBlock的方法参数时
[@[] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    
}];
复制代码
  1. block作为GCD API的方法参数时
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
});
复制代码

block与对象类型的auto变量

(这里都说ARC环境下)

在block捕获对象类型的auto变量时。__main_block_desc_0结构体内部会多出__main_block_copy_0__main_block_dispose_0函数,在block移动到堆空间时堆对象进行适当的return和release。

先贴一份cpp代码(auto变量)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [NSObject new]; //OC对象
        void (^block)(void) = ^{
            NSLog(@"obj is %@", obj); //捕获
        };
        obj = nil;
        block();
    }
    return 0;
}




//block结构体
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  //如果外部为__weak内部也会为__weak
  NSObject * __strong obj; //对于auto变量,结构体中保存变量与原本相同。
  __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_tz_hcmmb5t57v1cr81ydm6s5s140000gn_T_main_d73acf_mi_0, obj);
}


//当block从栈移动到堆中时,执行此方法。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {

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

//当block从堆中移除(释放)时,执行此方法。
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    //release引用的变量
    _Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}


//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};


// main函数
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
        
        //在构造block时,将OC对象传入
        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;
}

复制代码

对象的捕获方面

基本也符合上面的规律,以局部变量为例。

只是对于对象而言,传递的都是指针。 而且为了保证OC对象在block内部正常访问,会被强引用以延长对象生命周期。

对于栈上的block

如果block是在栈上,将不会对auto变量产生强引用

当block被拷贝到堆上

会执行内部的_Block_object_assign函数,结构体内所捕获的auto对象的类型(__strong/__weak)决定如何对其进行引用。

当block从堆中移除(释放)

会执行内部的_Block_object_dispose函数,将结构体内所捕获的auto对象进行release。


__block

编译器会将__block变量包装成一个对象(__Block_byref_age_0),被声明的值作为对象的属性存在。


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 10;
        
        MJBlock block = ^{
            __strong int myage = age;
            age = 20;
            NSLog(@"age is %d", age);
        };

        
        block();
        
    }
    return 0;
}


//__block对象结构体
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

//block结构体
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *p;
  
  //不再是int age;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_p, __Block_byref_age_0 *_age, int flags=0) : p(_p), 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
    
    //从__Block_byref_age_0结构体中获得age变量,并且修改
    (age->__forwarding->age) = 20;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_0);
}


//c++ main函数
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

    
    //__block int age = 10;
    __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
    //等价于
    __Block_byref_age_0 age = {0,
                                &age,
                                0,
                                sizeof(__Block_byref_age_0),
                                10};
        
        
    //构建block结构体
    MJBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));
    //等价于
        MJBlock block = &__main_block_impl_0(__main_block_func_0,
                                         &__main_block_desc_0_DATA,
                                         p,
                                         &age,
                                         570425344);

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
    }
    return 0;
}
复制代码

外部使用__block对象时

实际上都是对结构体中的age指针进行操作,而不是结构体age。


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 10;
        age = 50;
    }
    return 0;
}



int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        (age.__forwarding->age) = 50;
    }
    return 0;
}
复制代码

__forwarding指针

__forwarding指向__block所包装的对象实体,以确保使用时的正确性。

  • 当block在栈中,__forwarding所指实体为栈上的__block包装对象。

  • 当block移动到堆上,__block包装对象在堆中也会被复制一份。而二者的__forwarding指针都指向堆中的__block包装对象。

__block修饰OC对象

OC对象的强弱引用不会体现在block结构体中(都是strong),而是体现在__Block_byref结构体中。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc] init];
        
        __block __weak JSPerson *weakPerson = person;
        MJBlock block = ^{
            NSLog(@"%p", weakPerson);
        };

        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*); // 当block被移动到堆,会对__Block_byref对象进行return(需要注意MRCblock进入堆中时不会retain该变量)
 void (*__Block_byref_id_object_dispose)(void*); // 当block从堆中移除,会对__Block_byref对象进行release
 MJPerson *__weak weakPerson; //__block包装的结构体中实际为weak引用。
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_weakPerson_0 *weakPerson; // // 依旧是strong引用
  __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;
  }
};
复制代码

三个已知的内存地址段

NSLog(@"数据段:age %p", &age);
NSLog(@"栈:a %p", &a);
NSLog(@"堆:obj %p", [[NSObject alloc] init]);
复制代码
关注下面的标签,发现更多相似文章
评论