iOS Runtime詳解與應(yīng)用

一:runtime簡單介紹
默認(rèn)項(xiàng)目中使用runtime代碼是不可以的,需要進(jìn)行下面設(shè)置
Project-->Build Setting--> Enable Strict Checking of objc_msgSend Calls 修改為NO即可

二:runtime涵蓋內(nèi)容
1.objc/message 消息發(fā)送和轉(zhuǎn)發(fā)機(jī)制
objc_msgSend 負(fù)責(zé)動態(tài)消息發(fā)送
_objc_msgForward 負(fù)責(zé)消息轉(zhuǎn)發(fā)
I.代碼如下:

struct objc_super {

   __unsafe_unretained _Nonnull id receiver;

   __unsafe_unretained _Nonnull Class class;
 
   __unsafe_unretained _Nonnull Class super_class;

};

OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

OBJC_EXPORT void
method_invoke(void /* id receiver, Method m, ... */ )

OBJC_EXPORT id _Nullable
_objc_msgForward(id _Nonnull receiver, SEL _Nonnull sel, ...)

II.消息轉(zhuǎn)發(fā)機(jī)制

2.objc/objc

typedef struct objc_class *Class;
struct objc_object {
    Class isa;
};
typedef struct objc_object *id;
typedef struct objc_selector *SEL;

 //sel和object方法
const char * _Nonnull   sel_getName(SEL _Nonnull sel)
SEL _Nonnull            sel_registerName(const char * _Nonnull str)
BOOL                    sel_isMapped(SEL _Nonnull sel)
SEL _Nonnull            sel_getUid(const char * _Nonnull str)
const  char * _Nonnull  object_getClassName(id _Nullable obj)
void * _Nullable        object_getIndexedIvars(id _Nullable obj)        

3.objc/runtime runtime核心內(nèi)容
I.objc_class結(jié)構(gòu)體

typedef struct objc_method *Method;
typedef struct objc_ivar *Ivar; 
typedef struct objc_category *Category;
typedef struct objc_property *objc_property_t;
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
   #endif

} OBJC2_UNAVAILABLE;

II.屬性澳化,方法,變量獲取常用方法
參考鏈接:http://www.reibang.com/p/cefa1da5e775
代碼如下:

//1.屬性
u_int count;
objc_property_t *properties = class_copyPropertyList([Person class], &count);
NSMutableArray *propertiesArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i <count; i++) {
    const char *propertyName = property_getName(properties[i]);
    //包含類型奈懒,修飾詞等內(nèi)容 參考:上面鏈接
    const char *propertyAttributes = property_getAttributes(properties[i]);
    [propertiesArray addObject:[NSString stringWithUTF8String:propertyName]];
}
free(properties);
NSLog(@"----%@",propertiesArray);


//2.方法
unsigned int methodCount;
Method *methods = class_copyMethodList([Person class], &methodCount);
NSMutableArray *methodArray = [NSMutableArray arrayWithCapacity:methodCount];
for (int i = 0; i <count; i++) {
    Method temp = methods[i];
    IMP imp = method_getImplementation(temp);
    SEL name_f = method_getName(temp);
    const char *name_s = sel_getName(name_f);
    int arguments = method_getNumberOfArguments(temp);
    const char *encoding = method_getTypeEncoding(temp);
    NSLog(@"方法名:%@,參數(shù)個數(shù):%d,編碼方式:%@",[NSString stringWithUTF8String:name_s],arguments,[NSString stringWithUTF8String:encoding]);
    [methodArray addObject:[NSString stringWithUTF8String:name_s]];
}
free(methods);
NSLog(@"----%@",methodArray);


//3.全局變量
u_int ivarcount;
Ivar *ivars = class_copyIvarList([Person class], &ivarcount);
NSMutableArray *ivarArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i <ivarcount; i++) {
    const char *ivarName = ivar_getName(ivars[i]);
    [ivarArray addObject:[NSString stringWithUTF8String:ivarName]];
}
free(ivars);
NSLog(@"----%@",ivarArray);

III.方法交換常用的經(jīng)典代碼

+ (void)load {
    Class class = [self class];
    SEL origSEL = @selector(viewWillAppear:);
    SEL replaceSEL = @selector(viewWillAppear:);

    Method originMethod = class_getInstanceMethod(class, origSEL);
    Method replaceMethod = class_getInstanceMethod(class, replaceSEL);

    IMP origIMP = class_getMethodImplementation_stret(class, origSEL);
    IMP replaceIMP = class_getMethodImplementation_stret(class, replaceSEL);

   //先為原始方法添加實(shí)現(xiàn),false有實(shí)現(xiàn)方法同衣,true沒有實(shí)現(xiàn)方法
   BOOL isAdd = class_addMethod(class, origSEL,replaceIMP , "v@:");
   if (isAdd) {
       //此時兩個方法名字皆指向同一個實(shí)現(xiàn)方法糊饱,下面為新方法變更實(shí)現(xiàn)方法
       class_replaceMethod(class, replaceSEL,origIMP , "v@:");
   }else{
     //交換兩個方法的實(shí)現(xiàn)
    method_exchangeImplementations(originMethod, replaceMethod);
   }
}
- (void)viewWillAppearN:(BOOL)animated {
   //調(diào)用系統(tǒng)方法
  [self viewWillAppearN:animated];

  NSString *name = [NSString stringWithUTF8String:object_getClassName(self)];
  NSString *trackName = [NSString stringWithFormat:@"%@----viewWillAppear",name];
  [[TrackObject sharedManager]track:trackName];

}

按鈕事件監(jiān)聽[是UIControl的方法 sendAction:to:forEvent:]

+ (void)load {
    Class class = [self class];
    SEL origSEL = @selector(sendAction:to:forEvent:);
    SEL replaceSEL = @selector(sendActionN:to:forEvent:);

    Method originMethod = class_getInstanceMethod(class, origSEL);
    Method replaceMethod = class_getInstanceMethod(class, replaceSEL);

    IMP origIMP = class_getMethodImplementation_stret(class, origSEL);
    IMP replaceIMP = class_getMethodImplementation_stret(class, replaceSEL);

    //先為原始方法添加實(shí)現(xiàn)魔市,false有實(shí)現(xiàn)方法,true沒有實(shí)現(xiàn)方法
    BOOL isAdd = class_addMethod(class, origSEL,replaceIMP , "v@:");
    if (isAdd) {
      //此時兩個方法名字皆指向同一個實(shí)現(xiàn)方法讲婚,下面為新方法變更實(shí)現(xiàn)方法
      class_replaceMethod(class, replaceSEL,origIMP , "v@:");
     }else{
       //交換兩個方法的實(shí)現(xiàn)
       method_exchangeImplementations(originMethod, replaceMethod);
     }
}
- (void)sendActionN:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event {
   [self sendActionN:action to:target forEvent:event];
    NSString *trackName = [NSString stringWithFormat:@"%@----%@",target,NSStringFromSelector(action)];
   [[TrackObject sharedManager]track:trackName];
}

其他的參照WOCrashProtector尿孔,里面的內(nèi)容很全

4.objc/NSObject 最基礎(chǔ)類NSObject介紹
三:項(xiàng)目應(yīng)用場景
1.防止項(xiàng)目閃退
WOCrashProtector 就是很好的防止閃退的依賴庫
2.代碼無縫埋點(diǎn)
3.類別中設(shè)置屬性實(shí)現(xiàn)
4.切面編程的核心
5.開啟動態(tài)創(chuàng)建類,讓你代碼有逼格【裝逼專用磺樱,項(xiàng)目中勿用】

- (void)viewDidLoad {
   [super viewDidLoad];
   
   //創(chuàng)建類對象
   Class newClass = objc_allocateClassPair([NSObject class], "EdenModel", 0);
   class_addMethod(newClass, @selector(test), (IMP) test, "v@:");  //添加方法
   NSString*name =@"name";
   class_addIvar(newClass, name.UTF8String, sizeof(id), log2(sizeof(id)), @encode(id)); //添加屬性
   objc_registerClassPair(newClass); //注冊此類對象纳猫,至關(guān)重要

   //開始用這個類
   NSObject *edenModelInstance = [NSClassFromString(@"EdenModel") new];
   [edenModelInstance setValue:@"杰克" forKey:@"name"];
   NSLog(@"我的名字:---%@",[edenModelInstance valueForKey:@"name"]); //我的名字:---杰克
   [edenModelInstance performSelector:@selector(test)];
   objc_msgSend(edenModelInstance, @selector(test)); //通過消息發(fā)送調(diào)用方法
}

//方法的實(shí)現(xiàn)
void test(id self, SEL _cmd){
    NSLog(@"This object is %p.",self);  //edenModelInstance
    NSLog(@"Class is %@, and super is %@.",[self class],[self superclass]);  //edenModelInstance
    Class currentClass = [self class];
    for( int i = 1; i < 5; ++i )
    {
       NSLog(@"Following the isa pointer %d times gives %p",i,currentClass);
       currentClass = object_getClass(currentClass);
    }
    NSLog(@"NSObject's class is %p", [NSObject class]); //NSObject 0x7fff89be1d00
    NSLog(@"NSObject's meta class is %p",object_getClass([NSObject class])); //NSObject 0x7fff89be1cd8
}

四:第三方庫有關(guān)runtime知識源碼探索
1.MJExtension
2.Aspects
3.jrswizzle
4.UIViewController-Swizzled
五:額外相關(guān)知識load,initialize和init

  1. load 在程序啟動前初始化執(zhí)行一次,且只執(zhí)行一次

  2. initialize 在init前初始化執(zhí)行一次竹捉,且只執(zhí)行一次

  3. init 為實(shí)例方法顯而易見芜辕,每調(diào)用一次執(zhí)行一次

  4. 區(qū)別及調(diào)用順序
    參考鏈接: http://www.reibang.com/p/d2ba735fa0bd
    http://www.reibang.com/p/939765be93a7

    int main(int argc, char * argv[]) {
       NSLog(@"%s:1",__func__);//第一個監(jiān)測點(diǎn)
       NSString * appDelegateClassName;
       @autoreleasepool {
       NSLog(@"%s:2",__func__);//第二個監(jiān)測點(diǎn)
       appDelegateClassName = NSStringFromClass([AppDelegate class]);
       }
      return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    
    //AView.m
    @implementation AView
    + (void)load {
        NSLog(@"%s",__func__);
    }
    + (void)initialize {
      NSLog(@"%s",__func__);
    }
    -(instancetype )init{
       if (self = [super init]) {
           NSLog(@"%s",__func__);
       }
      return self;
    }
    @end
    

執(zhí)行結(jié)果如下:

iOSAnimation[17421:357707]   +[AView load]
iOSAnimation[17421:357707]    main:1
iOSAnimation[17421:357707]    main:2
iOSAnimation[17421:357707]   +[AView initialize]
iOSAnimation[17421:357707]   -[AView init]
iOSAnimation[17421:357707]   -[AView init]
因此load里面的代碼不可過多,否則會造成啟動時間過長

load和initialize在父類和子類中執(zhí)行的順序

load 調(diào)用   父類-->子類-->類別
initialize調(diào)用 (無類別)父類-->子類 有類別 父類類別-->子類類別  有類別方法會覆蓋本類的方法

代碼執(zhí)行結(jié)果如下:

+[Person load]
+[Son load]
+[Person(Extention) load]
 main
+[Person(Extention) initialize] Person
-[Person init]
+[Person(Extention) initialize] Son 
+[Son initialize] Son
-[Person init]
-[Son init]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末块差,一起剝皮案震驚了整個濱河市侵续,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌憨闰,老刑警劉巖状蜗,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鹉动,居然都是意外死亡轧坎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門泽示,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缸血,“玉大人蜜氨,你說我怎么就攤上這事∩有海” “怎么了飒炎?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長笆豁。 經(jīng)常有香客問我郎汪,道長,這世上最難降的妖魔是什么闯狱? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任煞赢,我火速辦了婚禮,結(jié)果婚禮上哄孤,老公的妹妹穿的比我還像新娘耕驰。我一直安慰自己,他們只是感情好录豺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著饭弓,像睡著了一般双饥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弟断,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天咏花,我揣著相機(jī)與錄音,去河邊找鬼阀趴。 笑死昏翰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的刘急。 我是一名探鬼主播棚菊,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼叔汁!你這毒婦竟也來了统求?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤据块,失蹤者是張志新(化名)和其女友劉穎码邻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體另假,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡像屋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了边篮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片己莺。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡奏甫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出篇恒,到底是詐尸還是另有隱情扶檐,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布胁艰,位于F島的核電站款筑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏腾么。R本人自食惡果不足惜奈梳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望解虱。 院中可真熱鬧攘须,春花似錦、人聲如沸殴泰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悍汛。三九已至捞魁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間离咐,已是汗流浹背谱俭。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宵蛀,地道東北人昆著。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像术陶,于是被迫代替她去往敵國和親凑懂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355