注:该读书笔记是为参加牛客网举办的“有书共读”活动所写,按照约定会在牛客网发布。

第6条 理解“属性”这一概念

6.1 实例变量

@inteface EOCPerson : NSObject {
@public
  NSString *_firstName;
  NSString *_lastName;
@private
  NSString *_someInternalData;
}

这样可以定义实例变量,并定义其作用域。但编写Objecitve-C代码的时候却很少这么做。

这种做法的问题是:对象布局在编译期就已经固定了,只要一碰到访问_firstName的代码,编译器就把其替换为“偏移量”,这个偏移量是“硬编码”,表示该变量距离存放对象的内存区域的起始地址有多远。

如果这个类在前面或中间增加了实例变量,全部或部分实例变量的偏移量就会改变,那么,其他访问了这些实例变量的代码,就都需要重新编译,否则就会出错。

Objective-C的解决方法:

一、

把实例变量当做一种存储偏移量所用的特殊变量,由“类对象”保管。

偏移量会在运行期查找,这样的话就不必重新编译使用实例变量的类,甚至可以在运行期间增加新的实例变量。

这就是稳固的“应用程序二进制接口”(ABI),其中就有定义了生成代码时所应遵循的规范。

有了ABI,我们就可以在分类或实现文件中定义实例变量了。

二、

另一个方法是,不直接访问实例变量,而通过存取方法来做。Objective-C对存取方法有着严格的命名规范。所以Objective-C才能根据名称自动创建出存取方法。

6.2 属性

属性,可以认为是:编译器自动创建出一套存取方法,用以访问给定类型中具有给定名称的变量。

@interface EOCPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end

以上代码会生成_firstName_lastName的实例变量。

如果需要指定实例变量的名字,可以在类的实现代码中通过@synthesize语法来指定实例变量的名字:

@implementation EOCPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end

除了自己实现存取方法,还可以使用@dynamic关键字,告诉编译器:不要自动创建属性所用的实例变量,也不要为其创建存取方法。编译器相信这些方法能在运行时找到,所以需要在运行期动态创建存取方法。如,从CoreData框架中的NSManagedObject类里继承了一个子类,那么就需要在运行期动态创建存取方法。之所以要这么做,是因为子类中的某些属性不是实例变量,其数据来自远方的数据库中。

@implementation EOCPerson
@dynamic firstName, lastName;
@end

属性可以拥有原子性(atomicity,nonatomic)、读写权限(readwrite、readonly)、内存管理语义(assign、strong、weak、unsafe_unretained、copy)、方法名(getter=<name>、setter=<name>)的属性特质。</name></name>

具备atomic特质的获取方***通过锁定机制来确保其原子性。

第7条 在对象内部尽量直接访问实例变量

  • 在init方法以及dealloc方法中,总是应该直接通过实例变量来读写数据。
  • 直接访问不经过Objective-C的方法派发,所以速度比较快。在这种情况下,编译器所生成的代码会直接访问保存对象实例变量那块内存。
  • 直接访问实例变量时,因为没有经过get和set的方法,所以会绕过相关属性所定义的“内存管理语义”,如果在ARC下直接访问一个声明为copy的属性,那么并不会拷贝该属性,只会保留新值释放旧值。
  • 如果直接访问实例变量,那么不会触发“键值观察”(KVO),需要手动设置。
  • 通过属性来访问可以在getter和setter中打断点查看堆栈信息,更方便调试相关信息。

一个比较合理的折中方案则是,在写入变量时,用setter做,而在读取变量时,则直接用实例变量来访问。这样既能提高读取操作的速度,又能控制对属性的写入操作。之所以要通过setter来写入,其首要原因在于,这样做能够确保相关属性的“内存管理语义”得到贯彻。

第8条 理解“对象等同性”的概念

  • ==比较的是两个指针本身

  • isEqual是NSObject协议中声明的方法,用于判断两个对象的等同性,返回一个BOOL值;hash方法是生成对象的哈希值,返回一个NSUInteger值。

  • 对于一些特定的类,有设定了默认的表示对象等同性的方法,如:

    • NSString:isEqualToString
    • NSArray:isEqualToArray
    • NSDictionary:isEqualToDictionary
  • 有些类还会有identifier方法,返回一个标识符,根据标识符来判断等同性。

第9条 以“类族模式”隐藏实现细节

就是定义一个抽象基类,然后实现若干个子类,最后在抽象基类增加+的方法用于创建子类对象。用户无需知道实现细节,就可以创建具体的实现类。

使用isKindOfClass:可以判断某个对象是否属于某个基类。

第10条 在既有类中使用关联对象存放自定义数据

可以给某个对象关联许多其他的对象,这些对象通过key来区分。存储时还能指定存储策略,用以维护相应的“内存管理语义”。

存储策略由名为objc_AssociationPolicy的枚举所定义。加入关联对象成为了属性,那么它就会具备与策略对应的语义。

关联类型 等效的@property属性
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatonic, retain
OBJC_ASSOCIATION_COPY_NONATIOMIC nonatomic, copy
OBJC_ASSOCIATION_RETAIN retain
OBJC_ASSOCIATION_COPY Copy
  • objc_setAssociatedObject(obj,key,,value,associationPolicy)设置关联对象
  • objc_getAssociatedObject(obj, key)获得key对应的关联对象
  • objc_removeAssociatedObject(obj)移除关联对象

可以将关联对象看做NSDictionary,但是设置关联对象的键,必须是同一个指针,才判断为相等。因此一般设置关联对象的值时通常使用静态全局变量做键。