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的總結,更多內容有待進一步的探索,歡迎一塊兒學習討論。

相關文章
相關標籤/搜索