ObjC之RunTime(下)

以前经过学习官方文档对runtime有了初步的认识,接下来就要研究学习runtime到底能用在哪些地方,能如何改进咱们的程序。html

本文也能够从icocoa浏览。ios

Swizzling

Swizzling能够分为method swizzling和class(isa)swizzling两种。顾名思义就是将方法/类在运行时替换掉。git

Method Swizzling

在运行时替换/修改某个方法——能够是本身写的方法也能够是系统的方法——固然通常是用于替换框架类中的方法。github

//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上有不少是这样进行替换的:objective-c

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须要注意的地方作了详尽的说明。app

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的总结,更多内容有待进一步的探索,欢迎一块儿学习讨论。

相关文章
相关标签/搜索