Method Swizzling的各種姿勢(shì)

不知道從哪里復(fù)制的~浮驳!非原創(chuàng)就是了??

因?yàn)镺bjective-C的runtime機(jī)制, Method Swizzling這個(gè)黑魔法解決了我們實(shí)際開發(fā)中諸多常規(guī)手段所無法解決的問題, 比如代碼的插樁,Hook,Patch等等. 我們首先看看常規(guī)的Method Swizzling是怎樣用的, NSHipster有一篇介紹基本用法的文章Method Swizzling, 我們就先以這篇文章中的示例開始說起吧:

@implementation UIViewController (Tracking)
 
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
 
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);
 
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
 
        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));
 
        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}
 
#pragma mark - Method Swizzling
 
- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}
 
@end

簡(jiǎn)要說明一下以上代碼的幾個(gè)重點(diǎn):
通過在Category的+ (void)load方法中添加Method Swizzling的代碼,在類初始加載時(shí)自動(dòng)被調(diào)用,load方法按照父類到子類,類自身到Category的順序被調(diào)用.
在dispatch_once中執(zhí)行Method Swizzling是一種防護(hù)措施,以保證代碼塊只會(huì)被執(zhí)行一次并且線程安全,不過此處并不需要,因?yàn)楫?dāng)前Category中的load方法并不會(huì)被多次調(diào)用.
嘗試先調(diào)用class_addMethod方法,以保證即便originalSelector只在父類中實(shí)現(xiàn),也能達(dá)到Method Swizzling的目的.
xxx_viewWillAppear:方法中[self xxx_viewWillAppear:animated];代碼并不會(huì)造成死循環(huán),因?yàn)镸ethod Swizzling之后, 調(diào)用xxx_viewWillAppear:實(shí)際執(zhí)行的代碼已經(jīng)是原來viewWillAppear中的代碼了.
其實(shí)以上的代碼也可以簡(jiǎn)寫為以下:

+ (void)load {
    Class class = [self class];
     
    SEL originalSelector = @selector(viewWillAppear:);
    SEL swizzledSelector = @selector(xxx_viewWillAppear:);
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    if (!originalMethod || !swizzledMethod) {
        return;
    }
     
    IMP originalIMP = method_getImplementation(originalMethod);
    IMP swizzledIMP = method_getImplementation(swizzledMethod);
    const char *originalType = method_getTypeEncoding(originalMethod);
    const char *swizzledType = method_getTypeEncoding(swizzledMethod);
     
    // 這兒的先后順序是有講究的,如果先執(zhí)行后一句,那么在執(zhí)行完瞬間方法被調(diào)用容易引發(fā)死循環(huán)
    class_replaceMethod(class,swizzledSelector,originalIMP,originalType);
    class_replaceMethod(class,originalSelector,swizzledIMP,swizzledType);
}

這是因?yàn)閏lass_replaceMethod方法其實(shí)能夠覆蓋到class_addMethod和method_setImplementation兩種場(chǎng)景, 對(duì)于第一個(gè)class_replaceMethod來說, 如果viewWillAppear:實(shí)現(xiàn)在父類, 則執(zhí)行class_addMethod, 否則就執(zhí)行method_setImplementation將原方法的IMP指定新的代碼塊; 而第二個(gè)class_replaceMethod完成的工作便只是將新方法的IMP指向原來的代碼.
但此處需要特別注意交換的順序,應(yīng)該優(yōu)化把新的方法指定原IMP,再修改原有的方法的IMP.

除了以上的場(chǎng)景之外,其它場(chǎng)景下我們?nèi)绾问褂肕ethod Swizzling呢?

  1. 在不同類之間實(shí)現(xiàn)Method Swizzling
    上面示例是通過Category來新增一個(gè)方法然后實(shí)現(xiàn)Method Swizzling的, 但有一些場(chǎng)景可能并不適合使用Category(比如私有的類,未獲取到該類的聲明), 此時(shí)我們應(yīng)該如何來做Method Swizzling呢?
    例如已知一個(gè)className為Car的類中有一個(gè)實(shí)例方法- (void)run:(double)speed, 目前需要Hook該方法對(duì)速度小于120才執(zhí)行run的代碼, 按照方法交換的流程, 代碼應(yīng)該是這樣的:
#import 
@interface MyCar : NSObject
@end
 
@implementation MyCar
 
+ (void)load {
    Class originalClass = NSClassFromString(@"Car");
    Class swizzledClass = [self class];
    SEL originalSelector = NSSelectorFromString(@"run:");
    SEL swizzledSelector = @selector(xxx_run:);
    Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
     
    // 向Car類中新添加一個(gè)xxx_run:的方法
    BOOL registerMethod = class_addMethod(originalClass,
                                          swizzledSelector,                                          method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
    if (!registerMethod) {
        return;
    }
     
    // 需要更新swizzledMethod變量,獲取當(dāng)前Car類中xxx_run:的Method指針
    swizzledMethod = class_getInstanceMethod(originalClass, swizzledSelector);
    if (!swizzledMethod) {
        return;
    }
     
    // 后續(xù)流程與之前的一致
    BOOL didAddMethod = class_addMethod(originalClass,
                                        originalSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {
        class_replaceMethod(originalClass,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}
 
- (void)xxx_run:(double)speed {
    if (speed < 120) {
        [self xxx_run:speed];
    }
}
 
@end

與之前的流程相比,在前面添加了兩個(gè)邏輯:
利用runtime向目標(biāo)類Car動(dòng)態(tài)添加了一個(gè)新的方法,此時(shí)Car類與MyCar類一樣具備了xxx_run:這個(gè)方法,MyCar的利用價(jià)值便結(jié)束了;
為了完成后續(xù)Car類中run:與xxx_run:的方法交換,此時(shí)需要更新swizzledMethod變量為Car中的xxx_run:方法所對(duì)應(yīng)的Method.
以上所有的邏輯也可以合并簡(jiǎn)化為以下:

+ (void)load {
    Class originalClass = NSClassFromString(@"Car");
    Class swizzledClass = [self class];
    SEL originalSelector = NSSelectorFromString(@"run:");
    SEL swizzledSelector = @selector(xxx_run:);
    Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
     
    IMP originalIMP = method_getImplementation(originalMethod);
    IMP swizzledIMP = method_getImplementation(swizzledMethod);
    const char *originalType = method_getTypeEncoding(originalMethod);
    const char *swizzledType = method_getTypeEncoding(swizzledMethod);
         
    class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);
    class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);
}

簡(jiǎn)化后的代碼便與之前使用Category的方式并沒有什么差異, 這樣代碼就很容易覆蓋到這兩種場(chǎng)景了, 但我們需要明確此時(shí)class_replaceMethod所完成的工作卻是不一樣的.
第一個(gè)class_replaceMethod直接在Car類中注冊(cè)了xxx_run:方法, 并且指定的IMP為當(dāng)前run:方法的IMP;
第二個(gè)class_replaceMethod與之前的邏輯一致, 當(dāng)run:方法是實(shí)現(xiàn)在Car類或Car的父類, 分別執(zhí)行method_setImplementation或class_addMethod;

  1. 如何實(shí)現(xiàn)類方法的Method Swizzling
    以上的代碼都是實(shí)現(xiàn)的對(duì)實(shí)例方法的交換, 那如何來實(shí)現(xiàn)對(duì)類方法的交換呢, 依舊直接貼代碼吧:
@interface NSDictionary (Test)
@end
 
@implementation NSDictionary (Test)
 
+ (void)load {
    Class cls = [self class];
    SEL originalSelector = @selector(dictionary);
    SEL swizzledSelector = @selector(xxx_dictionary);
     
    // 使用class_getClassMethod來獲取類方法的Method
    Method originalMethod = class_getClassMethod(cls, originalSelector);
    Method swizzledMethod = class_getClassMethod(cls, swizzledSelector);
    if (!originalMethod || !swizzledMethod) {
        return;
    }
     
    IMP originalIMP = method_getImplementation(originalMethod);
    IMP swizzledIMP = method_getImplementation(swizzledMethod);
    const char *originalType = method_getTypeEncoding(originalMethod);
    const char *swizzledType = method_getTypeEncoding(swizzledMethod);
     
    // 類方法添加,需要將方法添加到MetaClass中
    Class metaClass = objc_getMetaClass(class_getName(cls));
    class_replaceMethod(metaClass,swizzledSelector,originalIMP,originalType);
    class_replaceMethod(metaClass,originalSelector,swizzledIMP,swizzledType);
}
 
+ (id)xxx_dictionary {
    id result = [self xxx_dictionary];
    return result;
}
 
@end

相比實(shí)例方法的Method Swizzling,流程有兩點(diǎn)差異:
獲取Method的方法變更為class_getClassMethod(Class cls, SEL name),從函數(shù)命名便直觀體現(xiàn)了和class_getInstanceMethod(Class cls, SEL name)的差別;
對(duì)于類方法的動(dòng)態(tài)添加,需要將方法添加到MetaClass中,因?yàn)閷?shí)例方法記錄在class的method-list中, 類方法是記錄在meta-class中的method-list中的.

  1. 在類簇中如何實(shí)現(xiàn)Method Swizzling
    在上面的代碼中我們實(shí)現(xiàn)了對(duì)NSDictionary中的+ (id)dictionary方法的交換,但如果我們用類似代碼嘗試對(duì)- (id)objectForKey:(id)key方法進(jìn)行交換后, 你便會(huì)發(fā)現(xiàn)這似乎并沒有什么用.
    這是為什么呢? 平常我們?cè)赬code調(diào)試時(shí),在下方Debug區(qū)域左側(cè)的Variables View中,常常會(huì)發(fā)現(xiàn)如__NSArray或是__NSCFConstantString這樣的Class類型, 這便是在Foundation框架中被廣泛使用的類簇, 詳情請(qǐng)參看Apple文檔class cluster的內(nèi)容.
    所以針對(duì)類簇的Method Swizzling問題就轉(zhuǎn)變?yōu)槿绾螌?duì)這些類簇中的私有類做Method Swizzling, 在上面介紹的不同類之間做Method Swizzling便已經(jīng)能解決該問題, 下面一個(gè)簡(jiǎn)單的示例通過交換NSMutableDictionary的setObject:forKey:方法,讓調(diào)用這個(gè)方法時(shí)當(dāng)參數(shù)object或key為空的不會(huì)拋出異常:
@interface MySafeDictionary : NSObject
@end
 
@implementation MySafeDictionary
 
+ (void)load {
    Class originalClass = NSClassFromString(@"__NSDictionaryM");
    Class swizzledClass = [self class];
    SEL originalSelector = @selector(setObject:forKey:);
    SEL swizzledSelector = @selector(safe_setObject:forKey:);
    Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
     
    IMP originalIMP = method_getImplementation(originalMethod);
    IMP swizzledIMP = method_getImplementation(swizzledMethod);
    const char *originalType = method_getTypeEncoding(originalMethod);
    const char *swizzledType = method_getTypeEncoding(swizzledMethod);
     
    class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);
    class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);
}
 
- (void)safe_setObject:(id)anObject forKey:(id)aKey {
    if (anObject && aKey) {
        [self safe_setObject:anObject forKey:aKey];
    }
    else if (aKey) {
        [(NSMutableDictionary *)self removeObjectForKey:aKey];
    }
}
 
@end
  1. 在Method Swizzling之后如何恢復(fù)
    使用了Method Swizzling的各種姿勢(shì)之后, 是否有考慮如何恢復(fù)到交換之前的現(xiàn)場(chǎng)呢?
    一種方案就是通過一個(gè)開關(guān)標(biāo)識(shí)符, 如果需要從邏輯上面恢復(fù)到交換之前, 就設(shè)置一下這個(gè)標(biāo)識(shí)符, 在實(shí)現(xiàn)中判定如果設(shè)定了該標(biāo)識(shí)符, 邏輯就直接調(diào)用原方法的實(shí)現(xiàn), 其它什么事兒也不干, 這是目前大多數(shù)代碼的實(shí)現(xiàn)方法, 當(dāng)然也是非常安全的方式, 只不過當(dāng)交換方法過多時(shí), 每一個(gè)交換的方法體中都需要增加這樣的邏輯, 并且也需要維護(hù)大量這些標(biāo)識(shí)符變量, 只是會(huì)覺得不夠優(yōu)雅, 所以此處也就不展開詳細(xì)討論了.
    那下面來討論一下有沒有更好的方案, 以上描述的Method Swizzling各種場(chǎng)景和處理的技巧, 但綜合總結(jié)之后最核心的其實(shí)也只做了兩件事情:
    class_addMethod 添加一個(gè)新的方法, 可能是把其它類中實(shí)現(xiàn)的方法添加到目標(biāo)類中, 也可能是把父類實(shí)現(xiàn)的方法添加一份在子類中, 可能是添加的實(shí)例方法, 也可能是添加的類方法, 總之就是添加了方法.
    交換IMP 交換方法的實(shí)現(xiàn)IMP,完成這個(gè)步驟除了使用method_exchangeImplementations這個(gè)方法外, 也可以是調(diào)用了method_setImplementation方法來單獨(dú)修改某個(gè)方法的IMP, 或者是采用在調(diào)用class_addMethod方法中設(shè)定了IMP而直接就完成了IMP的交換, 總之就是對(duì)IMP的交換.
    那我們來分別看一下這兩件事情是否都還能恢復(fù):
    對(duì)于class_addMethod, 我們首先想到的可能就是有沒有對(duì)應(yīng)的remove方法呢, 在Objective-C 1.0的時(shí)候有class_removeMethods這個(gè)方法, 不過在2.0的時(shí)候就已經(jīng)被禁用了, 也就是蘋果并不推薦我們這樣做, 想想似乎也是挺有道理的, 本來runtime的接口看著就挺讓人心驚膽戰(zhàn)的, 又是添加又是刪除總覺得會(huì)出岔子, 所以只能放棄remove的想法, 反正方法添加在那兒倒也沒什么太大的影響.
    針對(duì)IMP的交換, 在Method Swizzling時(shí)做的交換動(dòng)作, 如果需要恢復(fù)其實(shí)要做的動(dòng)作還是交換回來罷了, 所以是可以做到的, 不過需要怎樣做呢? 對(duì)于同一個(gè)類, 同一個(gè)方法, 可能會(huì)在不同的地方被多次做Method Swizzling, 所以要回退某一次的Method Swizzling, 我們就需要記錄下來這一次交換的時(shí)候是哪兩個(gè)IMP做了交換, 恢復(fù)的時(shí)候再換回來即可. 另一個(gè)問題是如果已經(jīng)經(jīng)過多次交換, 我們?cè)鯓诱业竭@兩個(gè)IMP所對(duì)應(yīng)的Mehod呢, 還好runtime提供了一個(gè)class_copyMethodList方法, 可以直接取出Method列表, 然后我們就可以逐個(gè)遍歷找到IMP所對(duì)應(yīng)的Method了, 下面是對(duì)上一個(gè)示例添加恢復(fù)之后實(shí)現(xiàn)的代碼邏輯:
#import @interface MySafeDictionary : NSObject
@end
 
static NSLock *kMySafeLock = nil;
static IMP kMySafeOriginalIMP = NULL;
static IMP kMySafeSwizzledIMP = NULL;
 
@implementation MySafeDictionary
 
+ (void)swizzlling {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        kMySafeLock = [[NSLock alloc] init];
    });
     
    [kMySafeLock lock];
     
    do {
        if (kMySafeOriginalIMP || kMySafeSwizzledIMP) break;
         
        Class originalClass = NSClassFromString(@"__NSDictionaryM");
        if (!originalClass) break;
         
        Class swizzledClass = [self class];
        SEL originalSelector = @selector(setObject:forKey:);
        SEL swizzledSelector = @selector(safe_setObject:forKey:);
        Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
        if (!originalMethod || !swizzledMethod) break;
         
        IMP originalIMP = method_getImplementation(originalMethod);
        IMP swizzledIMP = method_getImplementation(swizzledMethod);
        const char *originalType = method_getTypeEncoding(originalMethod);
        const char *swizzledType = method_getTypeEncoding(swizzledMethod);
         
        kMySafeOriginalIMP = originalIMP;
        kMySafeSwizzledIMP = swizzledIMP;
         
        class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);
        class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);
    } while (NO);
     
    [kMySafeLock unlock];
}
 
+ (void)restore {
    [kMySafeLock lock];
     
    do {
        if (!kMySafeOriginalIMP || !kMySafeSwizzledIMP) break;
         
        Class originalClass = NSClassFromString(@"__NSDictionaryM");
        if (!originalClass) break;
         
        Method originalMethod = NULL;
        Method swizzledMethod = NULL;
        unsigned int outCount = 0;
        Method *methodList = class_copyMethodList(originalClass, &outCount);
        for (unsigned int idx=0; idx < outCount; idx++) {
            Method aMethod = methodList[idx];
            IMP aIMP = method_getImplementation(aMethod);
            if (aIMP == kMySafeSwizzledIMP) {
                originalMethod = aMethod;
            }
            else if (aIMP == kMySafeOriginalIMP) {
                swizzledMethod = aMethod;
            }
        }
        // 盡可能使用exchange,因?yàn)樗莂tomic的
        if (originalMethod && swizzledMethod) {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
        else if (originalMethod) {
            method_setImplementation(originalMethod, kMySafeOriginalIMP);
        }
        else if (swizzledMethod) {
            method_setImplementation(swizzledMethod, kMySafeSwizzledIMP);
        }
        kMySafeOriginalIMP = NULL;
        kMySafeSwizzledIMP = NULL;
    } while (NO);
     
    [kMySafeLock unlock];
}
 
- (void)safe_setObject:(id)anObject forKey:(id)aKey {
    if (anObject && aKey) {
        [self safe_setObject:anObject forKey:aKey];
    }
    else if (aKey) {
        [(NSMutableDictionary *)self removeObjectForKey:aKey];
    }
}
 
@end

注意 這段代碼的Method Swizzling和恢復(fù)都需要主動(dòng)調(diào)用, 并且相比上面其它的示例, 這段代碼還添加如鎖機(jī)制來加之保護(hù). 這個(gè)示例是以不同的類來實(shí)現(xiàn)的Method Swizzling和恢復(fù), 如果是Category或者是類方法, 根據(jù)之前的示例也需要做相應(yīng)的調(diào)整.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胆萧,一起剝皮案震驚了整個(gè)濱河市温兼,隨后出現(xiàn)的幾起案子谱轨,更是在濱河造成了極大的恐慌批旺,老刑警劉巖史辙,帶你破解...
    沈念sama閱讀 221,331評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菌仁,死亡現(xiàn)場(chǎng)離奇詭異浩习,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)济丘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門谱秽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人摹迷,你說我怎么就攤上這事疟赊。” “怎么了峡碉?”我有些...
    開封第一講書人閱讀 167,755評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵近哟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我鲫寄,道長(zhǎng)吉执,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,528評(píng)論 1 296
  • 正文 為了忘掉前任地来,我火速辦了婚禮戳玫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘未斑。我一直安慰自己咕宿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,526評(píng)論 6 397
  • 文/花漫 我一把揭開白布蜡秽。 她就那樣靜靜地躺著府阀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪载城。 梳的紋絲不亂的頭發(fā)上肌似,一...
    開封第一講書人閱讀 52,166評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音诉瓦,去河邊找鬼川队。 笑死,一個(gè)胖子當(dāng)著我的面吹牛睬澡,可吹牛的內(nèi)容都是我干的固额。 我是一名探鬼主播,決...
    沈念sama閱讀 40,768評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼煞聪,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼斗躏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起昔脯,我...
    開封第一講書人閱讀 39,664評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤啄糙,失蹤者是張志新(化名)和其女友劉穎笛臣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隧饼,經(jīng)...
    沈念sama閱讀 46,205評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沈堡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,290評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了燕雁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诞丽。...
    茶點(diǎn)故事閱讀 40,435評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖拐格,靈堂內(nèi)的尸體忽然破棺而出僧免,到底是詐尸還是另有隱情,我是刑警寧澤捏浊,帶...
    沈念sama閱讀 36,126評(píng)論 5 349
  • 正文 年R本政府宣布懂衩,位于F島的核電站,受9級(jí)特大地震影響呛伴,放射性物質(zhì)發(fā)生泄漏勃痴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,804評(píng)論 3 333
  • 文/蒙蒙 一热康、第九天 我趴在偏房一處隱蔽的房頂上張望沛申。 院中可真熱鬧,春花似錦姐军、人聲如沸铁材。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)著觉。三九已至,卻和暖如春惊暴,著一層夾襖步出監(jiān)牢的瞬間饼丘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工辽话, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肄鸽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,818評(píng)論 3 376
  • 正文 我出身青樓油啤,卻偏偏與公主長(zhǎng)得像典徘,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子益咬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,442評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20161102/17920.html 因?yàn)镺b...
    F麥子閱讀 669評(píng)論 0 1
  • 因?yàn)镺bjective-C的runtime機(jī)制, Method Swizzling這個(gè)黑魔法解決了我們實(shí)際開發(fā)中諸...
    SuAdrenine閱讀 508評(píng)論 0 1
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉逮诲,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,725評(píng)論 0 9
  • 我們常常會(huì)聽說 Objective-C 是一門動(dòng)態(tài)語言,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,199評(píng)論 0 7
  • Method Swizzling參考資料 1.用到的運(yùn)行時(shí)基礎(chǔ)知識(shí)介紹 SEL : 方法選擇器,SEL是函數(shù)ob...
    shannoon閱讀 1,370評(píng)論 0 7