iOS 属性关键字进阶篇
之前写过 《iOS属性关键字》基础篇,便于初学者理解,但没有从根本上去说明,属性的本质,这次就更进一步说明
属性的本质
ivar + getter + setter
- ivar 实例变量
- 存取方法(access method = getter + setter)
直白一点说,每个属性本身就是一个实例的封装,getter 和 setter 方法,就是这个实例开放给外界的外部接口,
我们可以根据 getter 和 setter 方法,访问这个实例。
举个例子:
@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
上述代码写出来的类与下面这种写法等效:
@interface Person : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end
runtime 中对属性的更新
property 在 runtime 中是 objc_property_t 定义如下:
typedef struct objc_property *objc_property_t;
而 objc_property 是一个结构体,包括 name 和 attributes,定义如下:
struct property_t {
const char *name;
const char *attributes;
};
这这其中 attributes 本质是 objc_property_attribute_t ,定义了 property 的一些属性,定义如下:
/// Defines a property attribute
typedef struct {
const char *name; /**< The name of the attribute */
const char *value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
而 attributes 的具体内容是什么呢?其实,包括:类型,原子性,内存语义和对应的实例变量。
例如:
我们定义一个 string 的 property @property (nonatomic, copy) NSString *string;,
通过 property_getAttributes(property)获取到 attributes 并打印出来之后的结果为 T@"NSString",C,N,V_string
其中 T 就代表类型,可参阅 Type Encodings C 就代表 Copy,N 代表 nonatomic,V 就代表对于的实例变量。
ivar、getter、setter 是如何生成并添加到这个类中的?
“自动合成”( autosynthesis)
完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”( autosynthesis )。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法”( synthesized method )的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为 _firstName 与 _lastName。也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字.
@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
其实相关的代码,会大致生成了五个东西
- OBJC_IVAR_$类名$属性名称 :该属性的“偏移量” (offset),这个偏移量是“硬编码” (hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。
- setter 与 getter 方法对应的实现函数
- ivar_list :成员变量列表
- method_list :方法列表
- prop_list :属性列表
我们每次在增加一个属性:
- 系统首先会在 ivar_list 中添加一个成员变量的描述
- 在 method_list 中增加 setter 与 getter 方法的描述,
- 在属性列表中增加一个属性的描述
然后计算该属性在对象中的偏移量,然后给出 setter 与 getter 方法对应的实现,在 setter 方法中从偏移量的位置开始赋值,在 getter 方法中从偏移量开始取值 , 为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.
反过来推一推一个程序如何从空到有
这里为什么会用 “空” ,而不是 “无”,因为我们所有的创造都不是完全从无开始的,像一个人也不是从无到有的,一个人的诞生也是从空到有,必须有“空”,至于什么是 “空”,就不赘述。
一个程序的诞生,必须先要有内存空间,这个内存空间就是整个程序的原点,也是它的空。而如何让空的东西变成有,我们就要赋予它规则,有规则后,才能一生二,二生三,三生万物,这便是天地最根本的道理。而程序,也是遵循这个道理,更透彻的说,一段程序,就是承载一段有规律 0 和 1 的集合。
😓本质总是这样简单又复杂
结束语
实际上,这本身就是一种封装,便于开发者更简单实现,基础类的定义;这也是为什么 OC 代码的编写方式 和 C 代码编写方式的不一样的地方。