循环引用

今天记录一下OC里面另一个常见的问题,那就是循环引用,总的来说现在我会想到的只有三种,后续如果有其他再类似的我会再一一补上:

  1. 类的循环引用
  2. MRC retain循环引用
  3. ARC strong对象的循环引用(其实和2是一样的,只是关键字不同而已)

类的循环引用

首先我们来看几个代码,场景是这样子的,一个人有一量车,所以Person类有个变量car,而车本身需要有个主人,表示谁的,所以Car类又有个变量ower,表示拥有者,所以类的代码如下,因为.m文件没有写其他实现,所以此处就不截取了

Person.h

1
2
3
4
5
6
7
8
9
#import <Foundation/Foundation.h>
#import "Car.h"
@interface Person : NSObject
@property (nonatomic, retain) Car *car;
@end

Car.h

1
2
3
4
5
6
7
8
#import <Foundation/Foundation.h>
#import "Person.h"
@interface Car : NSObject
@property (nonatomic, retain) Person *owner;
@end

main.m

1
2
3
4
5
6
7
8
9
10
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}

当你点击运行时,会发现编译不过,而且会有以下的报错信息
编译错误信息
看到这样的问题,新手朋友可能就会懵了,为什么我已经声明了,也用#import引用了,为啥他还会unknow呢?
现在我们试着将Car类中的#import “Person.h”改成@class Person;你会发现,所有错误都没了,编译也通过了,这又是为什么呢?关于这个问题,我们就有必要来解释一下几个关键字了。

#import 、#include 、@class的区别

1.#import和#include都能完整地包含某个文件的内容,#import能防止同一个文件被包含多次

2.@class仅仅是声明一个类名,并不会包含类的完整声明;所以@class能用来解决循环包含的问题

如何理解1中的#import 能防止同一个文件被包含多次呢,由上面的例子可以看到,Person类中#import “Car.h”,main.m中又#import “Car.h”,对于这样的操作,编译过程中只会import一次,如果是用include,就会多次包含,而由于上面的例子的特殊性,Person需要包含Car,Car又要包含Person,用编译时,就会出现Person.h->Car.h->Person.h…这样的循环操作,因为import和include的功能,你可以理解为简单的拷贝,所以会出现多份Person.h或者多份Car.h,所以此次使用@class,它仅仅是声明表示Person或者Dog是一个类,存在这么一个类,至于他里面有什么属性方法,它都不知道,所以在开发时,在.h文件引入其他类时,尽可能使用@class来代替#import,继承除外(那是因为了必须知道父类的细节),不仅能提高效率还能避免循环引用的问题。(提高效率指的是,当一个文件如果被多个文件import时,只要这文件有细微改动,其他文件编译时都需要重新编译,而@class则不会有这问题,因为它只表示有这么一个类,而不关心其细节)

修正完上述问题后的代码:

Person.h

1
2
3
4
5
6
7
8
9
#import <Foundation/Foundation.h>
@class Car;
@interface Person : NSObject
@property (nonatomic, retain) Car *car;
@end

Person.m

1
2
3
4
5
6
7
8
9
10
11
12
#import "Person.h"
#import "Car.h"
@implementation Person
- (void)dealloc {
self.car = nil;
NSLog(@"Person dealloc");
[super dealloc];
}
@end

Car.h

1
2
3
4
5
6
7
8
#import <Foundation/Foundation.h>
@class Person;
@interface Car : NSObject
@property (nonatomic, retain) Person *owner;
@end

Car.m

1
2
3
4
5
6
7
8
9
10
11
12
#import "Car.h"
@implementation Car
- (void)dealloc {
self.owner = nil;//set执行效果为,[_owner release]了,_owner = [nil retain],所以这是更安全的一种写法
NSLog(@"Car dealloc");
[super dealloc];
}
@end

main.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *owner = [[Person alloc]init];
Car *car = [[Car alloc]init];
owner.car = car;
car.owner = owner;
[car release];
[owner release];
}
return 0;
}

跑完上面的程序,你会发现,owner和car,谁都没有被释放,明明已经release了啊,其实运行过程,他们的关系如下图所示:
retain循环引用

当调用了[car release];[owner release];后,线1,线2断了,程序走完,栈内存中的car和owner由于不被引用,所以被系统回收了,而堆内存的由于还存在互相的强引用,各自的引用计数都为1,所以谁也无法被释放,如果我们修改下其中一方的引用不用retain,假设改了下面的程序:

Car.h

1
2
3
4
5
6
7
8
#import <Foundation/Foundation.h>
@class Person;
@interface Car : NSObject
@property (nonatomic, assign) Person *owner;
@end

再执行,你会发现,问题不存在了,我们的解决在于,只retain其中一方,所以当[car release];[owner release];后,_car引用计数为1,而_own引用计数为0,那么_own会被回收,就会调用self.car = nil,那么_car的引用计数就会变成0,此时,双双都被回收了,所以就不存在内存问题了。

至此,循环引用的问题就讲完了,在ARC,也存在这一问题,用的关键字是强引用strong,出现这种双方互相引用时,只要把其中一方的改成weak,就可以解决循环引用的问题了。


注:版权声明:本文为博主原创文章,未经博主允许不得转载。