Method Swizzling的各種姿勢(shì)

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

#import 
@implementation UIViewController (Tracking)
+ (void)load {
  staticdispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    Classclass= [self class];
    SEL originalSelector = @selector(viewWillAppear:);
    SEL swizzledSelector = @selector(xby_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)xby_viewWillAppear:(BOOL)animated {
  [self xby_viewWillAppear:animated];//因?yàn)榉椒ㄌ鎿Q,這里相當(dāng)于調(diào)用的是系統(tǒng)的viewWillAppear,所以自然也就不會(huì)造成循環(huán)調(diào)用的問題
  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的目的.
xby_viewWillAppear:方法中[self xby_viewWillAppear:animated];代碼并不會(huì)造成死循環(huán),因?yàn)镸ethod Swizzling之后,調(diào)用xby_viewWillAppear:實(shí)際執(zhí)行的代碼已經(jīng)是原來viewWillAppear中的代碼了.
其實(shí)以上的代碼也可以簡(jiǎn)寫為以下:

+ (void)load {
  Classclass= [self class];
  SEL originalSelector = @selector(viewWillAppear:);
  SEL swizzledSelector = @selector(xby_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(xby_run:);

  Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
  Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);

  // 向Car類中新添加一個(gè)xby_run:的方法
  BOOL registerMethod = class_addMethod(originalClass, swizzledSelector,
method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
  if(!registerMethod) {
    return;
  }
  
  // 需要更新swizzledMethod變量,獲取當(dāng)前Car類中xby_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)xby_run:(double)speed {
  if(speed < 120) {
    [self xby_run:speed];
  }
}
@end

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

+ (void)load {
  Class originalClass = NSClassFromString(@"Car");
  Class swizzledClass = [selfclass];

  SEL originalSelector = NSSelectorFromString(@"run:");
  SEL swizzledSelector = @selector(xby_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è)了xby_run:方法,并且指定的IMP為當(dāng)前run:方法的IMP;
第二個(gè)class_replaceMethod與之前的邏輯一致,當(dāng)run:方法是實(shí)現(xiàn)在Car類或Car的父類,分別執(zhí)行method_setImplementation或class_addMethod;

2.如何實(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(xby_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)xby_dictionary {
  id result = [self xby_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中的.

3.在類簇中如何實(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)如__NSArrayI或是__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 = [selfclass];

  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);
constchar*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

4.在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 = [selfclass];
    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閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異尊浓,居然都是意外死亡逞频,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門栋齿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苗胀,“玉大人,你說我怎么就攤上這事瓦堵』” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵菇用,是天一觀的道長澜驮。 經(jīng)常有香客問我,道長惋鸥,這世上最難降的妖魔是什么杂穷? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮卦绣,結(jié)果婚禮上耐量,老公的妹妹穿的比我還像新娘。我一直安慰自己滤港,他們只是感情好廊蜒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著溅漾,像睡著了一般山叮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上添履,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天屁倔,我揣著相機(jī)與錄音,去河邊找鬼缝龄。 笑死汰现,一個(gè)胖子當(dāng)著我的面吹牛挂谍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瞎饲,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼口叙,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了嗅战?” 一聲冷哼從身側(cè)響起妄田,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驮捍,沒想到半個(gè)月后疟呐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡东且,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年启具,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片珊泳。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鲁冯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出色查,到底是詐尸還是另有隱情薯演,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布秧了,位于F島的核電站跨扮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏验毡。R本人自食惡果不足惜衡创,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望米罚。 院中可真熱鬧钧汹,春花似錦丈探、人聲如沸录择。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隘竭。三九已至,卻和暖如春讼渊,著一層夾襖步出監(jiān)牢的瞬間动看,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國打工爪幻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留菱皆,地道東北人须误。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像仇轻,于是被迫代替她去往敵國和親京痢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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

  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20161102/17920.html 因?yàn)镺b...
    F麥子閱讀 668評(píng)論 0 1
  • 不知道從哪里復(fù)制的~篷店!非原創(chuàng)就是了?? 因?yàn)镺bjective-C的runtime機(jī)制, Method Swizzl...
    Bearger閱讀 487評(píng)論 0 0
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉祭椰,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,715評(píng)論 0 9
  • 我們常常會(huì)聽說 Objective-C 是一門動(dòng)態(tài)語言,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢疲陕?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,195評(píng)論 0 7
  • Method Swizzling參考資料 1.用到的運(yùn)行時(shí)基礎(chǔ)知識(shí)介紹 SEL : 方法選擇器,SEL是函數(shù)ob...
    shannoon閱讀 1,365評(píng)論 0 7