ARC模式很方便的使程序员可以不再关心是否通过retain/release正确地释放每个使用过的对象,而将更多的精力用于开发程序的核心功能,但是并不是使用了ARC就可以高枕无忧,在引用的时候还要注意引用的方式,特别是在块引用中,避免循环引用问题。

一、循环引用的例子

使用引用计数,必须要注意循环引用的问题。环形相互引用的多个对象,将会导致内存泄露,因为循环中的引用计数不会降为0.

Object A->Object B: 
Object B->Object C: 
Object C->Object A:

这种情况下,就算想将A、B、C都释放掉,但按照规则,也只有等到C释放掉之后才有可能释放掉A(否则A的引用计数为1,无法被释放),同样,B释放之后才能释放C,而B释放需要先将A释放,这就形成了循环引用,结果是三个对象都不会被释放,这样就造成了内存泄露。

二、强引用和弱引用

2.1、强引用

一般情况下,当默认创建一个对象的参数的时候,我们都会习惯使用strong的关键词,比如

#import <Foundation/Foundation.h>

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

这样在引用的时候,这个person变量就是强引用,如果使用下面的代码声明两个Person的对象,然后将bPerson赋值给aPerson.person,那么就是在强引用bPerson,所以打印引用计数是2

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *aPerson = [[Person alloc] init];
    aPerson.name = @"hu";
    NSLog(@"%@,%lu",aPerson.name,CFGetRetainCount((__bridge CFTypeRef)aPerson));
    
    Person *bPerson = [[Person alloc] init];
    bPerson.name = @"dong";
    NSLog(@"%@,%lu",bPerson.name,CFGetRetainCount((__bridge CFTypeRef)bPerson));

    NSLog(@"========================");
    
    aPerson.person = bPerson;
    NSLog(@"%@,%lu",aPerson.name,CFGetRetainCount((__bridge CFTypeRef)aPerson));
    NSLog(@"%@,%lu",bPerson.name,CFGetRetainCount((__bridge CFTypeRef)bPerson));
    
    NSLog(@"========================");
    
    bPerson = nil;
    NSLog(@"%@,%@",aPerson.person.name,bPerson.name);
    
    NSLog(@"========================");
    
    [self initButton];
}

打印结果

2017-04-22 14:17:01.644 arcTest[13163:2976579] hu,1
2017-04-22 14:17:01.644 arcTest[13163:2976579] dong,1
2017-04-22 14:17:01.644 arcTest[13163:2976579] ========================
2017-04-22 14:17:01.644 arcTest[13163:2976579] hu,1
2017-04-22 14:17:01.645 arcTest[13163:2976579] dong,2
2017-04-22 14:17:01.645 arcTest[13163:2976579] ========================
2017-04-22 14:17:01.645 arcTest[13163:2976579] dong,(null)
2017-04-22 14:17:01.645 arcTest[13163:2976579] ========================

在aPerson.person = bPerson;调用之后,bPerson的引用计数为2,就是初始化一次,然后又被引用了一次。但是当bPerson为nil的时候,在aPerson.person中,还是有bPerson的name,并没有被释放掉。

2.1、弱引用

将strong这个关键字改为weak,就修改为了弱引用的方式,当你声明一个弱变量时,系统会追踪赋值给这个变量的引用。当引用的对象释放时,弱变量会被自动设置为nil。

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (strong,nonatomic) NSString *name;
//@property (strong,nonatomic) Person *person;  //强引用
@property (weak,nonatomic) Person *person;  //弱引用
@end

修改为弱引用之后,上面的函数打印的结果

2017-04-22 14:22:26.759 arcTest[13191:2983670] hu,1
2017-04-22 14:22:26.759 arcTest[13191:2983670] dong,1
2017-04-22 14:22:26.759 arcTest[13191:2983670] ========================
2017-04-22 14:22:26.759 arcTest[13191:2983670] hu,1
2017-04-22 14:22:26.759 arcTest[13191:2983670] dong,1
2017-04-22 14:22:26.759 arcTest[13191:2983670] ========================
2017-04-22 14:22:26.760 arcTest[13191:2983670] (null),(null)
2017-04-22 14:22:26.760 arcTest[13191:2983670] ========================

在bPerson设置为空时,aPerson.person中的name也变成了空值。

三、block块中的使用

在ViewController中,声明一个block

#import <UIKit/UIKit.h>

typedef void(^viewCallBack)(int);

@interface ViewController : UIViewController
@property(copy,nonatomic) viewCallBack callBack;

-(void)logCallback:(viewCallBack)callback;
@end

点击按钮的时候,跳转到SecondViewControler

-(void)initButton{
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 40)];
    [btn setTitle:@"切换" forState:UIControlStateNormal];
    [btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
    [self.view addSubview:btn];
    [btn addTarget:self action:@selector(changeVC) forControlEvents:UIControlEventTouchUpInside];
}

-(void)changeVC{
    SecondViewController *secondVC = [[SecondViewController alloc] init];
    [self presentViewController:secondVC animated:true completion:nil];
}

-(void)logCallback:(viewCallBack)callback
{
    self.callBack = callback;
    NSLog(@"回调咯");
    self.callBack(2);
}

在SecondViewController中,有一个变量m_person和name分别在.h和.m文件中;

SecondViewController.h文件

#import <UIKit/UIKit.h>
#import "Person.h"

@interface SecondViewController : UIViewController
@property (strong,nonatomic) Person *m_person;

@end

SecondViewController.m文件

#import "SecondViewController.h"
#import "ViewController.h"

@interface SecondViewController ()
{
    NSString *name;
}
@end

@implementation SecondViewController

在SecondViewController中,点击按钮,返回界面并且调用block块,如果是不管强弱引用,直接调用

-(void)changeVC{
    [((ViewController*)[self presentingViewController]) logCallback:^(int s) {
        self.m_person.name = [NSString stringWithFormat:@"%d",s];
        name = [NSString stringWithFormat:@"%d",s];
        NSLog(@"%d",s);
    }];
    [self dismissViewControllerAnimated:YES completion:nil];
}

会发现虽然界面返回了,东西也打印了,看似没有问题,其实并没有调用SecondViewController的dealloc函数,也就是并没有销毁掉SecondViewController这个对象

-(void)dealloc{
    NSLog(@"dealloc");
}

解决方案就是解决引用的问题,首先m_person需要使用弱引用,

__weak typeof(self) weakSelf = self;
weakSelf.m_person.name = [NSString stringWithFormat:@"%d",s];

而name因为并不是self的成员变量,默认是强引用,所以需要使用__strong来解决

__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf->name = [NSString stringWithFormat:@"%d",s];

这样处理之后的代码就是这样

-(void)changeVC{
    __weak typeof(self) weakSelf = self;
    [((ViewController*)[self presentingViewController]) logCallback:^(int s) {
        weakSelf.m_person.name = [NSString stringWithFormat:@"%d",s];
        __strong typeof(weakSelf) strongSelf = weakSelf;
        strongSelf->name = [NSString stringWithFormat:@"%d",s];
        NSLog(@"%d",s);
    }];
    [self dismissViewControllerAnimated:YES completion:nil];
}

这样处理之后,才会正确的调用dealloc的函数销毁对象。

四、Demo下载

Github下载地址:https://github.com/DamonHu/HudongBlogDemo/arcTest

Gitosc下载地址:http://git.oschina.net/DamonHoo/arcTest

五、参考文章


☟☟可点击下方广告支持一下☟☟

最后修改:2021 年 03 月 07 日
请我喝杯可乐,请随意打赏: ☞已打赏列表