Block 捕获变量的实现原理之对象类型变量

之前我们探讨了Block捕获基本数据变量的实现原理,对于对象类型变量的捕获原理是不是跟基本数据变量一样呢?

3.捕获对象类型变量

我们继续使用之前介绍过的clang命令来重写OC代码来查看转化之后的C++代码来探讨Block对于对象类型的捕获原理.我们使用自定义Person 类来做说明:

@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@end

@implementation Person
@end

3.1  Block捕获静态变量或者全局变量

3.1.1 捕获静态变量

[示例代码3.1.1]

​
    static Person *person;
    person = [[Person alloc] init];
    person.name = @"Jack";
    void(^block)(void) = ^(){
        person.name = @"Frank";
    };
    block();
    NSLog(@"person.name == %@", person.name);
​​

​

我们将转化之后的代码进行删减[之前已经出现过的部分重复结构体不再列出]和重新排列[方便查看]:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person **person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person **_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Person **person = __cself->person; // bound by copy

            ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_002eea_mii_1);
        }
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*/);}

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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        static Person *person;
        person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_002eea_mii_0);
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &person, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_002eea_mii_2, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("name")));
    }
    return 0;
}

去除main()函数中的各种强制转化,可以更加清晰地查看调用关系:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        static Person *person;
         //使用运行时方式消息机制调用方法,创建Person对象
        person = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));

        //调用setName:方法
        objc_msgSend(person, sel_registerName("setName:"), &__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_002eea_mii_0);

        //初始化__main_block_impl_0实例block
        block = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &person, 570425344);

        //调用block的FunPtr实现
        block->FuncPtr(block);
                    NSLog(&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_002eea_mii_2, objc_msgSend(person, sel_registerName("name")); //打印消息
    }
    return 0;
}

在struct __main_block_impl_0的实现中:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person **person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person **_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

我们发现了一个用于存储person变量的二级指针变量,所以在Block 中不仅可以修改Person 的成员变量,还可以对person变量重新赋值.

在Block捕获对象对象类型变量时,我们struct __main_block_desc_0中,我们发现了额外的两个实现函数:

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

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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
  •  __main_block_copy_0:当block进行copy操作的时候就会自动调用,主要用来根据flags参数处理Block捕获的对象变量是否需要强引用.底层是通过调用_Block_object_assign来实现的,该函数的的原型有三个参数:
  1. void *destAddr:需要进行copy操作的新对象(copy之后的对象)地址;
  2. const void *object:需要进行copy操作的原始对象(copy之前的对象)地址;
  3. const int flags:对象标识位,是一个用于标记对象类型枚举值,用于判断标志是否需要对Block捕获的对象进行强引用.BLOCK_FIELD_IS_OBJECT表示是一个普通的对象,BLOCK_FIELD_IS_BLOCK是一个Block变量,BLOCK_FIELD_IS_BYREF是一个被__block修饰的变量,BLOCK_FIELD_IS_WEAK表示是一个使用__weak修饰的变量,BLOCK_BYREF_CALLER表示一个使用内部copy/dispose进行管理的辅助函数,不需要进行引用计数操作.
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};

enum {
    BLOCK_ALL_COPY_DISPOSE_FLAGS = 
        BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_BYREF |
        BLOCK_FIELD_IS_WEAK | BLOCK_BYREF_CALLER
};

// Runtime entry point called by compiler when assigning objects inside copy helper routines
BLOCK_EXPORT void _Block_object_assign(void *destAddr, const void *object, const int flags);
    // BLOCK_FIELD_IS_BYREF is only used from within block copy helpers
  • __main_block_dispose_0:当block从堆中移除时就会自动调用,主要用于处理对捕获对象的release操作,断开对捕获对象的强引用操作,底层通过_Block_object_dispose实现.
// runtime entry point called by the compiler when disposing of objects inside dispose helper routine
BLOCK_EXPORT void _Block_object_dispose(const void *object, const int flags);

3.1.2 捕获全局变量

Person *person;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        person = [[Person alloc] init];
        person.name = @"Jack";
        void(^block)(void) = ^(){
            person.name = @"Frank";
        };
        block();

    }
    return 0;
}

使用clang命令进行重写:

Person *person;

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) {

            ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_b01edc_mii_2);
        }

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; 
        person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_b01edc_mii_1);
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    }
    return 0;
}

对于全局变量,Block并没有进行任何额外的操作,毕竟全局普通变量Block作用域享有与其他函数同等的操作权限.

3.2  捕获不使用__block修饰的对象变量

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Person *person = __cself->person; // bound by copy

            ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_b041db_mii_2);
        }
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*/);}

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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        Person *person;
        person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_b041db_mii_1);
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_b041db_mii_3, block);

    }
    return 0;
}

在Block的实现中,将Person的地址传入Block的构造函数中,所以在Block可以修改person指向空间的值,也就是各个成员变量的值,但是不可以修改person指针地址.

3.3 捕获__block对象变量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block Person *person;
        person = [[Person alloc] init];
        person.name = @"Jack";
        void(^block)(void) = ^(){
            person.name = @"Frank";
        };
        block();
    }
    return 0;
}

使用clang命令进行重写

struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *person;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_person_0 *person; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__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_person_0 *person = __cself->person; // bound by ref

            ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)(person->__forwarding->person), sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_c45b4a_mii_2);

        }
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, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131};
;
        (person.__forwarding->person) = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));

        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)(person.__forwarding->person), sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_c45b4a_mii_1);
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));


        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_c45b4a_mii_3, block);

    }
    return 0;
}

在这个实现中,出现了新的结构体:

struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *person;
};
  • 使用__block修饰的变量被封装了一个struct __Block_byref_person_0类型的结构体,同时将内部的__forwarding指针指向了自己的结构体地址,在之后关于person的操作中,无论是Block内部还是外部,其实都是使用该结构体来进行person变量的相关操作,这就保证person变量数据操作的一致性.此时就可以通过struct __Block_byref_person_0结构体来实现捕获变量的属性修改以及变量本身的重新赋值等;
  • 这时候的变量的存储以及释放操作不再由Block本身的copy/dispose来控制,而是由struct __Block_byref_person_0结构体的copy/dispose来控制;
  • 初始化时__Block_byref_id_object_copy_131时,使用了地址偏移量的形式来获取成员变量:
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

其中(char*)dst + 40 就是Person *person的首地址(因为在64位系统中,int占据四个字节, void *占据八个字节, 所以40=8(void *)+8(__Block_byref_person_0 *) + 4( int) + 4( int) + 8(void *)  + 8 (void *)),而131=128+3=BLOCK_BYREF_CALLER|BLOCK_BYREF_IS_OBJECT.

4. __block对对象变量生命周期(引用计数)的影响?

自定义一个NSObject的分类,在Targets-Build Phases中将该文件设置为-fno-objc-arc:

@interface NSObject (RetainCount)
- (NSInteger)getRetainConut;
@end
@implementation NSObject (RetainCount)
- (NSInteger)getRetainConut {
    return self.retainCount;
}

同时将在Person中重写dealloc方法:

@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@end

@implementation Person
- (void)dealloc {
    NSLog(@"对象开始释放");
}
@end

然后我们尝试输出对象的引用计数:

        __block Person *person;
        person = [[Person alloc] init];
        person.name = @"Jack";
        NSLog(@"retainCount == %ld", [person getRetainConut]);
        
        void(^block)(void) = ^(){
            person.name = @"Frank";
        };
        NSLog(@"retainCount == %ld", [person getRetainConut]);
        block();

输出结果:

retainCount == 1
retainCount == 1
对象开始释放

其实从3.1.3的重写代码中我们可以看到如果使用了__block来修饰对象变量那么对象变量会被封装成新的结构体,通过该结构体的内部实现来控制对象的生命周期.所以在Block调用为结束之前,该变量是不会被释放的.来做个简单验证,将block的执行改在3s之后:

    __block Person *person;
    person = [[Person alloc] init];
    person.name = @"Jack";
    NSLog(@"retainCount == %ld", [person getRetainConut]);
    void(^block)(void) = ^(){
        person.name = @"Frank";
    };
    NSLog(@"retainCount == %ld", [person getRetainConut]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        block();
    });

输入结果:

17:05:12.599798+0800 BlockDemo[49800:10999998] retainCount == 1
17:05:12.599929+0800 BlockDemo[49800:10999998] retainCount == 1
17:05:15.600426+0800 BlockDemo[49800:10999998] 对象开始释放

所以我们可以看到在当前person定义的作用域结束时,person并没有被释放,而是在3s之后Block执行结束之后person 对象才被释放.所以在使用__block时不用担心局部对象变量会被提前释放,因为Block强引用了对象变量,只有等到Block的生命周期结束之后有可能会被释放.

如果我们不使用__block修饰会出现什么现象呢?

    Person *person;
    person = [[Person alloc] init];
    person.name = @"Jack";
    NSLog(@"retainCount == %ld", [person getRetainConut]);
    void(^block)(void) = ^(){
        person.name = @"Frank";
    };
    NSLog(@"retainCount == %ld", [person getRetainConut]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        block();
    });

输出结构:

17:11:05.834223+0800 BlockDemo[49853:11005986] retainCount == 1
17:11:05.834365+0800 BlockDemo[49853:11005986] retainCount == 3
17:11:08.834791+0800 BlockDemo[49853:11005986] 对象开始释放

同样,person对象需要在Block执行完成之后才可以被释放,而且Block的出现对于对象变量明显产生了强引用.根据我们3.2的从写代码分析可知,

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

不使用__block修饰时,在Block内部实现结构体中引用了person对象,同时使用__main_block_impl_0中的__main_block_desc_0中的辅助copy函数来管理引用对象的引用计数,所以在Block的实现中,我们可以发现Block其实是强引用了局部对象.这是因为其实每个变量默认的修饰符在缺省时默认是__strong,所以Block强引用了person对象,所以在Block执行期间,person对象不会被释放.

那么如果此时这个外部对象本身直接或者间接强引用了block,两个对象相互强引用了彼此会怎么样呢?会出现循环引用问题.

5. 循环引用问题

循环引用,就是指由于对象之间相互持有,而导致对象占用的空间不能及时进行释放,从而导致内存泄漏的现象.常见的循环引用问题,主要存在于两类:

  • 两个对象之间直接或者间接相互持有:例如
@class Son;

@interface Father : NSObject
@property (strong, nonatomic) Son *son;
@end

@implementation Father

- (void)dealloc {
    NSLog(@"Father开始释放!");
}
@end


@interface Son : NSObject
@property (strong, nonatomic) Father *father;
@end

@implementation Son
- (void)dealloc {
    NSLog(@"Son开始释放!");
}
@end



//在使用中出现这样的代码
 Father *father = [[Father alloc] init];
 Son *son = [[Son alloc] init];
 father.son = son;
 son.father = father;

这种情况的主要解决方案是将其中一个属性生命为weak,至于将哪个属性声明为weak要看具体的需要.常见的应用场景还有控制器有一个UITableView对象属性,而UITableView对象又将控制器设置为代理.

  • 对象属性中存在Block属性,而Block又间接使用了当前对象.例如,当前控制器有一个camera属性用来自定义相机,当摄像机闪光灯打开时需要将界面上的btn_flash.selected = true,否则btn_flash.selected = false:
@interface ViewController ()
@property (strong, nonatomic) Camera *camera;
@property (strong, nonatomic) UIButton *btn_flash;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.camera = [[Camera alloc] init];
    self.camera.didStatusChange = ^(BOOL off) {
        self.btn_flash.selected = off;
    };
}

由于camera强引用了回调didStatusChange,而didStatusChange在定义时又强引用了当前控制器self,导致相互引用计数啊永远不为零,造成空间无法释放.这种循环引用比较隐蔽,不太容易发现,平时需要多注意.解决这类循环引用问题,方法很多,我们这里介绍两种:

(1) 我们常用的处理方法是使用__weak方法来告诉Block不要对捕获的对象进行强引用,破坏循环引用的闭环,使对象在适当的时候进行释放.

@interface ViewController ()
@property (strong, nonatomic) Camera *camera;
@property (strong, nonatomic) UIButton *btn_flash;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.camera = [[Camera alloc] init];

    __weak typeof(self) weakSelf = self;
//或者直接使用 __weak ViewController *weakSelf = self;
    self.camera.didStatusChange = ^(BOOL off) {
        weakSelf.btn_flash.selected = off;
    };
}

这里需要注意的是:使用__weak之后,Block不再强引用捕获对象,所以在Block执行时,需要确认引用到的对象依然没有被销毁,否则该对象的引用就没有任何意义了.虽然这种情况并不常用,但是如果你有足够的理由需要这么做,可以使用再使用__strong 关键字对引用对象进行一次强引用来延长引用对象的生命周期,避免提前释放.所以我们也可以得出一个注意事项,那就是在没有循环引用的情况下,Block会自动处理捕获对象的生命周期问题,保证在Block执行时引用对象是未被释放的,所以尽量不要人为的干预.

@interface ViewController ()
@property (strong, nonatomic) Camera *camera;
@property (strong, nonatomic) UIButton *btn_flash;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.camera = [[Camera alloc] init];

    __weak typeof(self) weakSelf = self;
//或者直接使用 __weak ViewController *weakSelf = self;
    self.camera.didStatusChange = ^(BOOL off) {
         __strong strongSelf = weakSelf;
         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
         NSLog("self == %@", strongSelf);
         strongSelf.btn_flash.selected = off;
        //code here to do what you want related self
        
    });

    };
}

使用__strong 关键字可以使strongSelf这个变量强引用weakSelf变量,使得在新的Block在生命周期内,局部变量strongSelf对weakSelf的持有不释放.当新的Block执行完毕之后,strongSelf对wealSelf的强持有被释放,此时self对象才有可能被释放.

(2)根据上边的操作我们可以获得新的启示,所以对于Block的循环引用问题,我们可以有一种新的方法:即在Block中使用一个新Block来捕获临时变量,既可以保证在Block运行期间引用对象不释放,又可以保证不产生循环引用.

    void(^block)(ViewController *, BOOL) = ^(ViewController *vc,BOOL off) {
        NSLog(@"self == %@", vc);
        vc.btn_flash.selected = off;
        //code here to do what you want related self(vc)
    };
    __weak typeof (self) weakSelf = self;
    self.callback = ^(BOOL off){
        __strong typeof(weakSelf) strongSelf = weakSelf;
        block(strongSelf, off);
    };

在这种操作中,由于strongSelf是一个局部变量,所以被block捕获之后会保证在blcok的生命周期内strongSelf不会被释放,当block执行结束之后就会释放掉strongSelf,同时断开对于weakSelf的强引用,这时候self对象是否释放就跟block没有关系了.由于这里的block操作只是一个过渡,所以可以继续简化一下,让block变成匿名实现:

__weak typeof (self) weakSelf = self;
self.callback = ^(BOOL off){
    __strong typeof(weakSelf) strongSelf = weakSelf;
    ^(ViewController *vc, BOOL off){
        NSLog(@"self == %@", vc);
        vc.btn_flash.selected = off;
        //code here to do what you want related self
     }(strongSelf, off);
    };

当然,其实之所以循环引用会不能释放空间,是因为彼此之间的持有不能正常释放,如果我们可以手动将其中一个的持有关系打破即可.例如,我们可以这样(我们手动将强引用临时变量置为nil,断开其中的强引用):

void(^block)(ViewController *, BOOL) = ^(ViewController *vc,BOOL off) {
    NSLog(@"self == %@", vc);
    vc = nil;
    //code here to do what you want related self
    };

__block typeof (self) vc = self;
self.callback = ^(BOOL off){
block(vc, off);
    };

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值