⼀、属性的内部实现原理
下面我将通过一个例子来引导出为什么assign,retain,copy的内部实现语句为什么要那么写,那么写是为了达到什么效果.
新建一个工程,将内存管理改成手动设置.
新建一个Person类,它有属性name.
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
//这里为什么这么用assign(一般基本数据类型用),是为了复写set,get 方法,实现推导出内部实现
@property (nonatomic,assign)NSString *name;
- (instancetype)initWithName:(NSString *)name;
+ (instancetype)personWithName:(NSString *)name;
@end
// Person.m
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) {
_name = name;
}
return self;
}
+ (instancetype)personWithName:(NSString *)name
{
Person *p = [[Person alloc]initWithName:name];
return p;
}
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建一个Person类的对象
Person *p = [[Person alloc]init]; //p引用计数为1
NSString *name = [[NSString alloc]initWithFormat:@"张三"];
p.name = name; //name引用计数为1
[name release]; //name引用计数为0
//思考:此时的name释放会将p.name这个指针释放掉吗?
NSLog(@"%@",p.name);
[p release]; //p引用计数为0
}
return 0;
}
**上面出现了崩溃,因为你打印的p.name的NSString 的对象已经被释放了,如果你打印就相当于产生了一个野指针.所以会崩溃.
解决野指针:
我们可以讲直接赋值改成retain,那么对应的name的计数器就会为2,release后,还是1,还是可以打印的.但是由于这是我们需要推到的内部实现,所以还是用assign直接赋值来思考,可不可以在调用setName的时候retain一下,就可以实现那句打印.
// 在Person.m中添加set get方法
-(void)setName:(NSString *)name
{
_name = [name retain];
}
- (NSString *)name
{
return _name;
}
**这里补充一个点:如果你同时 重新复写set get方法会默认出错,因为当你同时重写这两个方法时,你声明的属性就失效了,此时就不会生成_name .
解决方法有两个: 1.在Person.h中添加成员变量_name;
2.在Person.m中添加成员变量名的连接语句
@synthesize name = _name;**
打印结果:2015-11-07 20:01:05.473 1[3157:313893] 张三
下面我们修改main.m中的代码:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建一个Person类的对象
Person *p = [[Person alloc]init]; //p引用计数为1
NSString *name = [[NSString alloc]initWithFormat:@"张三"];
p.name = name; //name引用计数为2,因为retain了一次
p.name = name; //name引用计数为3,因为retain了一次
p.name = name; //name引用计数为4,因为retain了一次
p.name = name; //name引用计数为5,因为retain了一次
[name release];
[p release];
// 这样会产生一个问题,person类的对象释放了,但是此时的name的对象还没有被释放
NSLog(@"%@",p.name);
}
return 0;
}
怎么解决这个内存泄露呢:因为赋值的相同的值,所以我们可以在set方法中加一个判断,如果新的name和已经存储的name不相同才执行retain +1
// Person.m
-(void)setName:(NSString *)name
{
if (_name != name) {
_name = [name retain];
}
_name = [name retain];
}
改变mian.m中的代码,将不同的name付给p.name又会产生什么错误呢
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建一个Person类的对象
Person *p = [[Person alloc]init]; //p引用计数为1
NSString *name = [[NSString alloc]initWithFormat:@"张三"];
NSString *name1 = [[NSString alloc]initWithFormat:@"李四"];
NSString *name2 = [[NSString alloc]initWithFormat:@"王五"];
p.name = name;
p.name = name1;
p.name = name2;
[name release];
[name1 release];
[name2 release];
// 这样会也会产生一个问题,person类的对象释放了,但是此时的name的对象也是还没有被释放
NSLog(@"%@",p.name);
}
return 0;
}
// 打印结果:2015-11-07 20:21:52.097 1[3217:324727] 王五
**该问题也是内存泄露,怎么解决呢?
思路:我们可以先将之前_name里面的值release掉再重新赋值**
// Person.m
-(void)setName:(NSString *)name
{
if (_name != name) {
// 如果你对一个空release 相当于你什么都没做
// [nil release];
[_name release];
_name = [name retain];
}
**这样每个只需要release一次就可以,但是最后剩下还有一个name和p没有释放,那么怎么做可以同时释放p对象和里面的name呢?
⼆、dealloc内释放实例变量
思路:因为当某个对象的引用计数变为0 时,会自动调用dealloc方法,所以我们可以通过复写父类的方法实现**
// Person.m
-(void)dealloc
{
[_name release];
[super dealloc];
}
**最后是get方法的复写,如果你想还想用这个p.name,但是p已经释放了呢?
思路:可以在get方法中retain一次,但是又不知道什么时候释放,所以可以再autorelease,自然在出了释放池之后,就可以自动释放了**
// Person.m
-(NSString *)name
{
return [[_name retain]autorelease];
}
以上就是retain的内部实现.copy的原理是一样的.
三.便利构造器⽅法的实现原理
由于你在便利构造器中返回的对象没有释放掉,但是这生成的对象必然在程序中使用的,但是又不知道在什么时候释放,所以我们可以配合自动释放池使用,因为自动释放池会自动收集autorelease对象,生成一个栈,将该种类的对象压入,出池后会给每一个栈内对象release一次进行释放.
+ (instancetype)personWithName:(NSString *)name
{
Person *p = [[Person alloc]initWithName:name];
return [p autorelease];
}