美文网首页
iOS 捋清楚block - 2

iOS 捋清楚block - 2

作者: 水煮杰尼龟 | 来源:发表于2021-04-01 21:35 被阅读0次
    接上篇来看看__blcok

    我们一般用来解决在block里无法修改局部变量的问题,而且它不能修饰全局变量,静态变量(static)
    首先如下来看看修饰基本数据类型

    __block int a = 10;
            void (^mallocBlock)(void) = ^void {
                NSLog(@"%d",a);
            };
    
    转变cpp代码
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
            void (*mallocBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    
    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
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_5c_vc7szrdj0xj63p71bn1fz8n80000gn_T_main_ae0187_mi_0,(a->__forwarding->a));
            }
    
    • 可以看到当用__block修饰基本数据类型的时候,会将其包装成__Block_byref_a_0对象,__Block_byref_a_0里的*__forwarding&a__Block_byref_a_0对象的地址,也就是指向的是它自己,里面的a则为基本数据类型的值10,然后这个对象被捕获进block里,这里是地址捕获,看到传的是&a,那么就自然可以拿到地址改变其内部a的值了。
    • 当我们访问a的时候,是怎么访问的呢,不论是在block外还是在block内,都是通过a->__forwarding->a,这里可能疑问为啥不直接a->a呢,实际上在block里访问,这两种方式没什么区别。这里是因为在block复制到堆上的时候,由于是__block修饰,会进行一个_Block_byref_copy操作,这里面会将__Block_byref_a_0对象也copy到堆区,那么我们在block内部修改了__Block_byref_a_0里的a=20,实际上访问的是堆区的__Block_byref_a_0对象的a,但是我们再在外面访问栈区的__Block_byref_a_0对象的a,按理说应该还是10,因为一个在栈区一个在堆区,所以为了保证两边访问到的都是改过后的20_Block_byref_copy里还有一个将栈区block__forwarding指向堆区__Block_byref_a_0对象的操作,那么栈区就可以通过它自己的__forwarding来访问到堆区__Block_byref_a_0对象的a了也就是20,这样就达到了我们想要的修改效果了。

    这部分源码可以自己去看看·

    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; // 堆区的block的forwarding 指向自己
            src->forwarding = copy;  // 栈区的block的forwarding 指向堆区的block
            copy->size = src->size;
            printf("_Block_byref_copy 111\n");
            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;
    
                if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                    struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                    struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                    copy3->layout = src3->layout;
                }
    
                (*src2->byref_keep)(copy, src);
            }
            else {
                // Bitwise copy.
                // This copy includes Block_byref_3, if any.
                printf("_Block_byref_copy 222\n");
                ///内存拷贝
                memmove(copy+1, src+1, src->size - sizeof(*src));
            }
        }
        // already copied to heap
        else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
            printf("_Block_byref_copy 333\n");
            latching_incr_int(&src->forwarding->flags);
        }
        
        return src->forwarding;
    }
    
    再来看看__block修饰对象
    struct __Block_byref_obj_0 {
      void *__isa;  //8
    __Block_byref_obj_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
     NSObject *__strong 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;
      }
    };
    
    __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)objc_getClass("NSObject"), sel_registerName("new"))};
    
    static void __Block_byref_id_object_copy_131(void *dst, void *src) {
     _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    }
    static void __Block_byref_id_object_dispose_131(void *src) {
     _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    }
    
    • 上一篇最后有讲ARC下,在栈block进行copy到堆上的时候,并没有走_Block_object_assign,这里__block修饰还是走这个函数的。而_Block_object_assign内部判断走的_Block_byref_copy.。
    • 先继续看上面的cpp代码,可以看到包装成的新对象里多了2个函数指针,__Block_byref_id_object_copy__Block_byref_id_object_dispose 追踪到最后发现是_Block_object_assign_Block_object_dispose,昂,咋又是它,不过参数发生了变化,经过源码追踪发现ARC_Block_object_assign并不会走2次,于是有了下面的探索。
    • 那么跟上一篇一样,转成.ll中间文件来看看
    main函数里的部分代码
    %14 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %6, i32 0, i32 4
      store i8* bitcast (void (i8*, i8*)* @__Block_byref_object_copy_ to i8*), i8** %14, align 8
      %15 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %6, i32 0, i32 5
      store i8* bitcast (void (i8*)* @__Block_byref_object_dispose_ to i8*), i8** %15, align 8
    
    两个主要函数
    define internal void @__Block_byref_object_copy_(i8* %0, i8* %1) #3 {
      %3 = alloca i8*, align 8
      %4 = alloca i8*, align 8
      store i8* %0, i8** %3, align 8
      store i8* %1, i8** %4, align 8
      %5 = load i8*, i8** %3, align 8
      %6 = bitcast i8* %5 to %struct.__block_byref_obj*
      %7 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %6, i32 0, i32 6
      %8 = load i8*, i8** %4, align 8
      %9 = bitcast i8* %8 to %struct.__block_byref_obj*
      %10 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %9, i32 0, i32 6
      %11 = load %0*, %0** %10, align 8
      store %0* null, %0** %7, align 8
      %12 = bitcast %0** %7 to i8**
      %13 = bitcast %0* %11 to i8*
      call void @llvm.objc.storeStrong(i8** %12, i8* %13) #2
      %14 = bitcast %0** %10 to i8**
      call void @llvm.objc.storeStrong(i8** %14, i8* null) #2
      ret void
    }
    
    define internal void @__Block_byref_object_dispose_(i8* %0) #3 {
      %2 = alloca i8*, align 8
      store i8* %0, i8** %2, align 8
      %3 = load i8*, i8** %2, align 8
      %4 = bitcast i8* %3 to %struct.__block_byref_obj*
      %5 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %4, i32 0, i32 6
      %6 = bitcast %0** %5 to i8**
      call void @llvm.objc.storeStrong(i8** %6, i8* null) #2
      ret void
    }
    

    那么大致可以看出这里存了这2个函数,__Block_byref_object_copy___Block_byref_object_dispose_ ,通过objcstoreStrong方法来强引用以及释放的操作,那么这个是什么时候调用的呢,可以在_Block_byref_copy源码里找到蛛丝马迹。

    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;
    
                if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                    struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                    struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                    copy3->layout = src3->layout;
                }
                printf("_Block_byref_copy 444\n");
                (*src2->byref_keep)(copy, src);
    

    最后调用了(*src2->byref_keep)(copy, src);,这里的byref_keepbyref_destroy对应的应该就是__Block_byref_obj_0里的那2函数指针了。我们在这里打上断点print看看这个byref_keep

    image.png
    算是验证了我的猜测。
    因为默认我们申明的局部变量就是strong修饰的,我们再来看看weak修饰,并转换.ll文件
    NSObject *obj = [NSObject new];
            __block __weak NSObject *weakObj = obj;  
            void (^mallocBlock)(void) = ^void {
                NSLog(@"%@",weakObj);
            };
    转换.ll 再看看__Block_byref_object_copy_和__Block_byref_object_dispose_
    
    define internal void @__Block_byref_object_copy_(i8* %0, i8* %1) #3 {
      %3 = alloca i8*, align 8
      %4 = alloca i8*, align 8
      store i8* %0, i8** %3, align 8
      store i8* %1, i8** %4, align 8
      %5 = load i8*, i8** %3, align 8
      %6 = bitcast i8* %5 to %struct.__block_byref_weakObj*
      %7 = getelementptr inbounds %struct.__block_byref_weakObj, %struct.__block_byref_weakObj* %6, i32 0, i32 6
      %8 = load i8*, i8** %4, align 8
      %9 = bitcast i8* %8 to %struct.__block_byref_weakObj*
      %10 = getelementptr inbounds %struct.__block_byref_weakObj, %struct.__block_byref_weakObj* %9, i32 0, i32 6
      %11 = bitcast %0** %7 to i8**
      %12 = bitcast %0** %10 to i8**
      call void @llvm.objc.moveWeak(i8** %11, i8** %12) #2
      ret void
    }
    define internal void @__Block_byref_object_dispose_(i8* %0) #3 {
      %2 = alloca i8*, align 8
      store i8* %0, i8** %2, align 8
      %3 = load i8*, i8** %2, align 8
      %4 = bitcast i8* %3 to %struct.__block_byref_weakObj*
      %5 = getelementptr inbounds %struct.__block_byref_weakObj, %struct.__block_byref_weakObj* %4, i32 0, i32 6
      %6 = bitcast %0** %5 to i8**
      call void @llvm.objc.destroyWeak(i8** %6) #2
      ret void
    }
    

    这时候你会发现__Block_byref_object_copy___Block_byref_object_dispose_一个是调用的moveWeak,一个是destroyWeak,进行弱引用的操作。

    所以在栈block进行copy到堆上时,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,再而根据传进去的flags调用_Block_byref_copy,最终调用__Block_byref_object_copy_会根据修饰符对__block对象进行强/弱引用操作。
    实际看看
    image.png
    image.png
    可以看出来,确实__weak没有增加obj的引用计数,而且不管使用__weak还是__strongblock对新生成的__Block_byref对象(blockObj)都是强引用,这里要注意block前block后blockObj不是一个东西,因为会被copy到堆。

    但是这里发现,现在ARC下实际上obj引用计数的增加,在加上__block后就已经发生了,而好像不是像前面分析的那样,在block复制到堆的时候进行操作,我猜测在block复制到堆的时候应该只是改变了引用obj的对象,看.ll文件里的做法,像是把栈区__Block_byref对象对obj的引用取消了,再把堆区的引用加上,所以没有发生改变,因为反正经过block复制到堆后,通过__forwarding都是访问到堆上的__Block_byref里的obj。-------昂,希望有大佬帮我捋一下。

    循环引用

    前面捋的差不多了,那么就来看看实际一点的东西。

    • 你强引用我,我强引用你, 或者多个对象绕成了一个环 即产生了循环引用。
    • 这篇文章只看一下block的循环引用,如果看了上一篇应该已经很清楚了,总结过只有堆区的block才持有对象
    • 那么第一步确认一下是否堆区的block,看看是否访问了auto对象。然后看看这个auto对象是否和这个block有无直接或间接的关系不就行了吗。
    先写一个循环引用
    @interface Person : NSObject
    
    @property(nonatomic,copy)void (^mallocBlock)(void);
    @end
    @implementation Person
    
    - (void)dealloc
    {
        NSLog(@"dealloc -- %@",self);
    }
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            void (^mallocBlock)(void);
            {
                Person *person = [Person new];
                mallocBlock = ^void {
                    NSLog(@"%@",person);
                };
                person.mallocBlock = mallocBlock;
            }
          
        }
        return 0;
    }
    
    • 如上这样,mallocBlock访问了auto变量person,再进行了赋值,从而从栈block复制到,所以其对person进行了进行了强引用,然后person里又有个成员变量对block进行了强引用,这样就循环引用了,最终结果person无法释放,dealloc没有走。
      要解决循环引用,打断其中的一条线就行了
    • 比如让blockperson是弱引用(__weak typeof(Person) *weakPerson = person;)
    • 或者personblock是弱引用(@property(nonatomic,weak)void (^mallocBlock)(void);) 这种一般是不会这么做的,会有一些提前释放的问题。
    • 或者将person当做参数传入block,(void (^mallocBlock)(Person *per);)
    • 或者用__block修饰对象,但是需要在block里用完了手动置为nil,缺点是这个block必须被调用
    • 或者使用__unsafe_unretained修饰符,但是如它的名字一样,是unsafe不安全的,如下:
    __unsafe_unretained Person *unsafePer = nil;
            Person *person = [Person new];
            unsafePer = person;
            void (^mallocBlock)(void) = ^{
                NSLog(@"%@",unsafePer);
            };
            person.mallocBlock = mallocBlock;
            person = nil;
            NSLog(@"%p ",unsafePer);
            [unsafePer sayHello];
    
    image.png
    可以看出person 已经释放了,但是unsafePer还是指向原来的地址,然后就因为访问了野指针崩溃了。
    所以我基本都是__weak来解决block的循环引用
    我们平常可能大部分都是由于堆block内部访问self或者self.xxx或者[self xxx],然后selfblock直接或者间接也有引用,形成了一个环,导致了循环引用。
    这里self也是一个局部变量,通过方法参数传入方法内,因为iOS里的方法默认会有俩参数self_cmd,所以在block里访问self也是访问了局部变量,如果blockcopy行为,就会产生引用关系。
    image.png
    举几个常见的栗子来看看
    @interface TestManager : NSObject
    +(instancetype)sharedManager;
    
    @property(nonatomic,copy)void(^block)(void);
    
    -(void)requestData:(void(^)(void))block;
    
    +(void)requestData:(void(^)(void))block;
    @end
    @implementation TestManager
    +(instancetype)sharedManager {
        static dispatch_once_t onceToken;
        static TestManager *instance;
        dispatch_once(&onceToken, ^{
            instance = [[TestManager alloc] init];
        });
        return instance;
    }
    -(void)requestData:(void(^)(void))block{
        NSLog(@"%@",[block class]);
        !block?:block();
    }
    
    +(void)requestData:(void(^)(void))block{
        NSLog(@"%@",[block class]);
        !block?:block();
    }
    @end
    
    @interface NextViewController ()
    @property(nonatomic,strong)TestManager *testManager;
    @end
    
    @implementation NextViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.name = @"海贼:王路飞";
        
        void (^testBlock)(void) = ^{
            self.name = @"1. 海贼:王路飞 是一定会成为海贼王的";
        };
    //    [TestManager sharedManager].block = ^{
    //        self.name = @"2. 海贼:王路飞 是一定会成为海贼王的";
    //    };
    //    [[TestManager sharedManager]requestData:^{
    //        self.name = @"3. 海贼:王路飞 是一定会成为海贼王的";
    //    }];
    //    [TestManager requestData:^{
    //        self.name = @"4. 海贼:王路飞 是一定会成为海贼王的";
    //    }];
    //    self.testManager = [TestManager new];
    //    [self.testManager requestData:^{
    //        self.name = @"5. 海贼:王路飞 是一定会成为海贼王的";
    //    }];
    //    self.testManager = [TestManager new];
    //    self.testManager.block = ^{
    //        self.name = @"6. 海贼:王路飞 是一定会成为海贼王的";
    //    };
     
    }
    
    @end
    

    ARC下你能清晰的知道上面6种情况,哪些对self强引用了(注意区分栈block堆block,以下是针对堆block),哪些会循环引用吗。

    1. 强引用了,不会循环引用,因为testBlock没有跟self产生关系,没有形成环,会释放
    2. 强引用了,不会循环引用,但是单例一直不释放block,而block强引用self导致self无法释放
    3. 未强引用,不会循环引用,block仍然是栈block,会释放,因为单例并不持有block
    4. 未强引用,不会循环引用,block仍然是栈block,会释放
    5. 未强引用,不会循环引用,block仍然是栈block,会释放
    6. 强引用了,会循环引用,self->testManager->block->self,无法释放

    搞清楚了我相信其他情况也可以清晰了。

    最后带一下__strong__weak配合使用场景

    比如从一个ViewController present过来,在第二个NextViewController,定义一个如下block

    __weak typeof(self) weakSelf = self;
        self.testBlock = ^{
            ///  现实中可能是异步网络请求
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"我是%@",weakSelf.name);
            });
        };
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        self.testBlock();
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    

    __weak修饰,点击返回上一个ViewController,延时2秒打印,结果如下

    image.png
    这样就不是我们想要的结果,name我们没有拿到了,因为weakSelf已经mo得了。
    而我们block内部加上__strong typeof(weakSelf) strongSelf = weakSelf;再看看
    image.png
    可以看到,是用完了,self才释放的。

    关于block就捋这么多吧,END

    相关文章

      网友评论

          本文标题:iOS 捋清楚block - 2

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