@property 其实就是在编译阶段由编译器自动帮我们生成ivar成员变量getter方法,setter方法。使用“自动合成”( auto synthesis)这个过程由编译器在编译阶段执行自动合成,所以编译器里看不到这些“合成方法”(synthesized method)的源代码。除了生成getter、setter方法之外,编译器还要自动向类中添加成员变量(在属性名前面加下划线,以此作为实例变量的名字)。
实际流程:
每次增加一个属性,系统都会在 ivar_list 中添加一个成员变量的描述,在 method_list 中增加 setter 与 getter 方法的描述,在 prop_list 中增加一个属性的描述,计算该属性在对象中的偏移量,然后给出 setter 与 getter 方法对应的实现。
在 setter 方法中从偏移量的位置开始赋值,在 getter 方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转。
详细底层流程看这个《@property深入代码理解》
尝试手工创建存取器
@interface Person : NSObject
{
NSString *_name; //成员变量
NSUInteger _age;
}
- (void)setName:(NSString*)name;
- (NSString*)name;
- (void)setAge:(NSUInteger)age;
- (NSUInteger)age;
@end
上面的代码中_name和_age就是Person的实例变量,默认生成与原名一样的带下划线的成员变量,并且可以看到分别对这两个实例变量声明了get/set方法,即存取器。存取器就是对实例变量进行赋值和取值。按照约定赋值方法以set开头,取值方法以实例变量名命名。
实现方法:
@implementation Person
- (void)setName:(NSString*)name {
###注意:必须使用_name来赋值,使用self.name来设置值时编译器会自动转为调用该setter函数,会导致无限递归
###使用_name则是直接访问底层的存储属性,不会调用该方法来赋值
###这里使用copy是为了防止NSMutableString多态
_name = [name copy];
}
- (NSString*)name {
###必须使用_name来访问属性值,使用self.name来访问值时编译器会自动转为调用该getter函数,会造成无限递归
return _name;
}
- (void)setAge:(NSUInteger)age {
_age = age;
}
- (NSUInteger)age {
return _age;
}
@end
上述代码就是手动创建变量的getter和setter的实现,getter和setter本质就是符合一定命名规范的实例方法。
实例方法与点语法的调用:
Person *p = [[Person alloc] init];
//函数调用name的setter
[p setName:@"番茄"];
//函数调用age的setter
[p setAge:100];
//函数调用name和age的getter
NSLog(@"%@ %ld", [p name], [p age]);
*打印结果*
输出: 番茄 22
通过调用方式可以看出,setter和getter本质就是实例方法,可以通过函数调用的方式来使用。但是这种方式还是很费事的,所以为了方便使用,Objective-C允许使用点语法来访问getter和setter。
p.name = @"番茄";
p.age = 110;
使用点语法访问的方式本质还是调用了我们手动创建的setter和getter。 当有很多变量需要设置时,这样手工创建setter和getter的方式很操蛋了,因此合成存取方法就诞生了。
合成存取方法:
@interface Person : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
//编译器会帮我们自动生成_name和_age这两个实例变量,下面代码就可以正常使用这两个变量了
//也可以自定义命名例如:name = _myName;这样用下划线方式使用只能是_myName,不会再是_name了
@synthesize name = _name;
@synthesize age = _age;
@end
属性用@property声明默认是原子性(Atomicity)的,这会导致一些花销。当你不仅仅有一个线程, 那么getter和setter可能会在同一时间去调用,这就意味着getter/setter可能会被另一个方法打扰,很有可能造成数据错误。
在iOS中使用同步锁的开销比较大, 这会损耗性能。一般情况下并不要求属性必须是原子性(Atomicity),因为这并不能保证“线程安全”(thread safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才醒。因此,几乎所有代码的属性设置都会使用nonatomic,这样能够提高访问性能。
@property (nonatomic) NSString*model; //设置非原子性
@property NSString * name1; //默认是atomic 默认使用的strong关键字修饰
@property()NSString* nothing2; //默认是atomic
@property(copy) NSString * name3;
@end
NSMutableString * muString = [[NSMutableString alloc]initWithString:@"ceshi"];
self.name1 = muString;
self.name2 = muString;
self.name3 = muString;
//修改元数据
[muString appendString:@"123"];
NSLog(@"self.name1 : %@",self.name1);
NSLog(@"self.name2 : %@",self.name2);
NSLog(@"self.name3 : %@",self.name3);
*打印结果:*
self.name1 : ceshi123
self.name2 : ceshi123
self.name3 : ceshi
@property还有一些关键字,它们都是有特殊作用的,在声明属性(property)时候,常用关键字能够传递出相关行为的额外信息,比如上述代码中的nonatomic、strong、copy:
@property(nonatomic,strong) NSString *carName;
@property(nonatomic,strong) NSString *carType;
我把它们分为三类,分别是:原子性,存取器控制,内存管理。
原子性:atomic nonatomic
存取器控制:readwrite readonly
内存管理:assign、strong、weak、copy、retain
详细查看:《IOS 常用关键字基础大全》
还有点儿要注意的:
属性的setter方法和getter方法是不能同时进行重写的,这是因为,一旦你同时重写了这两个方法,那么系统就不会帮你生成这个成员变量了,所以会报错,
要同时重写setter与getter方法会报错.png
如果真的就想非要重写这个属性的setter和getter方法的话,就要手动生成成员变量,然后就可以重写了。或者是用 @synthesize name = _name;
网友评论