之前通过学习官方文档对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的话有以下弊端:

  1. 方法的原先实现被完全重载了,无法调用原先的实现。尤其是为库类中方法重载的时候,我们往往希望获得原先的实现,而不是简单的全盘替换。
  2. 如果有多个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:

这些分别对应着:

  1. 查看所属类以及类的继承关系;
  2. 查看是否实现了某个方法或者协议
  3. 判断对象是否相等

这些功能在NSObject类和协议里定义的,一般情况下,iOS上的类都能使用。 接下来要介绍两个开源库:Mantle 和Overcoat,他们是内省的重要应用。 在实际中,我们通常会在程序中设计一个Model层,用于Json和Object之间的转化。比较完备的Model类会考虑到:

  1. NSString属性
  2. 通过NSString生成的如NSURL等属性
  3. NSNumber,NSDate等属性的格式化
  4. 实现NSCoding,NSCopying协议等等
  5. 。。。。

一般情况下,程序里的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(下)的更多相关文章

  1. ObjC之RunTime(上)

    转载自这里. 最近看了一本书——iOS6 programming Pushing the Limits(亚马逊有中文版),最后一章是关于Deep ObjC的,主要内容是ObjC的runtime.虽然之 ...

  2. UE4 Runtime下动态给Actor添加组件

    http://www.v5xy.com/?p=858 UE4的组件分为两种:USceneComponent, UActorComponent UActorComponent (NewObject(th ...

  3. 利用objc的runtime来定位次线程中unrecognized selector sent to instance的问题

    昨天遇到一个仅仅有一行错误信息的问题: -[NSNull objectForKey:]: unrecognized selector sent to instance 0x537e068 因为这个问题 ...

  4. 构思一个在windows下仿objc基于动画层ui编程的ui引擎

    用c/c++编程有些年了,十个指头可以数齐,在涉入iOS objc开发后,有种无比舒服的感觉,尤其在UI开发上. 在QuartzCore.framework下动画和透明窗口等许多效果的事都变得那么方便 ...

  5. 由objC运行时所想到的。。。

    objC语言不仅仅有着面向对象的特点(封装,继承和多态),也拥有类似脚本语言的灵活(运行时),这让objC有着很多奇特的功能-可在运行时添加给类或对象添加方法,甚至可以添加类方法,甚至可以动态创建类. ...

  6. runtime作用

    1.发送消息 方法调用的本质,就是让对象发送消息. objc_msgSend,只有对象才能发送消息,因此以objc开头. 使用消息机制前提,必须导入#import <objc/message.h ...

  7. runtime 运行机制2

    Mike_zh QQ:82643885 end: blogTitle 博客的标题和副标题 博客园 首页 新随笔 联系 订阅 <a id="MyLinks1_XMLLink" ...

  8. iOS学习之Runtime(一)

    一.Runtime简介 因为Objective-C是一门动态语言,所以它总是想办法把一些决定性工作从编译链接推迟到运行时,也就是说只有编译器是不够的,还需要一个运行时系统(runtime system ...

  9. iOS模式详解—「runtime面试、工作」看我就 🐒 了 ^_^.

    Write in the first[写在最前] 对于从事 iOS 开发人员来说,当提到 ** runtime时,我想都可以说出来 「runtime 运行时」和基本使用的方法.相信很多开发者跟我当初一 ...

随机推荐

  1. go的编译与重启

    ps -ef|grep pro-name| grep -v grep|awk '{print $2}'|xargs kill -9 > /dev/null go build nohup ./xe ...

  2. siriWave.js的demo

    demo.html <style>body { background: #000; }</style> <script src="../siriwave.js& ...

  3. MongoDB 创建集合

    createCollection() 方法 MongoDB db.createCollection(name, options) 是用来创建集合. 语法: 基本的 createCollection() ...

  4. [小北De编程手记] : Lesson 05 - Selenium For C# 之 API 下

    上一篇,我们介绍了一些Selenium WebDriver相关的API,下面我们就接着上一篇继续介绍Selenium常用的API,这一篇的内容主要涉及到以下话题: Selenium API:复杂事件处 ...

  5. hive中的bucket table

    前言 bucket table(桶表)是对数据进行哈希取值,然后放到不同文件中存储 应用场景 当数据量比较大,我们需要更快的完成任务,多个map和reduce进程是唯一的选择.但是如果输入文件是一个的 ...

  6. python的继承多态以及异常处理

    1.单继承 # 动物类 class Animal(object): def __init__(self, name): self. __name = name def run(self): print ...

  7. java img图片转pdf 工具类

    package com.elitel.hljhr.comm.web.main.controller; import java.io.File; import java.io.FileNotFoundE ...

  8. 【Leetcode】【Hard】Reverse Nodes in k-Group

    Given a linked list, reverse the nodes of a linked list k at a time and return its modified list. If ...

  9. 实验 MPLS LDP配置

    实验 MPLS LDP配置 一.学习目的 掌握启用和关闭MPLS的方法 掌握启用和关闭MPLS LDP配置方法 掌握使用MPLS LDP配置LSP的方法 二.拓扑图 三.场景 你是公司的网管员,公司的 ...

  10. windows server 2003安装Oracle webtier 32位因环境变量原因报错

    在服务中启动Oracle processer manager时报错:错误1053:服务没有及时响应启动或控制请求 原因是本系统还安装过BI和Oracle数据库等产品 解决方法:删除和本次安装无关的环境 ...