zoukankan      html  css  js  c++  java
  • 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的话有以下弊端:

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

  • 相关阅读:
    计算机组成原理——辅助存储器
    什么是区块链?
    博客园添加背景音乐插件
    计算机组成原理——《深入理解计算机系统》|虚拟存储器
    计算机组成原理——主存储器考研题
    C++ 构造函数初始化列表
    C++ 运行时类别识别
    华为,加油!
    计算机组成原理——cache高速缓存存储器
    计算机组成原理——按字节编址与按字编址
  • 原文地址:https://www.cnblogs.com/scorpiozj/p/3405084.html
Copyright © 2011-2022 走看看