属性的内部实现
属性的内部实现(也就是getter、setter方法的实现),主要跟属性的attribute有关。
assign
assign一般用来标记标量(基本数据类型或者没有 * 号的)和代理delegate。
用assign来标记的的属性,对应生成的getter、setter方法,没有对野指针、内存泄漏作相应的判断,因为assign标记的是标量属性,不需要对其进行指针判断。
但是,assign也可以用来标记非标量的属性。如:用assign标记NSString *类型的属性。如果这样标记的话,就必须重写getter、setter方法,因为这些属性赋值的情况,都是将地址赋给另一个指针。很容易出现野指针和内存泄漏这种状况。
理解重写的思路:
1、创建一个Person类,声明一个属性@property (nonatomic , assign)NSString *name;此时name的默认getter、setter方法是按照assign的getter、setter方法生成的,也就是没有加上指针判断的。
2、在main函数中,创建Person类的对象p1,然后alloc出来一个NSString对象str,并赋初值@“贝爷”。这里创建了一个空间,内容是“贝爷”,有一个指针str指向它,引用计数为1。
3、p1调用setter方法,参数是str。此时将p1对象的name属性指向了“贝爷”所在的空间。
4、str使用完毕,将str释放release。此时引用计数 -1后变为0 ,此时系统已经检测到引用计数为0 ,将“贝爷”所在的内存回收。那么name就变成了野指针。
5、为了避免上面的状况,在setter方法中,将_name = [name retain],把引用计数与指针数目对应。
6、在main函数中,再alloc一个NSString的对象str2,并赋初值@“六娃”,则这里开辟了另一个空间,有一个指针str2指向它,引用计数为1。
7、p1再调setter方法,str2作为参数,此时,将p1对象的name属性指向了“六娃”所在的内存,并把引用计数+1,为2。
8、str2用完了,就将str2释放release。此时,“六娃”所在的空间有一个p1的name属性指向,引用计数为1。但是,“贝爷”所在的内存被泄漏了。
所以,在setter方法中,在[name retain]之前,应该把属性_name释放release掉。
9、此时,p1再次调用setter方法,参数是[p1 name],也就是name所指向的内存。那么在setter方法中,由于先对_name释放release了,此时引用计数为0 。那么,这块内存“六娃”就被系统回收,那么,接下来的[name retain]已经无权再对这块内存进行操作。
10、所以,setter方法中,在[_name release]之前应该加一个判断,判断赋给_name的 name是否相等,如果不相等,才release掉_name,然后retain。
代码如下:
Person.h
#import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic , assign)NSString *name; @end |
Person.m
#import "Person.h" @implementation Person //用assign标记,如果不重写,就会直接用assign的getter、setter方法 //重写retain,则就不会用自动生成的getter,setter方法。而是用重写以后的。 @synthesize name = _name; - (void)setName:(NSString *)name{ if (_name != name) { [_name release]; _name = [name retain]; } } - (NSString *)name{ //苹果建议这样写,减少程序崩溃的几率 //如果写出野指针,只要在出自动释放池后,让其释放。一般自动释放池套在main函数里。 return [[_name retain] autorelease]; } @end |
main.m
#import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *p1 = [[Person alloc]init]; [p1 setAge:10]; NSString *nameStr = [[NSString alloc]initWithFormat:@"贝爷"]; [p1 setName:nameStr]; NSLog(@"%@",[p1 name]); //安全释放 [nameStr release]; nameStr = nil; //释放了nameStr,此时属性name的指针还指向nameStr原来所指向的值,(由于[p1 setName:nameStr];时,没有使引用计数+1,)在nameStr释放后,引用计数为0,此时“贝爷”所在的内存已经被回收,导致name属性存得地址成为野指针。所以在.m中name的_name = [name retain],使引用计数和所指向的指针个数一致。 // NSString *nameStr2 = [[NSString alloc]initWithFormat:@"六娃"]; // [p1 setName:nameStr2]; // NSLog(@"%@",p1.name); // // [nameStr2 release]; // nameStr2 = nil; // //上面的name先指向“贝爷”,然后nameStr被释放,引用计数 -1,此时,重新开辟空间,放入“六娃”,nameStr2指向“六娃”。[p1 setName:nameStr2];把nameStr2的地址赋给name,name就指向了“六娃”,此时,“贝爷”所在的内存就泄漏了。所以在.m中在retain之前,应该将_name release。“贝爷”被回收,name重新指向“六娃” } return 0; } |
retain
以上就是用assign来展示retain的内部getter、setter方法实现。
也就是说,声明一个属性,attribute设置为retain。那么这个属性的内部实现,就是上面的10点。
@property (nonatomic , retain)NSString *name;
copy
copy的内部实现,与retain的一样,只是将内容copy到另一个空间之前,判断copy内容过去的地址是否与现在的地址相同,避免野指针;再把原来的指针release掉,避免内存泄漏。注意的是,拷贝过去的那个空间引用计数为0。
Person.h
#import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic , copy)NSString *sex; - (instancetype)initWithName:(NSString *)name age:(NSInteger)age sex:(NSString *)sex; @end |
Person.m
copy语义的内部实现
#import "Person.h" @implementation Person //copy //与retain的情况差不多。 @synthesize sex =_sex; - (void)setSex:(NSString *)sex{ if (_sex != sex) { [_sex release]; _sex = [sex copy]; } } - (NSString *)sex{ return [[_sex retain]autorelease]; } - (void)dealloc{ [_name release]; [_sex release]; [super dealloc]; } - (instancetype)initWithName:(NSString *)name age:(NSInteger)age sex:(NSString *)sex{ if (self = [super init]) { self.name = name; self.age = age; self.sex = sex; } return self; } @end |
main.m
#import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *p2 = [[Person alloc]init]; NSString *str1 = [[NSString alloc]initWithFormat:@"男"]; [p2 setSex:str1]; [str1 release]; str1 = nil; NSString *str2 = [[NSString alloc]initWithFormat:@"女"]; [p2 setSex:str2]; [str2 release]; str2 = nil; [p2 setSex:[p2 sex]]; } return 0; } |
初始化方法
Person.h
#import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic , copy)NSString *sex; @property (nonatomic,assign)NSInteger age; @property (nonatomic , copy)NSString *sex; - (instancetype)initWithName:(NSString *)name age:(NSInteger)age sex:(NSString *)sex; @end |
Person.m
#import "Person.h" @implementation Person - (void)dealloc{ [_name release]; [_sex release]; [super dealloc]; } - (instancetype)initWithName:(NSString *)name age:(NSInteger)age sex:(NSString *)sex{ if (self = [super init]) { self.name = name; self.age = age; self.sex = sex; } return self; } @end |
用self.name(调自己的setter方法,在传进来的参数name地址给自己之前,做了判断,retain等操作。避免野指针和内存泄漏)
main.m
#import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *p4 = [[Person alloc]initWithName:@"白雪公主" age:18 sex:@"男"]; } return 0; } |
便利构造器
便利构造器,在实现.m文件中,return 出来的地址,要用[ p autorelease ]来释放。符合谁污染谁治理原则。
便利构造器创建的对象不需要进行释放,否则会出现过度释放。
Person.h声明便利构造器
#import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic , assign)NSString *name; @property (nonatomic,assign)NSInteger age; @property (nonatomic , copy)NSString *sex; - (instancetype)initWithName:(NSString *)name age:(NSInteger)age sex:(NSString *)sex; //便利构造器 + (instancetype)personWithName:(NSString *)name age:(NSInteger)age sex:(NSString *)sex; @end |
person.m
#import "Person.h" @implementation Person - (instancetype)initWithName:(NSString *)name age:(NSInteger)age sex:(NSString *)sex{ if (self = [super init]) { self.name = name; self.age = age; self.sex = sex; } return self; } //便利构造器 + (instancetype)personWithName:(NSString *)name age:(NSInteger)age sex:(NSString *)sex{ Person *p = [[Person alloc]initWithName:name age:age sex:sex]; //出了自动释放池再让其释放 return [p autorelease]; } @end |
main.m
#import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { //便利构造器 //便利构造器创建的对象不用release,否则会过度释放 //因为便利构造器内部已经用了autorelease。 Person *p3 = [Person personWithName:@"小金刚" age:19 sex:@"男"]; //过度释放 // [p3 release]; Person *p4 = [[Person alloc]initWithName:@"白雪公主" age:18 sex:@"男"]; NSLog(@"%ld",[p4 retainCount]);//1 NSMutableArray *arr = [NSMutableArray array]; //装入容器(不管是数组,字典,集) //对象装入容器的时候,引用计数 +1。 [arr addObject:p4]; NSLog(@"%ld",[p4 retainCount]);//2 //对象移出容器的时候,引用计数 -1 [arr removeObject:p4]; NSLog(@"%ld",[p4 retainCount]);//1. [p4 release]; p4 = nil; } return 0; } |
集合内存管理
main.m
Person *p4 = [[Person alloc]initWithName:@"白雪公主" age:18 sex:@"男"]; NSLog(@"%ld",[p4 retainCount]);//1 NSMutableArray *arr = [NSMutableArray array]; //装入容器(不管是数组,字典,集) //对象装入容器的时候,引用计数 +1。 [arr addObject:p4]; NSLog(@"%ld",[p4 retainCount]);//2 //对象移出容器的时候,引用计数 -1 [arr removeObject:p4]; NSLog(@"%ld",[p4 retainCount]);//1. [p4 release]; p4 = nil; |