股票场内基金交易,没时间盯盘?
本文可以看做是我对《Effective Objective-C 2.0》一书做的学习笔记,延续自前两篇《Objective-C 规范代码编写建议一》和《Objective-C 规范代码编写建议二》。
利用关联对象存放自定义数据
一般情况下我们会直接声明一个属性来记录数据,但有时这并不起作用。比如对于分类而言,它们既无法合成声明属性的实例变量,也不能用 interface – extension 来声明不可见的私有属性。这时,就可以利用 Objective-C 中一项强大特性解决问题——关联对象(Associated Object)。
1 2 3 4 5 6 7 |
// 为指定对象设置关联值 void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); // 提取关联值 id objc_getAssociatedObject(id object, const void *key); // 移除指定对象的全部关联值 objc_removeAssociatedObjects(id object); |
其中,objc_AssociationPolicy(关联策略)对应于属性中的特性,有如下几个类型:
1 2 3 4 5 6 |
OBJC_ASSOCIATION_ASSIGN // assign OBJC_ASSOCIATION_RETAIN_NONATOMIC // strong, nonatomic OBJC_ASSOCIATION_COPY_NONATOMIC // copy, nonatomic OBJC_ASSOCIATION_RETAIN // strong OBJC_ASSOCIATION_COPY // copy |
比如在某分类中声明了一个 BOOL 属性 isSomething,那么可以这样手动实现 setter 和 getter 方法:
1 2 3 4 5 6 7 8 9 10 11 |
#import <objc/runtime.h> - (void)setIsSomething:(BOOL)isSomething{ // objc_getAssociatedObject 函数返回的是 id 类型,因此需要包装成 NSNumber 对象 objc_setAssociatedObject(self, _cmd, @(isSomething), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (BOOL)isSomething{ return [objc_getAssociatedObject(self, @selector(setIsSomething:)) boolValue]; } |
需要说明的是,关联对象应该在其它方法都行不通是才考虑使用。首先这种键值对的形式在整体逻辑架构上就显得不够明确,降低了代码的可读性;其次,关联对象和关联值之间的关系并没有正式定义,其内存管理语义在关联时才会定义,如果出现 bug,很难查明并加以调试。
理解消息传递和消息转发机制
消息传递
对对象进行方法调用以 Objective-C 的术语来说,叫做“消息传递”。其中消息包括 name(名称)和 selector(选择器),可以接受参数,还可能有返回值。在向某对象传递消息时,Objective-C 会采用“动态绑定”机制,直到运行期才会确定需要调用的方法。
假设我们在程序中传递这样一个消息(调用方法):
1 2 |
id returnValue = [object methodName:parameter]; |
object 被称为“接收者”,methodName 即选择器,它与参数合起来就是一条消息。编译器看到消息后,会将其转换为一条 C 语言函数 objc_msgSend 调用,此函数也是消息传递机制的核心函数:
1 2 |
id returnValue = objc_msgSend(object, @selector(methodName:), parameter); |
objc_msgSend 函数会依据接收者和选择器的类型来调用适当方法。它会现在接收者的类中搜索器方法列表,当找到了与选择器名称相同的方法,就会跳转至其实现代码。如果找不到,则沿着接收者的继承体系向上查找。如果最终依然没有相符的方法,则会执行“消息转发”操作。
每个类都有一块叫做“快速映射表”的缓存,objc_msgSend 函数会将匹配出的结果缓存至其中,以保证多次调用时的执行速度。
消息转发
当对象接收到无法解读的消息时,就会启动“消息转发”机制,我们可以通过它告诉对象如何处理位置消息。
消息转发分为两大阶段:
动态方法解析
首先系统会征询接收者所属的类,看其是否可以动态添加方法,处理当前的未知选择器,这叫做“动态方法解析”;
接收者在收到未解读消息后,首先会调用其所属类中的类方法:
1 2 3 |
+ (BOOL)resolveInstanceMethod:(SEL)sel; // 针对对象方法 + (BOOL)resolveClassMethod:(SEL)sel; // 针对类方法 |
返回的 BOOL 值表示这个类是否能新增一个方法处理未知选择器。我们可以将相关实现代码写好后利用 class_addMethod 函数插入此类即可。此方案常用来实现 @dynamic 属性。
备援接收者和完整消息转发
如果第一阶段结束任然存在未知选择器,接收者就再也无法通过动态添加方法的手段来响应了。此时,系统会先征询接收者是否有其他可处理这条消息的对象,存在的话系统会将消息转给那个对象,称为“备援接收者”。其对应方法如下:
1 2 |
- (id)forwardingTargetForSelector:(SEL)aSelector; |
我们可以在这个方法中指定接收对象。
如果还是无法解决未知选择器,唯一能做的就是启用完整的消息转发机制了。系统会把与消息相关的全部细节,包括选择器、目标等参数都封装到 NSInvocation 对象中,给接收者最后一次机会处理当前消息。
此步骤会调用这个方法:
1 2 |
- (void)forwardInvocation:(NSInvocation *)anInvocation; |
其中的操作很简单:设置 anInvocation 对象的 target 属性即可。不过由于和备援接收者的实现方案等效,因此很少有人采用这种方式。
整个消息转发流程可以用一张图加以描述:
利用“方法调配”调试黑盒方法
当我们想编辑某一方法时,一般的做法是继承子类进行重写。不过在了解 runtime 机制后,我们可以利用其强大特性在运行期改变选择器对应的方法以达到目的,这种方案被称为“方法调配”(method swizzing)。相比于前一种方案只能将编辑后的方法作用于继承子类,“方法调配”方案可以直接在本类中生效,具有更广的影响范围。如果想在项目中统一修改某一控件的功能,“方法调配”五一是不错的选择。
通常我们会通过互换两个方法的方式实现“方法调配”。主要用到的函数是:
1 2 3 4 5 6 7 |
// 获取方法 Method class_getInstanceMethod(Class cls, SEL name); // 对象方法 Method class_getClassMethod(Class cls, SEL name); // 类方法 // 交换方法 void method_exchangeImplementations(Method m1, Method m2); |
下面可以做个小实验,为 NSString 的 stringByAppendingString: 方法添加打印功能。这里采用分类的形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#import "NSString+TIM_Extension.h" #import <objc/runtime.h> @implementation NSString (TIM_Extension) + (void)load{ Method originalMethod = class_getInstanceMethod(self, @selector(stringByAppendingString:)); Method swappedMethod = class_getInstanceMethod(self, @selector(tim_stringByAppendingString:)); method_exchangeImplementations(originalMethod, swappedMethod); } - (NSString *)tim_stringByAppendingString:(NSString *)string{ NSString *finalString = [self tim_stringByAppendingString:string]; NSLog(@"originalString is %@, finalString is %@", self, finalString); return finalString; } @end |
tim_stringByAppendingString: 方法看上去似乎陷入了调用自身的死循环,不过由于和 stringByAppendingString: 方法互换,因此实际上在运行时执行的是 stringByAppendingString: 方法。
将分类添加到项目中运行起来,会在打印栏中发现许多有意思的打印信息。
和关联对象一样,非特殊情况下并不特别推荐使用“方法调配”,它将会全局性的永久改动某个类的功能,若是滥用,反而会令代码不易读懂且难于维护。常见的用法和我们举的例子一样,为一些类增加打印功能以便于调试。当然,我上一篇文章中介绍的 UITableView+FDTemplateLayoutCell 框架也提供了一个出色的“方法调配”技术由于实例。
想获得去掉 5 元限制的证券账户吗?

如果您想去掉最低交易佣金 5 元限制,使用微信扫描左边小程序二维码,访问微信小程序「优财助手」,点击底部菜单「福利」,阅读文章「通过优财开证券账户无最低交易佣金 5 元限制」,按照文章步骤操作即可获得免 5 元证券账户,股票基金交易手续费率万 2.5。
请注意,一定要按照文章描述严格操作,如错误开户是无法获得免 5 元证券账户的。