iOS Block

作者: 一字码 | 来源:发表于2021-09-29 15:15 被阅读0次

    block的本质
    block本质上也是一个oc对象,他内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。block其实也是NSObject的子类
    block的类型
    共有三种类型的block分别是:全局的,栈上的,堆上的

    __NSGlobalBlock__ ( _NSConcreteGlobalBlock )//直到程序结束才会被回收
    __NSStackBlock__ ( _NSConcreteStackBlock )//栈中的内存由系统自动分配和释放,作用域执行完毕之后就会被立即释放
    __NSMallocBlock__ ( _NSConcreteMallocBlock )//平时编码过程中最常使用到的。存放在堆中需要我们自己进行内存管理。
    

    block是如何定义其类型

    block是如何定义其类型
    三种block的创建
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // 1. 内部没有调用外部变量的block
            void (^block1)(void) = ^{
                NSLog(@"Hello");
            };
            // 2. 内部调用外部变量的block
            int a = 10;
            void (^block2)(void) = ^{
                NSLog(@"Hello - %d",a);
            };
           // 3. 直接调用的block的class
            NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
                NSLog(@"%d",a);
            } class]);
        }
        return 0;
    }
    

    打印结果:

    2021-09-28 11:30:11.568128+0800 OC-Review[44456:6774536] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
    

    block的变量捕获

    局部变量

    • auto变量 - 值传递
    • static变量 - 指针传递
      全局变量
    • 直接访问
    1. 一旦block中捕获的变量为对象类型,block结构体中的__main_block_desc_0会出两个参数copy和dispose。因为访问的是个对象,block希望拥有这个对象,就需要对对象进行引用,也就是进行内存管理的操作。比如说对对象进行retarn操作,因此一旦block捕获的变量是对象类型就会会自动生成copy和dispose来对内部引用的对象进行内存管理。
    2. 当block内部访问了对象类型的auto变量时,如果block是在栈上,block内部不会对person产生强引用。不论block结构体内部的变量是__strong修饰还是__weak修饰,都不会对变量产生强引用。
    3. 如果block被拷贝到堆上。copy函数会调用_Block_object_assign函数,根据auto变量的修饰符(__strong,__weak,unsafe_unretained)做出相应的操作,形成强引用或者弱引用
    4. 如果block从堆中移除,dispose函数会调用_Block_object_dispose函数,自动释放引用的auto变量。

    block内修改变量的值

    局部变量

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            int a = 5;
            void(^blk)(void) = ^{
                a = 10;
            };
    
        }
        return 0;
    }
    

    block不能修改外部的局部变量,age是在main函数内部声明的,说明age的内存存在于main函数的栈空间内部,但是block内部的代码在__main_block_func_0函数内部。__main_block_func_0函数内部无法访问age变量的内存空间,两个函数的栈空间不一样,__main_block_func_0内部拿到的age是block结构体内部的age,因此无法在__main_block_func_0函数内部去修改main函数内部的变量。
    在最新的xcode中,已经对这种情况进行了错误代码提示


    错误代码提示图

    修改方式
    方式一:a使用static修饰。

    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            
            static int a = 5;
            void(^blk)(void) = ^{
                a = 10;
            };
            blk();
        }
        return 0;
    }
    -----
    2021-09-29 14:40:10.580041+0800 OC-Review[71142:7289047] 修改前5
    2021-09-29 14:40:10.581801+0800 OC-Review[71142:7289047] 修改后10
    

    方式二:__block

    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            
            __block int a = 5;
            void(^blk)(void) = ^{
                a = 10;
            };
            NSLog(@"修改前%d",a);
            blk();
            NSLog(@"修改后%d",a);
        }
        return 0;
    }
    2021-09-29 14:42:12.904192+0800 OC-Review[71207:7291575] 修改前5
    2021-09-29 14:42:12.905207+0800 OC-Review[71207:7291575] 修改后10
    

    查看源码:

    
    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     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) {
        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
    
                (a->__forwarding->a) = 10;
    }
    

    首先被__block修饰的a变量声明变为名为age的__Block_byref_a_0结构体,也就是说加上__block修饰的话捕获到的block内的变量为__Block_byref_a_0类型的结构体。

    __isa指针 :__Block_byref_age_0中也有isa指针也就是说__Block_byref_age_0本质也一个对象。
    __forwarding :__forwarding是__Block_byref_age_0结构体类型的,并且__forwarding存储的值为(__Block_byref_age_0 *)&age,即结构体自己的内存地址。
    __flags :0
    __size :sizeof(__Block_byref_age_0)即__Block_byref_age_0所占用的内存空间。
    a :真正存储变量的地方,这里存储局部变量10。
    

    __block将变量包装成对象,然后在把age封装在结构体里面,block内部存储的变量为结构体指针,也就可以通过指针找到内存地址进而修改变量的值。

    循环引用

    1. 情景一
        @autoreleasepool {
            Person *person = [[Person alloc] init];
            person.age = 10;
            person.block = ^{
                NSLog(@"%d",person.age);
            };
        }
        NSLog(@"大括号结束啦");
        return 0;
    } 
    

    可以发现大括号结束之后,person依然没有被释放,产生了循环引用
    Person对象和block对象相互之间产生了强引用,导致双方都不会被释放,进而造成内存泄漏


    循环引用示意
    1. 情景二
    #import "Person.h"
    
    @implementation Person
    
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            self.block = ^{
                NSLog(@"%@",[self class]);
            };
        }
        return self;
    }
    

    这是开发中经常遇到的一个场景。之前我们说过block会捕获局部,上面的OC函数调用转化为runtime代码为
    objc_msgSend(self,@selector(init)) 在OC的方法中 有2个隐藏参数 self和_cmd 这2个参数作为函数的形参,在方法作用域中属于局部变量 , 所以在block中使用self就满足之前提到的 block会捕获局部变量
    查看源码

    struct __Person__init_block_impl_0 {
      struct __block_impl impl;
      struct __Person__init_block_desc_0* Desc;
      Person *self;
      __Person__init_block_impl_0(void *fp, struct __Person__init_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __Person__init_block_func_0(struct __Person__init_block_impl_0 *__cself) {
      Person *self = __cself->self; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_sr_m_cfkwyx2h56vh4_kf65_vw40000gn_T_Person_3f840b_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
    }
    

    这里可以看到 __Person__init_block_impl_0结构体中 创建了一个Person *self的强指针 指向init方法中self
    指针所指向的person对象,使person引用计数+1 而person对block也有一个强引用。这里就造成了循环引用。
    解决方法
    首先为了能随时执行block,我们肯定希望person对block对强引用,而block内部对person的引用为弱引用最好。
    使用__weak 和 __unsafe_unretained修饰符可以解决循环引用的问题。

    • __weak 和 __unsafe_unretained的区别
      a __weak不会产生强引用,指向的对象销毁时,会自动将指针置为nil。因此一般通过__weak来解决问题。
      b __unsafe_unretained不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变。会造成野指针问题
    1. 情景三
      在block中调用super也会造成循环引用 :
    #import "Person.h"
    @implementation Person
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            self.block = ^{
                [super init];
            };
        }
        return self;
    }
    

    查看源码

    static void __Person__init_block_func_0(struct __Person__init_block_impl_0 *__cself) {
      ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"));
    }
    

    当使用[self class]时,会调用objc_msgSend函数,第一个参数receiver就是self,而第二个参数,要先找到self所在的这个class的方法列表

    当使用[super class]时,会调用objc_msgSendSuper函数,此时会先构造一个__rw_objc_super的结构体作为objc_msgSendSuper的第一个参数。 该结构体第一个成员变量receiver仍然是self,而第二个成员变量super_class即是所在类的父类

    struct __rw_objc_super {
        struct objc_object *object;
        struct objc_object *superClass;
        __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
    };
    
    runtime对外暴露的类型为:
    
    struct objc_super {
        __unsafe_unretained _Nonnull id receiver;
    
        __unsafe_unretained _Nonnull Class super_class;
    };
    
    结构体第一个成员receiver 就代表方法的接受者  第二个成员代表方法接受者的父类
    

    所以

    self.block = ^{
        [super init];
    };
    转化后是:
    self.block = ^{
            struct objc_super  superInfo = {
                .receiver = self,
                .super_class = class_getSuperclass(objc_getClass("Person")),
            };
    
            ((Class(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superInfo,@selector(init));
        };
    

    可以很明显的看到问题,block强引用了self,而self也强持有了这个block
    解决方法:

    #import "Person.h"
    @implementation Person
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            __weak __typeof(self) weakSelf = self;
            self.block = ^{
                [super init];
            };
        }
        return self;
    }
    
    
    
    

    相关文章

      网友评论

          本文标题:iOS Block

          本文链接:https://www.haomeiwen.com/subject/zqhjnltx.html