ObjC之RunTime(下)
之前通过学习官方文档对runtime有了初步的认识,接下来就要研究学习runtime到底能用在哪些地方,能如何改进我们的程序。
本文也可以从icocoa浏览。
Swizzling
Swizzling可以分为method swizzling和class(isa)swizzling两种。顾名思义就是将方法/类在运行时替换掉。
Method Swizzling
在运行时替换/修改某个方法——可以是自己写的方法也可以是系统的方法——当然一般是用于替换框架类中的方法。
//ZJView.m -Swizzling
+ (void)swizzleSetFrame
{
SEL originalSel = @selector(setFrame:);
Class myClass = [self class];
Method originMethod = class_getInstanceMethod(myClass, originalSel);
const char *originType = method_getTypeEncoding(originMethod);
originalIMP = (void *)method_getImplementation(originMethod);
class_replaceMethod(myClass, originalSel, (IMP)mySetFrame, originType); }
static void mySetFrame(id self, SEL _cmd,CGRect frame)
{
NSLog(@"run mySetFrame");
if (originalIMP)
{
frame.origin.y += 20;
originalIMP(self, _cmd, frame);
}
}
如上是在自定义的View类里替换了setFrame方法(注意这样做在实际的编码中没有意义,因为完全可以通过继承做到这一点,这里只是从代码的角度来理解method swizzling)。替换的方法可以放在+load里,或者自行显式的调用。这里需要注意的是stackoverflow上有很多是这样进行替换的:
if(class_addMethod() )
{
class_replaceMethod();
}
else
{
method_exchangeImplementations();
}
之所以按这样的流程处理是想先检测下class下是否有需要被替换的selector。但其实runtime已经考虑了这种情形,所以直接进行class_replaceMethod即可。 通常情况下,我们可以通过继承来重载某个方法,但对于没有继承关系的类的方法重载ObjC提供了Category。比如在iOS5前,要自定义NavigationBar的背景,我们就是通过创建一个category来重载drawRect。但是这样使用Category的话有以下弊端:
- 方法的原先实现被完全重载了,无法调用原先的实现。尤其是为库类中方法重载的时候,我们往往希望获得原先的实现,而不是简单的全盘替换。
- 如果有多个category的时候,无法保证哪一个胜出。
所以category往往用于给框架类添加方法。在这种情况下,method swizzling就是一个很好的选择。由于现在iOS的版本也日趋变多,有时也会遇到某些类的方法在不同iOS下有不同的表现。那么,我们就可以根据实际情况,征对不同的iOS版本,选择继续用默认的实现,或者自定义的实现,或者两者的结合。此外为了避免冲突,method swizzling最好在+load函数里调用。 鉴于在实践中使用method swizzling的场合较少,个人体会不够深刻,暂时个人理解只能在这个层次了。我在stakoverflow上找到一篇帖子,对于使用method swizzling需要注意的地方做了详尽的说明。
Class(isa) Swizzling
runtime通过object_setClass来动态的替换对象的class。值得注意的是新class的长度要和原先class的长度一致。此外,KVO的实现就是利用了isa swizzling,iOS6PTL中也对此进行了说明。比如对象a要观察b的某个属性,在添加observer的时候,系统会生成一个中间类,并把b的isa指针指向这个新类。这也说明因为是在runtime时处理KVO,使用KVO时一定要注意遵循相应的命名规范。
关联指针(Associative References)
关联指针指的是在runtime给某对象添加一个变量,添加的变量不会对原有的类产生任何影响——这是优于ObjC扩展(Extension)的地方,主要使用以下方法:
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);
对于框架类,由于没有源码,可以通过这种方式添加一些变量。比如UIView,UIAlertView都可以添加tag方便以后重新获得,我们也可以通过关联指针使其与某个对象相关联。
内省(Introspection)
好像也能叫Reflection(反射),不过我不确定。Introspection是OO语言都应该具备的特性,它指的是在runtime时对象通过请求可以查询自己类的关键信息的能力。首先ObjC语言本身就有这样的接口,比如:
-isKindOfClass:; -isMemberOfClass:
-respondsToSelector: ;-conformsToProtocol:
-isEqual:
这些分别对应着:
- 查看所属类以及类的继承关系;
- 查看是否实现了某个方法或者协议
- 判断对象是否相等
这些功能在NSObject类和协议里定义的,一般情况下,iOS上的类都能使用。 接下来要介绍两个开源库:Mantle 和Overcoat,他们是内省的重要应用。 在实际中,我们通常会在程序中设计一个Model层,用于Json和Object之间的转化。比较完备的Model类会考虑到:
- NSString属性
- 通过NSString生成的如NSURL等属性
- NSNumber,NSDate等属性的格式化
- 实现NSCoding,NSCopying协议等等
- 。。。。
一般情况下,程序里的Model不会只有一个,全部这样实现的话,很显然有很大一部分代码是“冗余”的但却不能通过继承之类的方法规避。Mantle就是一个很好的选择,它将你的注意力集中到Model的设计,实现部分只需要一些必须的方法,如:
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
//提供Json中key与Model中属性的对应,如果key与属性一致可以忽略不写
}
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key
{
//对一些需要进行格式化处理的key进行选择性操作
}
Introspection在Mantle中的应用就是runtime时通过class_copyPropertyList来获取类的属性列表,从而简化我们的工作。 至于Overcoat则是AFNetworking和Mantle的一个结合。AFNetworking是继ASINetwork后,iOS和OS X上出名的网络库,而且维护更新比较活跃。Overcoat的主要工作就是把通过AFnetworking获取的结果转化成对象,转化的过程就是使用了Mantle,并且把这部分工作放在了后台进行。Overcoat提供了一个例子ReadingList,大家可以好好研究下。注意ReadingList是需要使用到cocoapods的,不知道的朋友,out啦~
动态属性(方法)
之前提到过的,我们可以在runtime时添加方法,更进一步的,我们可以动态的添加属性而不用实现声明,下面的代码来自gist:
#import <objc/runtime.h>
#import <Foundation/Foundation.h> @interface Person : NSObject
@property (nonatomic,strong) NSMutableDictionary *properties;
@end @implementation Person -(id) init {
self = [super init];
if (self){
_properties = [NSMutableDictionary new];
}
return self;
} // generic getter
static id propertyIMP(id self, SEL _cmd) {
return [[self properties] valueForKey:NSStringFromSelector(_cmd)];
} // generic setter
static void setPropertyIMP(id self, SEL _cmd, id aValue) { id value = [aValue copy];
NSMutableString *key = [NSStringFromSelector(_cmd) mutableCopy]; // delete "set" and ":" and lowercase first letter
[key deleteCharactersInRange:NSMakeRange(0, 3)];
[key deleteCharactersInRange:NSMakeRange([key length] - 1, 1)];
NSString *firstChar = [key substringToIndex:1];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:[firstChar lowercaseString]]; [[self properties] setValue:value forKey:key];
} + (BOOL)resolveInstanceMethod:(SEL)aSEL {
if ([NSStringFromSelector(aSEL) hasPrefix:@"set"]) {
class_addMethod([self class], aSEL, (IMP)setPropertyIMP, "v@:@");
} else {
class_addMethod([self class], aSEL,(IMP)propertyIMP, "@@:");
}
return YES;
} @end int main(int argc, char *argv[]) {
@autoreleasepool {
Person *p = [Person new];
[p setName:@"Jon"];
NSLog(@"%@",[p name]);
}
}
以上是现阶段对runtime的总结,更多内容有待进一步的探索,欢迎一起学习讨论。
ObjC之RunTime(下)的更多相关文章
- ObjC之RunTime(上)
转载自这里. 最近看了一本书——iOS6 programming Pushing the Limits(亚马逊有中文版),最后一章是关于Deep ObjC的,主要内容是ObjC的runtime.虽然之 ...
- UE4 Runtime下动态给Actor添加组件
http://www.v5xy.com/?p=858 UE4的组件分为两种:USceneComponent, UActorComponent UActorComponent (NewObject(th ...
- 利用objc的runtime来定位次线程中unrecognized selector sent to instance的问题
昨天遇到一个仅仅有一行错误信息的问题: -[NSNull objectForKey:]: unrecognized selector sent to instance 0x537e068 因为这个问题 ...
- 构思一个在windows下仿objc基于动画层ui编程的ui引擎
用c/c++编程有些年了,十个指头可以数齐,在涉入iOS objc开发后,有种无比舒服的感觉,尤其在UI开发上. 在QuartzCore.framework下动画和透明窗口等许多效果的事都变得那么方便 ...
- 由objC运行时所想到的。。。
objC语言不仅仅有着面向对象的特点(封装,继承和多态),也拥有类似脚本语言的灵活(运行时),这让objC有着很多奇特的功能-可在运行时添加给类或对象添加方法,甚至可以添加类方法,甚至可以动态创建类. ...
- runtime作用
1.发送消息 方法调用的本质,就是让对象发送消息. objc_msgSend,只有对象才能发送消息,因此以objc开头. 使用消息机制前提,必须导入#import <objc/message.h ...
- runtime 运行机制2
Mike_zh QQ:82643885 end: blogTitle 博客的标题和副标题 博客园 首页 新随笔 联系 订阅 <a id="MyLinks1_XMLLink" ...
- iOS学习之Runtime(一)
一.Runtime简介 因为Objective-C是一门动态语言,所以它总是想办法把一些决定性工作从编译链接推迟到运行时,也就是说只有编译器是不够的,还需要一个运行时系统(runtime system ...
- iOS模式详解—「runtime面试、工作」看我就 🐒 了 ^_^.
Write in the first[写在最前] 对于从事 iOS 开发人员来说,当提到 ** runtime时,我想都可以说出来 「runtime 运行时」和基本使用的方法.相信很多开发者跟我当初一 ...
随机推荐
- webapp 的简单开发
web app 的技术平台很多,如adobe phonegap.sencha touch.appcan(国产).dcloud(国产)平台.我选择了dcloud平台,原因:简单,容易上手. web ap ...
- autocomplete 属性 清除input框输入存留历史值,防止下拉历史值显示
autocomplete 属性规定输入字段是否应该启用自动完成功能. 自动完成允许浏览器预测对字段的输入.当用户在字段开始键入时,浏览器基于之前键入过的值,应该显示出在字段中填写的选项. 注释:aut ...
- flex与相对定位在国内双核浏览器极速模式下的兼容性问题
在国内的浏览器中,360浏览器,QQ浏览器等绝大部分都是双核浏览器.双核浏览器即拥有IE兼容内核和非IE极速内核两个内核,分别对应兼容模式和极速模式.兼容模式时使用IE内核,极速模式采用webkit内 ...
- FPGA学习系列 各种门器件程序积累
1. 两输入与(and)门 entity and2gate is Port ( x : in STD_LOGIC; y : in STD_LOGIC; z : out STD_LOGIC);end a ...
- velecity报错:Caused by: org.apache.velocity.exception.ParseErrorException: Lexical error, Encountered: <EOF> after : "\'/order/pay?activity=\" + activityId);\r\n }*/\r\n</script>\r\n#end\r\n" at /a
Caused by: org.apache.velocity.exception.ParseErrorException: Lexical error, Encountered: <EOF> ...
- Nginx 性能参数优化
user www www; # ginx要开启的进程数 一般等于cpu的总核数,没必要开那么多,1个nginx内存消耗10兆左右 worker_processes 4; # 为每个进程分配cpu,上例 ...
- python三次输入错误验证登录
# login.py# 提示用户输入用户名和密码# 验证用户名和密码# 如果v错误,则输出用户名或密码错误# 如果成功,则输出欢迎,xxxnum = 0while True: name = input ...
- leetcode-surrounded regions-ZZ
Problem Statement (link): Given a 2D board containing 'X' and 'O', capture all regions surrounded by ...
- Windows 10 KMS 手工激活
第一.安装好Win10系统,不需要安装其他激活工具.第二.是删除默认序列号,打开命令提示符(管理员),运行 slmgr.vbs -upk,可提示已卸载了序列号. slmgr /ipk W269N-WF ...
- SAP成都研究院2018年总共87篇技术文章合集
2018年很快就要结束了.Jerry在2017年年底准备开始写这个公众号时,给自己定的目标是:2018年至少保证每周发布一篇高质量的文章.如今2018年就快过去了,高质量与否需要大家来反馈,至少从量上 ...