欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS block 中循环引用以及weakSelf和strongSelf的使用

程序员文章站 2024-01-14 22:28:46
...

iOS开发中,我们会经常用到block,对于block的使用,想必最需要注意的是循环引用的问题了。当然,你会说,所有的block都用__weak ,这样就不会有这个问题了。但是,事实并非如此!

假设有个对象person,person有个属性block.

typedef void (^ClickBlock)(void);


@interface JKPerson : NSObject

@property (nonatomic,copy) ClickBlock block;

@end
#import "JKPerson.h"

@implementation JKPerson

- (void)dealloc
{
    NSLog(@"person - %s",__func__);
}
@end

我们对block 这样赋值:

  {
      JKPerson *person = [[JKPerson alloc] init];  //引用计数1
      person.block = ^{
          NSLog(@"%@",person);//对外部auto变量自动拷贝 并强对person做一个强引用 
      };
  }

运行代码:不会打印

对于ARC环境下,在外层 "}" 结束的时候,系统会自动为我们加上[person release] 操作,但是,此时person有个_block有个对person的强引用,那么person就不会释放 , perosn不释放的情况下,_block也不会释放,这样就会造成永远释放不了,也就是循环引用。

这里贴出编译过后的c++(xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc  main.m)-sdk 编译mac或iphone -arch 编译cpu  

 {
            JKPerson *person = ((JKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((JKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("JKPerson"), sel_registerName("alloc")), sel_registerName("init"));
            //很多强转,不过可以看到调用objc_msgSend方法给person对象发送setBlock消息
            //__main_block_impl_0 就是block编译成c++代码的源码(大致如此)
            ((void (*)(id, SEL, ClickBlock))(void *)objc_msgSend)((id)person, sel_registerName("setBlock:"), ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344)));
 }//

//__main_block_impl_0是一个结构体,里面存在一个JKPerson *__strong person; 强指针指向、、person(就是因为arc会自动copy block到堆上)
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  JKPerson *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, JKPerson *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

在生成的main.cpp文件中,搜索int main ,可以看到上面第一段{ }里面的代码 ,通过分析可知,__main_block_impl_0(其实就是block在内存中的状态,以结构体存在)是一个结构体,里面存在一个JKPerson *__strong person; 强指针指向person(就是因为arc会自动copy block到堆上)

struct __main_block_desc_0 此结构体中的copy 和dispose 方法,分别在block拷贝到堆时以及block将要被释放时调用。通过对源码的分析,可以看到这两个方法的实现如此

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_impl_0 的一个成员,赋值如下

 __main_block_desc_0 {

    reserved=0

    Block_size=sizeof(struct __main_block_impl_0) //就是block的大小

    void (*copy)=__main_block_copy_0

    void (*dispose)=__main_block_dispose_0

 }

那么我们知道,block会对person对象有一个强引用存在,而person对象又会对_block有一个强引用(此现象就会产生循环引用,两者内存永远得不到释放)

那么为了解决这种问题的发生,我们会使用到__weak关键字

__unsafe_unretained (也可以不强引用,但是当引用计数为0的,修饰的对象不会置为nil,会造成野指针)

修改代码如下,使用__weak修饰person

{
            
     JKPerson *person = [[JKPerson alloc] init];
     __weak typeof(person) weakPerson = person;
     person.block = ^{
          NSLog(@"%@",weakPerson);
     };
 }

运行代码:打印 person - -[JKPerson dealloc]

当我们用__weak 修饰person对象时,再次编译c++代码(xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m)-sdk 编译mac或iphone -arch 编译cpu  -fobjc-arc -fobjc-runtime=ios-8.0.0用来解决有__weak修饰变量的编译  

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  JKPerson *__weak weakPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, JKPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

很明显可以看到,和上次编译的c++,不同点是JKPerson *__weak weakPerson; 为弱引用(这里根据开发经验就可以知道__strong __weak的作用)

通过打印信息,和对c++代码的简单分析,知道__weak的作用,可以使block对对象的引用为弱引用 就不会造成循环引用了

__weak修饰的对象在block内不会强引用,那么就有可能造成,外部提前释放了preson对象,当block代码执行时,person对象就已经被释放了,person就为nil. 这也是为什么外部用__weak修饰时,block里面想通过__weakSelf->直接访问成员变量时编译器是会报错的!