美文网首页workLanguageiOS精品文章
Block 是如何实现的?如何避免循环引用?

Block 是如何实现的?如何避免循环引用?

作者: Lefe | 来源:发表于2017-06-25 18:15 被阅读584次

    本文是作者Lefe所创,转载请注明出处,如果你在阅读的时候发现问题欢迎一起讨论。本文会不断更新。

    说明:

    使用 Block 的时候,我们通常会有以下几点疑问,我们带着这种疑问来阅读本文,本文难免会有遗漏或者错误,望读者朋友们提出来。Lefe 在使用 Block 的时候主要遇到了以下问题:

    • 难道只要使用了 self 就需要使用 __weak 来避免循环引用吗?
    • 为避免循环应用,为什么使用了 __weak 还需要使用 __strong ?
    • 为什么使用 __block 修改变量后就可以在 Block 内部修改它的值?
    • Block 什么时候释放呢?它是如何进行内存管理的?
    • Block 为什么要用 copy?
    • 为什么有些 Block 即使捕获了 self 也不会产生循环引用?
    • 自动变量(局部变量)如何被 Block 捕获的,对象又是如何被 Block 捕获的?

    带着这些问题,我们一块来揭开 Block 的真实面目,本文篇幅较长,可以分段阅读,建议读者耐心阅读,很枯燥的,如果能动手实现以下,会有趣很多。在阅读之前我们先了解下 Clang

    Clang

    本文主要用到了 Clang,那什么是 Clang 呢?它是 Xcode 默认的编译器。更多关于Clang 可以参考 本文 。这里我们主要用 Clang 把 Block 的实现转换成 C++ ,其实和 C 差不多,除了构造函数外。

    打开 shell,进入 Lefe 的测试项目中,输入:

    clang -rewrite-objc HelloLefe.m,这是会在当前目录下生产一个对应的 HelloLefe.cpp 文件,打开它就对了。截个图看看,别光看美女。

    屏幕快照 2017-06-25 上午9.36.43.png

    内存分配

    在阅读下文前,我们需要对内存分配有一定的了解

    • 栈: 在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
    • 堆: 就是那些由 new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个 release。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。这部分内存需要程序员手动释放。当然使用 ARC 后我们不需要处理。
    • 全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。这部分数据不需要程序员手动释放,他会随着程序的消失二释放。
    • 常量存储区: 这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。
    屏幕快照 2017-06-25 上午10.20.09.png

    关于内存分配的阅读 本文

    Block 是如何实现的

    掌握了 Clange 的基本使用,那我们就看看 Block 究竟做了什么。从一个简单的例子开始。

    Lefe 在 HelloLefe.m 文件中,写了一个 Block,使用 clang -rewrite-objc HelloLefe.m 转换,转换后可以看到 Block 的具体实现。

    - (void)lefeTestComplete
    {
        void (^complete)(void) = ^(void){
            NSLog(@"Block\n");
        };
        complete();
    }
    
    @end
    
    

    转换后的代码如下:

    • 发现每个结构体的生成都会是一个又长又臭的名字,它会使用类名 HelloLefe 和方法名 lefeTestComplete等生成一个结构体,也就是 block 的实现,它是一个很重要的结构体。它主要包含了2个结构体和一个构造方法。
    struct __HelloLefe__lefeTestComplete_block_impl_0 {
        struct __block_impl impl;
        struct __HelloLefe__lefeTestComplete_block_desc_0* Desc;
        
        // 构造方法
        __HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, int flags=0) {
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
        }
    };
    
    
    • __block_impl结构体,是 __HelloLefe__lefeTestComplete_block_impl_0 结构体的第一个变量
    struct __block_impl {
      void *isa;  // isa 指针,block 其实也是一个 OC 对象,每个类都有一个指向其实例的一个指针
      int Flags;
      int Reserved;
      void *FuncPtr; // 相当于 block 中要执行的函数的指针
    };
    
    • 结构体 __HelloLefe__lefeTestComplete_block_impl_0 的第二个变量
    static struct __HelloLefe__lefeTestComplete_block_desc_0 {
        size_t reserved;
        size_t Block_size;
    } __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0)};
    
    
    • 同样,以类名和方法名生成一个函数,这个函数也就是 ^(void){ NSLog(@"Block\n"); }; 转换后的结果,__cself 和 OC 中的 self 差不多一个意思,它就是 __HelloLefe__lefeTestComplete_block_impl_0,是指向结构体 __HelloLefe__lefeTestComplete_block_impl_0的指针
    static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
        
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__cv2l59cn5x90wh88hrkp35x80000gp_T_HelloLefe_b006ae_mi_0);
    }
    
    • 主函数,编译器编译后会自动给每个方法添加两个参数 self_cmd
    static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {
    
         // 这段代码是对 void (^complete)(void) = ^(void){ NSLog(@"Block\n");}; 的转换
         
        void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA));
        
        // 这段代码相当于对 complete(); 的转换,
        ((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
    }
    
    

    从上面的转换过程可以看出,声明一个 block 首先调用结构体 __HelloLefe__lefeTestComplete_block_impl_0 的构造函数,得到一个 IMP,相当于 OC 中的 IPM,它保存了这个 block 所需要的信息,当调用 block 的时候,直接调用 IPM-> FuncPtr。

    到这里相信读者还是对 Block 的实现很陌生,很正常,坚持阅读一会,试试看。头脑中试着把 Block 就当做是一个 NSObject 对象。

    Block 捕获变量

    记得刚接触 Block 的时候,只是隐约听到 Block 可以自动捕获 Block 中使用的变量。是的,Block 可以捕获它所用到的自动变量或对象,但是它只是捕获了它所用到的变量,其他用不到的变量它并不会捕获,这里就是引起循环引用的一个重点,下文会详细将到。对应全局变量 Block 并不或去捕获。

    以上的 block 的实现多少有点眉目了,那么 block 是如何捕获变量的,我把将要转换的代码改为:

    - (void)lefeTestComplete
    {
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d\n";
        
        void (^complete)(void) = ^(void){
            printf(fmt, val);
        };
        complete();
    }
    

    转换后的代码如下,观察的实现发现多了
    const char *fmt; int val; 这就是 block 捕获的变量,但我们发现 dmy 这个变量并没有捕获,因为在 block 中压根就没使用。结构体的构造方法也需要传入捕获的变量来构造结构体。

    struct __HelloLefe__lefeTestComplete_block_impl_0 {
      struct __block_impl impl;
      struct __HelloLefe__lefeTestComplete_block_desc_0* Desc;
      const char *fmt;
      int val;
      __HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
      const char *fmt = __cself->fmt; // bound by copy
      int val = __cself->val; // bound by copy
    
            printf(fmt, val);
        }
    
    static struct __HelloLefe__lefeTestComplete_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0)};
    
    static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d\n";
    
        void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA, fmt, val));
        ((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
    }
    
    

    修改 Block 中的捕获的变量

    上面的例子中,并不能在 Block 中修改所捕获的变量,那么如何修改 Block 中所捕获的变量呢?可以使用 __block。如果修改 Block 中的变量,编译器会直接报错。比如:

    - (void)leftTestBlock
    {
        int age = 0;
        void (^block)(void) = ^{
            age = 10;
        };
    }
    

    这段代码编译器直接会报错,可能有些同学会说直接用 __block,但是为什么使用 __block 就可以呢?再看一下下面的代码:

    // 全局变量
    int global_val = 1;
    // 全局静态变量
    static int static_global_val = 2;
    
    - (void)lefeTestComplete
    {
        // 静态变量
        static int static_val = 3;
        void (^complete)(void) = ^{
            global_val *= 1;
            static_global_val *= 2;
            static_val *= 3;
        };
        complete();
    }
    
    

    这段代码是没有任何问题的,它可以正常的编译通过,它没有使用 __block。详细大部分的同学读到这里都会有一个疑惑,这是为什么呢?我们不妨来看一下他的具体实现。发现全局变量并没有被捕获到 __HelloLefe__lefeTestComplete_block_impl_0 中,仅仅捕获了 static_val,想想也是,全局变量直接可以获取到,为什么还要捕获他呢?但捕获静态变量和以前不一样的是它捕获的是一个指针 int *static_val; 哦,对啊,直接使用它的指针就可以修改它了,但是为什么普通变量不可以使用其指针呢?因为一个 block 必须存在即使它所捕获变量的作用域释放掉,作用域释放掉后其变量也随之销毁,这意味着 block 就不能访问所捕获的自动变量了,如何修改?但是静态变量和全局变量不会释放啊!

    int global_val = 1;
    static int static_global_val = 2;
    
    
    struct __HelloLefe__lefeTestComplete_block_impl_0 {
      struct __block_impl impl;
      struct __HelloLefe__lefeTestComplete_block_desc_0* Desc;
      int *static_val;
      __HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
      int *static_val = __cself->static_val; // bound by copy
    
            global_val *= 1;
            static_global_val *= 2;
            (*static_val) *= 3;
        }
    
    static struct __HelloLefe__lefeTestComplete_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0)};
    
    static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {
        static int static_val = 3;
        void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA, &static_val));
        ((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
    }
    

    通过上面的学习我们可以了解到,修改 Block 中捕获的变量,可以使用一下几种方式:

    • 通过 __block 修饰变量
    • 使用静态变量
    • 使用全局变量、全局静态变量
    • 使用指针,静态变量就是通过指针来修改它的值的

    读到这里,相信你已经明白如何捕获自动变量了,也知道如何修改 Block 中所捕获的变量了,难道你不想知道为啥使用 __block 修饰后就可以修改 Block 中所捕获的变量吗?哈哈,坚持一下!

    __block 究竟是如何实现的呢?

    __block 如同 static, auto 等修饰符,主要作用是觉得某一变量该保存到哪里。看看它是如何实现的。把下面的代码转化:

    - (void)lefeTestComplete
    {
        __block int val = 10;
        void (^complete)(void) = ^{val = 1;};
        
        complete();
    }
    

    转换后发现多了很多内容,为什么使用 __block 需要增加这么多代码呢?Lefe 表示很好奇。当使用 __block 变量时,会将 __block 变量从栈拷贝的堆上。当多个 block 共用一个 __block 变量时,__block 变量有一个计数器来记录有多少个 block 引用了它,block 释放掉的时候,__block 变量的引用计数将减1,直到为0时,__block 变量才会释放。

    • __block 转换后的结构体
    struct __Block_byref_val_0 {
      void *__isa;
      // __forwarding 主要用来获取 __block 变量的值,它的指向会根据 block 所处的内存位置不同,所指向的也不同。
      __Block_byref_val_0 *__forwarding; 
      int __flags;
      int __size;
      int val; // 值
    };
    
    • Block 的实现,发现多了 __Block_byref_val_0,它就是一个 block 变量
    struct __HelloLefe__lefeTestComplete_block_impl_0 {
      struct __block_impl impl;
      struct __HelloLefe__lefeTestComplete_block_desc_0* Desc;
      __Block_byref_val_0 *val; // by ref
      
      __HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • ^{val = 1;} 的实现被转换后:
    static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
      __Block_byref_val_0 *val = __cself->val; // bound by ref
    (val->__forwarding->val) = 1;
    }
    
    static void __HelloLefe__lefeTestComplete_block_copy_0(struct __HelloLefe__lefeTestComplete_block_impl_0*dst, struct __HelloLefe__lefeTestComplete_block_impl_0*src) {
    _Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    
    static void __HelloLefe__lefeTestComplete_block_dispose_0(struct __HelloLefe__lefeTestComplete_block_impl_0*src) {
    _Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    
    static struct __HelloLefe__lefeTestComplete_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __HelloLefe__lefeTestComplete_block_impl_0*, struct __HelloLefe__lefeTestComplete_block_impl_0*);
      void (*dispose)(struct __HelloLefe__lefeTestComplete_block_impl_0*);
    } __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0), __HelloLefe__lefeTestComplete_block_copy_0, __HelloLefe__lefeTestComplete_block_dispose_0};
    
    • __block int val = 10; 转化后的代码如下,转换后变成一个结构体,并且初始化的时候值为 10
    static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {
        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
            (void*)0,
            (__Block_byref_val_0 *)&val, 
            0, 
            sizeof(__Block_byref_val_0), 
            10
        };
        
        void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    
        ((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
    }
    
    

    Block 的内存段

    下图主要说明 block 主要保存在栈,堆和数据区。


    屏幕快照 2017-06-23 下午10.06.35.png

    那什么样的变量分派到栈中、堆中或数据区呢?

    • 1、当 block 字面量写在全局作用域时,即为 global block;
    • 2、当 block 字面量不获取任何外部变量时,即为 global block;

    除了以上2中情况外,其他的都分派到栈区,分派到栈区的 block ,当作用域结束后,它所捕获的变量也就释放掉了。为了解决这个问题,Blocks 提供了一个函数,可以把栈上的 block 拷贝到堆上。这样即使作用域结束也不会使 block 被释放。被 copy 后的 block ,它的 isa 指针就会变成 impl.isa = &_NSConcreteMallocBlock。Block 也就成了堆上的 Block。

    使用 ARC 后,编译器会自动把栈中的 block 复制到堆上。

    typedef int (^blk_t)(int);
    blk_t func(int rate) {
        return ^(int count){return rate * count;}; 
    }
    
    blk_t func(int rate) {
        blk_t tmp = &__func_block_impl_0(
        __func_block_func_0, &__func_block_desc_0_DATA, rate);
        // 直接复制了一个 block,也就是拷贝到了堆上,即使当这个函数结束后,这个 block 任然不会被销毁 
        tmp = objc_retainBlock(tmp);
        return objc_autoreleaseReturnValue(tmp); }
    

    但是不是所有的时候,编译器都会执行 copy 操作的,以下情况编译器不会执行 copy 操作的

    • Block 作为一个参数传递给一个函数或方法时。

    举个例子,下面这个例子会直接 crash,所以需要给数组中的 block 要执行 copy 操作

    + (id)getBlockArray {
        int val = 10;
        return [[NSArray alloc] initWithObjects:
                ^{NSLog(@"blk0:%d", val);},
                ^{NSLog(@"blk1:%d", val);},
                nil
                ];
    }
    
    + (void)lefeTestComplete
    {
        id obj = [self getBlockArray];
        typedef void (^blk_t)(void);
        blk_t blk = (blk_t)[obj objectAtIndex:0];
        blk();
    }
    

    但是使用系统提供的方法不需要执行 copy 操作,比如 GCD,因为在函数内部自己已经实现了 copy。

    捕获对象:

    前面都在讲捕获的是基本类型的变量,那么 Block 是如何捕获对象的呢?下面的例子中的数组中,打印结果为:

    Array: (
        Lefe,
        Wang,
        Su,
        Yan
    )
    

    说明数组没有被释放掉。Block 内部会强引用对象,直到 Block 被释放,被引用的对象也将被释放。

    @implementation HelloLefe
    
    LefeBlock block;
    
    + (void)lefeTestComplete
    {
        NSMutableArray *array = [NSMutableArray array];
        block = ^(NSString *name){
            [array addObject:name];
            
            NSLog(@"Array: %@", array);
        };
    }
    
    + (void)addObject
    {
        block(@"Lefe");
        block(@"Wang");
        block(@"Su");
        block(@"Yan");
        
    }
    
    @end
    
    

    循环引用一:

    - (void)testMemoryLeakCase1
    {
        self.logId = @"Hello logId";
        
        /**
         这种情况最容易发现,因为编译器会自动提示出现循环引用
         Why?
         self(SecondViewController)持有了 finshBlock,你可以把它当作一个普通的属性,是强引用
         而 finshBlock 又引用了 self,这样就形成了一个闭环。
         How?
         既然是因为出现了闭环,我们只需要打破这层闭环就可以,让 finshBlock 持有一个弱引用,这样 self(SecondViewController)持有了 finshBlock,但是 finshBlock 没有持有 self
         */
        
        // __weak typeof(self) weakSelf = self; 一般的宏定义是这样的
        __weak SecondViewController *wSelf = self;
        
        self.finshBlock = ^(BOOL isSuccess) {
            [wSelf loginTest];
        };
        
        /**
         在我们的应用中一般是下面这种方式写,为啥使用了 __weak 和 __strong ?
         有人可能会问,先 weak 后 strong,那相当于还是强引用了 self,你确定 strong的是 self?
         */
        
        /**
         打印:
         (lldb) p weakSelf
         (SecondViewController *) $0 = 0x0000000101c16f10
         (lldb) p self
         (SecondViewController *) $1 = 0x0000000101c16f10
         (lldb) p strongSelf
         (SecondViewController *) $2 = 0x0000000101c16f10
         (lldb)
         
         发现 weakSelf self 和 strongSelf 的内存地址是一样的,只是一次浅拷贝;
         */
        __weak typeof(self) weakSelf = self;
        
        self.finshBlock = ^(BOOL isSuccess) {
            // 如果没有这句话,当 self 被释放后,weakSelf 就变为了空,所以关于 weakSelf 的一些操作也就没什么意义了,如果还想让 weakSelf 所调用的一些方法有意义那么久需要强引用 weakSelf;
            __strong typeof(self) strongSelf = weakSelf;
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"weakSelf.logId: %@", strongSelf.logId);
                NSString *name = strongSelf.logId;
                if (name.length > 0) {
                    NSLog(@"Hello world");
                }
                [strongSelf loginTest];
            });
        };
        self.finshBlock(YES);
        
        /**
         修改前的:
         self.finshBlock = ^(BOOL isSuccess) {
            [self loginTest];
         };
         */
    }
    

    循环引用二:

    - (void)testMemoryLeakCase2
    {
        /**
         这里面出现了两个对象的内存泄漏: task 和 self
         task的内存泄漏:
         task 有个属性叫 blcok,但是在 block 中又捕获了 task,这样就形成了一个闭环
         self 的内存泄漏:
         因为这个 block 中捕获了 self,block 没有释放那么 self 咋么能释放呢?
         所以只要打破这个闭环,self 就释放了。
         
         */
        AsyncTask *task = [AsyncTask new];
        
        __weak AsyncTask *wTask = task;
        task.block = ^(BOOL isFinish) {
            NSString *name = wTask.lastLoginId;
            self.logId = name;
        };
        [task sendLogin];
        
        /**
         AsyncTask *task = [AsyncTask new];
         task.block = ^(BOOL isFinish) {
         NSString *name = task;
         self.logId = name;
         };
         [task sendLogin];
         */
    }
    

    循环引用三:

    其实实例变量是通过 self->name 访问的,所以也可能造成循环引用。

    - (void)testMemoryLeakCase3
    {
        /**
         这里可能不太容易看出来,访问 name 实例变量相当于 self->name
         这样 self 持有 finshBlock, finshBlock 持有 self,形成闭环,造成循环引用
         */
        
        __weak SecondViewController *wSelf = self;
        self.finshBlock = ^(BOOL isFinish) {
            /*
             Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to strong variable first
             */
            // 发现这样写不行,还报错,它的意思是 __weak 指针可能为空,必须要强引用
            // wSelf->name = @"Hello lefe";
            
            /**
             那么为什么在 testMemoryLeakCase1 中 wSelf.logId = @"Hello logId"; 没有编译错误呢?我想
             估计 wSelf.logId 等价于 [wSelf logId],相当于调用了一个方法,
             nil 调用方法是没有错误的。你知道属性和实例变量的区别吗?
             
             下面这行代码也会报错的:
             __weak AsyncTask *task;
             task->_sex;
             
             */
            wSelf.logId = @"Hello logId";
            
            __strong SecondViewController *strongSelf = wSelf;
            strongSelf->_name = @"Hello lefe";
        };
        
        /**
        也可以使用下面方法来解除循环引用
        __block id temp = self;
        self.finshBlock = ^(BOOL isFinish) {
            temp = nil;
        };
        self.finshBlock(YES);
        */
        
        
        /**
         修改前的代码:
         self.finshBlock = ^(BOOL isFinish) {
            name = @"Hello lefe";
         };
         */
    }
    

    ===== 我是有底线的 ======
    喜欢我的文章,欢迎关注我的新浪微博 Lefe_x,我会不定期的分享一些开发技巧

    相关文章

      网友评论

      • 洁简:如果block可能不会引起循环引用 但是仍然用了__weak __strong也没啥问题吧
        Lefe:没事
      • yzhxcql:建议__weak的实现也加上
        Lefe:@Adoma 后续加一下
      • BearLin:写得不错:+1:想请教终端怎么弄的?:relieved:
        Lefe:http://www.jianshu.com/p/f9e8877f1724 看这篇文章
        BearLin:@Lefe 想问的是那个背景图片:joy:
        Lefe:进入终端,进入项目文件的目录,输入:clang ....... 文章有提到
      • 1dbdc3054b28:不错,讲解的很清楚了
        Lefe:@rusuKing :pray:

      本文标题:Block 是如何实现的?如何避免循环引用?

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