编写高质量代码是所有程序员的追求,《编写高质量代码》这本书对于改善Objective-C程序给了61建议。下面是个人的笔记。
1.尽量使用const、enum来替换预处理#define
相对于字符串字面量或数字,使用用常量来定义。
例如:
statis NSString *const kPageRefreshNotification = @"PageRefreshNotification";
statis const CGFloalt kTableViewHeight = 50.0;
enum兼有#define和const的所有优点,但只能用在整型中,所以在定义一个整型时,可以考虑使用枚举(enum)
例如:
enum{
MY_INT_CONST = 12345,
}
要点:
(1)尽量避免使用#define预处理命令。#define预处理命令不包括任何类型信息,仅仅是在编译前做替换操作。他们在重复定义是不会发出警告,容易在整个程序中产生不一致的值。
(2)在源文件(.m)中定义static const 类型常量应为无需全局引用,所以他们的名字不需要包含命名空间。
(3)在头文件(.h)中定;已的全局引用的常量,需要关联定义在源文件(.m)中的部分。因为需要被全局引用,所以他们的名字需要包含命名空间,通常用他们的类名作为命名前缀。
(4)尽量用ES_ENUM和NS_OPTIONS宏来实现枚举。
2.利用键-值机制访问类的私有成员变量和方法
在Objective-C中,类的成员变量或方法是没有绝对私有的,可以借助“runtime”机制进行对他们访问,键-值机制有三个常见的成员——键-值绑定(KVB)、键-值编码(KVC)和键-值观察(KVO)
KVC(Key-Value-Coding)
KVC主要利用一种使用字符串标识符,间接访问对象属性的机制,它是很多技术的基础。主要有(setValue:forKey,valueForKey)和(setValue:forKeyPath,valueForKeyPath)两种方法。这两种方法的使用用途,可以通过如下的代码来体现:
@interface Phone
{
NSStirng * phoneName;
}
@end
@interface Person
{
NSStirng* myName;
Phone *myPhone;
}
@end
@implementation Person
Phone *iPhone = ..... //phone对象
Person *xiaoming = ....//person对象
NSString *s1 = [iPhone valueForKey:@"phoneName"];
NSString *s2 = [xiaoming valueForKey:@"myName"];
NSString *s4 = [xiaoming valueForKeyPath:@"myPhone.phoneName"];//正确✅
NSString *s5 = [xiaoming valueForKey:@"myPhone.phoneName"];//错误❌
@end
注意:valueForKeyPath的值是一个路径(路径之间以点.分割),比如数据成员就是对象自己,寻值过程就会向下深入下去。
KVC实现原理是运用一个isa-swizzing技术。isa-swizzing就是类型混合指针机制,KVC通过isa-swizzing来实现其内部定位。
isa指针指向的是对象的类,这个类也是一个对象,有自己的权限。是根据类的定义编译而来。类对象负责维护一个方法调度表,非标本职上是指向类方法的指针组成的;类对象中还保留一个基类的指针,该指针又有自己的方法调度表和基类(还有所有通过集成得到的公共和保护的实例变量)。isa指针对消息分发机制和Cocoa对象的动态能力很关键。
例如下面的一行KVC代码:
[dict setValue:@"hello" forKey:@"nihao"];
就会被处理器编译成:
SEL sel = sel_get_uid ("setValue:forKey:");
IMP method = objc_msg_lookup (dict->isa,sel);
method(dict,sel,@"hello",@"nihao");
KVO(Key-Value Observing)
KVO通过实现名为NSKeyValueObserving的非正式协议,其作用是使对象可以将自己注册为其它对象的观察者。但被观察对象的属性之一发生改变时,会直接通知对应的观察者。Cocoa为遵循KVO对象的每个属性,都实现了自动观察者通知机制。
使用KVO,通常遵循如下流程:
1)注册与解除注册
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
这三个方法的定义在Foundation/NSKeyValueObserving.h中,NSObject、NSArray、NSSet均实现以上方法,因此我们不仅可以观察普通对象,还可以观察数组和集合类对象。注意,不要忘了解除注册,否则会造成资源泄漏。
2)处理变更通知
当被观察这类对象中的某个属性发生变更时,光插着需要处理接收到的变更通知。在观察类中,需要实现名为NSKeyValueObserving的category方法:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
其中,change这个字典保存了哪些变更的信息取决于注册时的NSKey-ValueObserving-Options
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
NSKeyValueObservingOptionNew = 0x01,
NSKeyValueObservingOptionOld = 0x02,
NSKeyValueObservingOptionInitial NS_ENUM_AVAILABLE(10_5, 2_0) = 0x04,
NSKeyValueObservingOptionPrior NS_ENUM_AVAILABLE(10_5, 2_0) = 0x08
};
3)手动或者自动实现KVO通知
KVO机制提供两种变更消息通知模式:手动和自动实现。在NSKeyValueObservingCustiomization的category中有方法:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;
默认情况下,KVO是自动实现的。其会自动调用NSKeyValueObserverNotification的category方法:
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
或:
- (void)willChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key;
- (void)didChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key;
又或:
- (void)willChangeValueForKey:(NSString *)key withSetMutation:(NSKeyValueSetMutationKind)mutationKind usingObjects:(NSSet *)objects;
- (void)didChangeValueForKey:(NSString *)key withSetMutation:(NSKeyValueSetMutationKind)mutationKind usingObjects:(NSSet *)objects;
KVB(Key-Value Binding)
KVB负责建立对象之间的绑定关系,以及移除和公布这种绑定关系。它用了几个非正式的协议。属性的绑定必须制定一个对象和一个指向该属性的键路径。
KVB实现的两个基本方法如下。
(1)为对象添加观察者:
OBserver addObserver:forKeyPath:(NSString *)keyPath options: context:;
(2) 观察者OBserver收到信息的处理函数
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
KVB和KVO最明显的使用场景就是在一些界面实时显示很强的地方,如股票走向、售票余额等,这种方式免去了自己操作通知的麻烦。
要点:
(1)在Objective-C中,类的成员变量或方法是没有绝对私有的,可以借助”编译运行时“机制,即”瞎子摸黑“机制来实现对他们的访问。
(2)KVC和KVO在定制子类的设计时特别重要。
(3)KVC、KVO和KVB都支持遍历。
(4)KVC主要通过isa指针来实现其内部查找定位。KVO其设计基于设计模式中的”观察者模式“。KVB和KVO最明显的使用场景就是在一些界面实时显示很强的地方。