使用@property时发生了什么

        @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方法是不能同时进行重写的,这是因为,一旦你同时重写了这两个方法,那么系统就不会帮你生成这个成员变量了,所以会报错,


使用@property时发生了什么_第1张图片
要同时重写setter与getter方法会报错.png

如果真的就想非要重写这个属性的setter和getter方法的话,就要手动生成成员变量,然后就可以重写了。或者是用 @synthesize name = _name;

你可能感兴趣的:(使用@property时发生了什么)