第10条:在既有类中使用关联对象存放自定义数据
可以给某对象关联许多其他对象,这些对象通过“键”来区分。存储对象值的时候可以致命“存储策略”,用以维护相应的“内存管理语义”。
-
存储策略由名为objc_AssociationPolicy的枚举所定义,如下
存储策略.png
-
管理关联对象
- void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
此方法以给定的键和策略为某对象设置关联对象值。 - id objc_getAssociatedObject(id object, const void *key)
此方法根据给定的键从某对象中获取相应的关联对象值。 - void objc_removeAssociatedObjects(id object)
此方法移除指定对象的全部关联对象。
- 我们可以把某对象想象成NSDictionary,存储关联对象就相当于在NSDictionary对象上调用setObject和objectForKey方法,然而两者之间有个重要差别:设置关联对象用的key是个“不透明的指针”(opaque pointer),即其所指向的数据结构不局限于某个特定类型的指针(void *),鉴于此,在设置关联对象值时,通常使用静态全局变量做键。
代码示例:
static void *const stringKey = @"stringKey";
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSString *str = @"1234";
UIAlertView *alerView = [[UIAlertView alloc] initWithTitle:@"touchBegan" message:@"Message" delegate:self cancelButtonTitle:@"cancel" otherButtonTitles:@"continue", nil];
objc_setAssociatedObject(str, stringKey, alerView, OBJC_ASSOCIATION_RETAIN);
sleep(4);
UIAlertView *alerView1 = objc_getAssociatedObject(str, stringKey);
if (alerView1) {
[alerView1 show];
}
return;
}
这儿把UIAlertView的对象关联到NSString的key值中去了,然后再取出来
要点:
-
可以通过“关联对象”机制来把两个对象连起来。
-
定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系”与“非拥有关系”
-
只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于查找的bug。
第11条:理解object_msgSend的作用
- 动态绑定:所要调用的函数要到运行期才能确定
- id returnValue = [someObject messageName:parameter];
someObject -> 接收者(receiver)
messageName: ->选择子(selector)
messageName:parameter ->选择子+参数=消息(message)
上面OC消息传递底层用C语言实现如下:
void objc_msgSend(someObject, @selector(messageName:), parameter)
- 尾调用优化(tail-call optimization)技术细节参考: http://en.wikipedia.org/wiki/Tail_call
- Objective-C运行环境中objc_msgSend的特殊处理函数
- objc_msgSend_stret。如果待发送的消息要返回结构体,交由此函数处理。
- objc_msgSend_fpret。如果消息返回的是浮点数,交由此函数处理。
- objc_msgSendSuper。如果要给超类发消息,交由此函数处理
要点:
-
消息由接收者、选择子及参数构成。给某对象“发送消息”也就相当于在该对象上“调用方法”。
-
发给某对象的全部消息都要由“动态消息派发系统”来处理,该系统会查出对应的方法,并执行其代码
第12条:理解消息转发机制
-
代理就是采用了消息转发机制,自己不实现,通过转发机制转给delegate的对象去实现。
-
下列代码演示了如何使用“resolveInstanceMethod:” 来实现@dynamic属性
id autoDictionaryGetter(id self,SEL _cmd); void autoDictionarySetter(id self,SEL _cmd,id value); + (BOOL)resolveInstanceMethod:(SEL)sel { //* 将选择子化为字符串 */ NSString *selectorString = NSStringFromSelector(sel); if (/* selector is from a @dynamic property */) { //* 检测其是否表示设置方法,若前缀未set,则表示设置方法,否则就是获取方法 */ if ([selectorString hasPrefix:@"set"]) { class_addMethod(self, sel, (IMP)autoDictionarySetter, “v@:@“); } else { class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:"); } return YES; } return [super resolveInstanceMethod:sel]; }
v@:@ 为类型编码(Type Encodings),详细信息请到官网Type Encodings
- 消息转发流程图
消息转发流程图.png
我自己测的时候没有走forwarInvocation方法,不知道为什么。
要点:
- 若对象无法响应某个选择子,则进入消息转发流程。
- 通过运行期的动态方法解析功能,我们可以再需要用到某个方法时再将其加入类中。
- 对象可以把其无法解读的某些选择子转交给其他对象来处理。
- 经过上述两步之后,如果还是没办法处理选择子,那就启动完成的消息转发机制。
第13条:用“方法调配技术”调试“黑盒方法”
method swizzing 没啥好记录的
要点:
- 在运行期,可以向类中新增或替换选择子所对应的方法实现。
- 使用另一份实现来替换原有的方法实现,这道工序叫做“方法调配”,开发者常用此技术向原有实现中添加新功能。
- 一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用。若是滥用,反而会令代码变得不易读懂且难于维护
网友评论