在前面, 我们知道了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.
好了, 这次我们就讲到这里, 下次我们继续~~