Objective - C基础: 第五天 - 6.循环引用

时间:2022-04-09 09:46:14

在前面, 我们知道了OC的内存管理以及@property的参数, 其实还有一个叫做循环引用, 其实所谓的循环引用很简单, 就是两个类中, 你包含我, 我包含你, 这个就叫循环引用, 比如下面这个例子:


#import <Foundation/Foundation.h>
#import "Car.h"

@interface Person : NSObject

@property (nonatomic, retain) Car *car;

@end

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

@interface Car : NSObject

@property (nonatomic, retain)Person *person;

@end






这样子就是循环引用, 但这个例子里的循环引用是有性能问题的, 需要改进, 我们来看看改进之后的例子:

#import <Foundation/Foundation.h>

@class Car;

@interface Person : NSObject

@property (nonatomic, retain) Car *car;

@end

#import <Foundation/Foundation.h>

@class Person;

@interface Car : NSObject

@property (nonatomic, retain)Person *person;

@end

为什么这么做呢? 我们来回想一下, #import的作用就是引用头文件, 如果在我们平常的简单编程里面, 直接这么做是没问题的, 但如果在几十个几百个类中循环引用, 一旦某个被循环引用的类修改了一点点, 那么全部引用它的类就要重新再引用, 就会造成性能问题, 所以我们这里使用@class.



@class的作用仅仅只是告诉编译器XXX是一个类, 我们可以使用, 不像#import那样直接引用, 但@class也并不是完美的, 如果我们使用手动引用计数, 需要在.m文件里release对象的话, 那么就需要使用到#import来辅助一下, 比如:

#import "Person.h"
#import "Car.h"

@implementation Person

- (void)dealloc
{
    [_car release];
    [super dealloc];
}

@end

#import "Car.h"
#import "Person.h"

@implementation Car

- (void)dealloc
{
    [_person release];
    [super dealloc];
}

@end


那么@class#import的区别在哪里? 我们来看看区别:

#improt 方法会包含被引用类的所有信息, 包括被引用类的变量和方法;
@class 方式只是告诉编译器在X.h文件中X *x只是类的声明, 具体这个类里面有什么信息, 这里不需要知道, 等实现文件中真正要用到时, 才会真正去查看X类中信息




总结
1. 如果有上百个头文件都#import了同一个文件, 或者这些文件一次被#import, 那么一旦最开始的文件稍有改动, 后面引用到这个文件的所有类都需要重新编译一边, 这样的效率可想而知, 而相对来讲, 使用@class方式就不会出现这种问题了.


2. 在.m实现文件中, 如果需要引用到被引用类的实例变量或者方法时, 还需要使用#import方法引入被引用类.





再回头看看我们的例子, 我们会发现, 该例子会有循环retain, 什么是循环retain呢? 其实循环retain一般只会出现在循环引用上, 当两个类相互引用并且使用retain的时候, 那么谁也不会被释放, 这样子就会造成内存泄漏, 比如:

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"

int main(int argc, const char * argv[])
{
    Person *p = [[Person alloc] init];
    Car *c = [[Car alloc] init];
    
    p.car = c;
    c.person = p;
    
    NSLog(@"p = %ld", [p retainCount]);
    NSLog(@"c = %ld", [c retainCount]);
    
    [c release];
    [p release];
    
    return 0;
}

打印出来的结果:

2015-01-27 12:23:23.885 07.循环引用[6274:661345] p = 2
2015-01-27 12:23:23.886 07.循环引用[6274:661345] c = 2



有人会想到, 简单啦, 直接写两次release就可以啦, 没错是可以这么做, 但这么做不符合apple的内存管理原则, 我们之前说过内存管理的原则是一次alloc一次release, 所以该做法不可取.


解决办法其实也很简单, 只要把其中一个类改成assign就可以解决了, 比如:

#import <Foundation/Foundation.h>

@class Car;

@interface Person : NSObject

@property (nonatomic, assign) Car *car;

@end

#import <Foundation/Foundation.h>

@class Person;

@interface Car : NSObject

@property (nonatomic, retain)Person *person;

@end



在看看 main()函数打印出来的结果:

2015-01-27 12:27:19.038 07.循环引用[6316:663154] p = 2
2015-01-27 12:27:19.039 07.循环引用[6316:663154] c = 1
2015-01-27 12:27:19.039 07.循环引用[6316:663154] Car被释放了
2015-01-27 12:27:19.039 07.循环引用[6316:663154] Person被释放了



两端互相引用造成内存泄漏的解决方案: 把其中一端的retain改成assign.





好了, 这次我们就讲到这里, 下次我们继续~~