在IOS開發和學習過程當中,咱們常常會接觸到一個詞: Runtime 。不少開發者對之既熟悉又陌生,基本都是淺嘗輒止,達不到靈活使用的水平(話說開發中也確實不常常用。。)本文和你們一塊兒研究一下,Runtime究竟是什麼,還有他的一些應用場景,畢竟Runtime是OC動態特性的核心,熟練掌握它能夠幫助咱們更好的控制類的屬性及方法,編寫出更高效的代碼。html
1、什麼是Runtime ios
無論你以前如何理解的Runtime,先把他扔一邊,咱們從頭梳理一下:數組
一、有一種大氣而準確的說法 : 緩存
Objective-C是C語言的擴展,並加入了面向對象特性和Smalltalk式的消息傳遞機制。OC中的Runtime實現了將C語言轉化爲面嚮對象語言的做用,實際上咱們的每一條OC代碼的執行都會轉換爲Runtime的函數調用。Runtime是OC底層的實現,其函數的調用是高效的,基於Runtime的代碼編寫也是高效的!框架
二、核心: 函數
是一個用C和彙編語言寫的Runtime庫(開源),這個庫所作的事情就是加載類信息,進行方法的分發和轉發,正是這個庫賦予了Objective-C動態特性。佈局
三、所謂的「動態特性」 :性能
(1)咱們比較下C++ 和OC,C++沒有動態特性,編譯時直接將代碼轉換爲機器語言,而OC是在運行的時候,經過Runtime把程序轉爲可令計算機讀懂的語言。二者都是對C進行了面向對象的擴展,可是實現機制不一樣。學習
(2)雖然RunTime賦予了OC動態特性,使得開發和使用變得至關靈活,可是歸根結底OC仍是一種編譯型的語言,其具備必定的動態性,可是其動態特性也比不上JavaScript這種解釋型的語言。atom
四、Runtime 其實有兩個版本(也就聽聽。。):
(1)「Modern」 和 「Legacy」。咱們如今用的 Objective-C 2.0 採用的是現行(Modern)版的 Runtime 系統,只能運行在 iOS 和 OS X 10.5 以後的64位程序中。而OS X較老的32位程序仍採用 Objective-C 1中得(早期) Legacy 版本的 Runtime 系統。這兩個版本最大的區別在於當你更改一個類的實例變量的佈局時,在早期版本中你須要從新編譯它的子類,而現行版本就不須要。
(2)蘋果開源了Runtime庫的代碼,同時GNU也維護着一個開源的版本,這兩個版本之間都在努力的保持一致。
五、常見的簡單使用場景,後面詳細羅列:
(1)動態的建立、改變類
(2)動態的建立、改變、遍歷屬性
(3)動態的建立、改變、交互、遍歷方法
2、OC中的對象模型
真正開始瞭解Runtime,有個基礎工做須要作,就是咱們要重溫一下OC的對象和類的結構
一、打開<objc/objc.h>看看objc_object的定義 (截圖看起來比較清晰,呵呵)
總結一下上面的:
(1)經常使用的id類型其實是一個指向objc_object(實例對象)結構體的指針,id一般指代一個對象,也就是說OC對象其實就一個指向objc_object結構體的指針
(2)看objc_object結構體定義,得知其結構體內有一個類型爲Class的字段isa,這就是常說的isa指針了。
(3)Class聲明爲一個指向objc_class的指針
關於 SEL、IMP的補充
(1)SEL是selector在Objective-C中的表示類型。selector能夠理解爲區別「方法」的ID。
typedef struct objc_selector *SEL; struct objc_selector { char *name; OBJC2_UNAVAILABLE;// 名稱 char *types; OBJC2_UNAVAILABLE;// 類型 };
name和types都是char類型。
(2)IMP是「implementation」的縮寫,它是由編譯器生成的一個函數指針。當你發起一個消息後,這個函數指針決定了最終執行哪段代碼。
二、接下來打開<objc/runtime.h>,看看objc_class的定義
研究一下objc_class中的幾個字段:
(1)isa:這裏的isa指針一樣是一個指向objc_class(類對象)的指針,代表該Class的類型,這裏的isa指針指向的就是常說的meta-class(元類)了。不難看出,類自己也是一個對象。一樣的,元類也是一個對象,爲了設計上的完整,元類的isa指針都會指向一個root metaclass(根元類)。根元類自己的isa指針指向本身,這樣就造成了一個閉環。
(2)super_class:這個指針就是指向該class的super class,即指向父類,若是該類已是最頂層的根類(如NSObject或NSProxy),則super_class爲NULL。
(3)cache:用於緩存最近使用的方法。消息發送時,系統會根據isa指針去查找可以響應這個消息的對象。在實際使用中,這個對象只有一部分方法是經常使用的,若是每次消息來時,都是在methodLists中遍歷一遍,性能必定不好。這時,在每次調用過一個方法後,這個方法就會被緩存到cache列表中,下次調用的時候runtime就會優先去cache中查找,若是cache沒有,纔去methodLists中查找方法。這樣,對於那些常常用到的方法的調用,提升了調用的效率。
(4)version:咱們可使用這個字段來提供類的版本信息。這對於對象的序列化很是有用,它可讓咱們識別出不一樣類定義版本中實例變量佈局的改變(不太明白的做用。。。)
(5)objc_method_list:方法鏈表中存放的是該類的成員方法(-方法),類方法(+方法)存在meta-class的objc_method_list鏈表中(就是元類的「實例方法」)。
關於結構體指針 Method、Ivar的補充
(1)Method表明類中的某個方法的類型
聲明: typedef struct objc_method *Method;
objc_method的定義以下:
struct objc_method { SEL method_name OBJC2_UNAVAILABLE; // 方法名 char *method_types OBJC2_UNAVAILABLE; // 方法類型 IMP method_imp OBJC2_UNAVAILABLE; // 方法實現 }
方法名method_name類型爲SEL。
方法類型method_types是一個char指針,存儲着方法的參數類型和返回值類型。
方法實現method_imp的類型爲IMP
(2)Ivar表明類中實例變量的類型
聲明:typedef struct objc_ivar *Ivar
objc_ivar的定義以下:
struct objc_ivar { char *ivar_name OBJC2_UNAVAILABLE; // 變量名 char *ivar_type OBJC2_UNAVAILABLE; // 變量類型 int ivar_offset OBJC2_UNAVAILABLE; // 基地址偏移字節 #ifdef __LP64__ int space OBJC2_UNAVAILABLE; // 佔用空間 #endif }
三、經典配圖展現:oc對象繼承模型
(1)上圖的幾個注意點
(2)舉個方法查找過程的例子:
調用respondsToSelector: 的時候,實例對象只須要根據其isa指針,找到其所屬的class,而後遍歷其methodLists,若是沒有,那麼根據這個類的super_class找到其父類,再看其父類是否能相應這個方法就能夠了,直到super_class爲nil時,就沒法響應這個方法了,return NO。
(3)調用類方法的不一樣:
當咱們使用類名調用類方法(+方法)時,只須要根據class的isa指針,找到其meta-class,而後經過meta-class的methodLists找到相應的方法既可(「類」是「元類」的對象)。
3、消息機制
一、OC中調用一個方法的本質就是在給對象發送消息,好比初始化一個NSObject對象:
NSObject *object = [[NSObject alloc] init];
事實上,在編譯時這句話會翻譯成一個C的函數調用,即:
objc_msgSend(objc_msgSend([NSObject?class],@selector(alloc)),@selector(init));
看看官方文檔:
二、關於消息執行的時機問題:
(1)思考:就如上文所述,OC的代碼翻譯成C的函數調用以後,就是把OC代碼轉換成C代碼了,那OC的動態特性體如今哪裏?不就和C的靜態特性同樣了麼?
(2)回答:對於C語言,函數的調用在編譯的時候就會去決定調用哪一個函數。而OC是一種動態語言,它會盡量的把代碼執行的決策從編譯和連接的時候,推遲到運行時。給一個對象發送的一個消息並不會當即執行,而是在運行的時候再去尋找他對應的實現。這樣就能夠把消息轉發給你想要的對象,或者隨意交換一個方法的實現之類的。
三、全部使用objc_msgSend函數,會執行如下步驟(也體現了objc_cache的做用)
(1)經過對象(類)的isa指針去找到他的class
(2)在class的method list 找到該消息的實現
(3)若是class中沒有該消息的實現,就繼續到它的super_class中去找
(4)一旦找到這個這個消息的實現,那麼就去執行他的IMP(函數指針,代碼所在空間)
四、常見的函數、頭文件
(1) #import <objc/runtime.h> : 主要包括 成員變量、類、方法
(2) #import <objc/message.h> : 消息機制
4、RunTime的使用實例
RunTime能夠很靈活的實現改變系統的方法及屬性的效果,靈活度之大以致於也形成了一些隱患 ——— 破壞了系統的封裝及代碼的可讀性,因此你們仍是謹慎使用,也不要在開發過程當中給隊友挖坑(不要問我怎麼知道的。。。)
下面蒐羅了一些經常使用的場景:
一、動態建立一個類
#import <objc/runtime.h> // 自定義一個方法 void reportFunction (id self, SEL _cmd) { NSLog(@"This object is %p", self); } int main(int argc, const char * argv[]) { @autoreleasepool {?? ? ? ? // 1.動態建立對象 建立一個Person 繼承自 NSObject類 Class newClass = objc_allocateClassPair([NSObject class], 「Person」, 0);? // 爲該類增長名爲Report的方法 class_addMethod(newClass, @selector(report), (IMP)reportFunction, @"v@:");?? ? ? ? // 註冊該類 objc_registerClassPair(newClass); // 建立一個 Student 類的實例 instantOfNewClass = [[newClass alloc] init]; // 調用方法 [instantOfNewClass report]; } return 0; }
二、關聯對象(分類中動態添加成員變量)
(1)對象在內存中的排布能夠當作是一個結構體,該結構體的大小並不能動態的變化,因此沒法在運行時動態的給對象增長成員變量,可是咱們能夠經過關聯對象的方法變相的給對象增長一個成員變量。
(2)好比,咱們想給NSObject新增一個關聯對象(就是添加成員變量):
建立一個NSObject的分類AssociatedObject,並聲明一個新屬性
@interface NSObject (AssociatedObject) @property (nonatomic, strong) id associatedObject;? @end 在NSObject+AssociatedObject.m文件裏面進行關聯 #import "NSObject+AssociatedObject.h" #import <objc/runtime.h> @implementation NSObject (AssociatedObject) @dynamic associatedObject; - (void)setAssociatedObject:(id)object { // 設置關聯對象 objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (id)associatedObject { // 獲得關聯對象 return objc_getAssociatedObject(self, @selector(associatedObject)); } @end
(3)咱們能夠經過上面的方法,給類動態的添加屬性,不過咱們更經常使用的是給一個類動態的添加block回調(想讓這個類的實例實現什麼業務邏輯,均可以經過賦值block,而後調用,來靈活的實現,使用起來也很方便,和添加屬性相似)。
三、交換方法(可更改系統方法)
咱們在開發過程當中會遇到一種常見的錯誤:給數組元素賦值nil,系統會崩潰。下面咱們參照這個案例,解釋下runtime交換方法的實現
(1)先建立一個數組
NSMutableArray *arrayM = [NSMutableArray array]; [arrayM addObject:@"1111"]; [arrayM addObject:@"2222"]; [arrayM addObject:nil]; //這裏會形成程序崩潰 [arrayM addObject:@"33333"];
(2)交換方法,將系統的addObject和自定義的方法進行交換,咱們先寫一個NSMutableArray的分類,給其添加新的方法,而後在其中實現與系統方法交換。注意:「交換」不等同於「替換」
(3)這裏有一個坑,addObject 其實是 調用 insertObject :atIndex:方法, 而且在運行過程當中,能夠看到這個方法是_NSArrayM的方法(不是NSMutableArray的方法,這裏有點像KVO的狀況),因此咱們要拿到_NSArrayM類,而後和它交換方法。能夠參照下面的代碼:NSClassFromString(@"__NSArrayM」)就是動態過程當中獲取這個類。
(4)代碼實現
@implementation NSMutableArray (XL) + (void)initialize //補充下這個方法只執行一次,類比load方法都是執行一次,可是後者編譯完就會執行 { //當前類被初始化的時候調用 Method m1 = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(addObject:)); Method m2 = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(new_AddObject:)); //下面這樣寫也能夠 //Method m2 = class_getInstanceMethod([NSMutableArray class], @selector(new_AddObject:)); method_exchangeImplementations(m1, m2); } - (void)new_AddObject:(id)objc { if (objc == nil) { //這裏的方法已經經過交換變成 addObject: [self new_AddObject:@"此處爲空"]; }else{ [self new_AddObject:objc]; } } @end
四、KVO的底層實現(自定義KVO)
(1)KVO的底層實現也是利用了RunTime機制,簡單點說,KVO機制就是在運行時,動態派生出被檢測對象的子類(NSKVONotifying_XXX),將被觀察對象的isa指向該子類,而後在新子類中重寫觀察屬性的set方法,接着在set方法裏調用觀察者的observeValueForKeyPath方法實現的監聽機制(暈了吧,這就對了。。。看例子)
(2)給出示例(Person類就不寫了,就一個屬性age),在控制器中進行監測,下面代碼執行結束,會顯示監聽到的age的變化
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Person *p = [[HMPerson alloc] init]; p.age = 20; self.p = p; //添加觀察者 [p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil]; //屬性改變了! p.age = 30; } - (void)dealloc { [self.p removeObserver:self forKeyPath:@"age"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSLog(@"%@對象的%@屬性改變了:%@", object, keyPath, change); } @end
(3)分析上面的例子,系統在運行時動態的進行了四項操做:
<1> 生成了Person的子類 NSKVONotifying_Person
<2> 在該子類中重寫了age的set方法
<3> 調用了觀察者的 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 方法
<4> 在p.age = 30 的時候,修改了對象中的isa指針,指向子類NSKVONotifying_Person,這樣,在調用set方法的時候就會去子類的空間中尋找方法的地址並調用(很關鍵的一步,你們能夠打斷點驗證)
(4)模擬KVO過程,咱們手動建立一個子類:NSKVONotifying_Person,注意,此時運行會報出「壞內存」的錯誤,由於你的建立的類和系統自動生成的子類重名了(這也是一種驗證KVO原理的方式)
#import "NSKVONotifying_Person.h" @implementation NSKVONotifying_HMPerson -(void)setAge:(int)age { //必須先調用父類的setAge方法,保證父類的set方法正常運行 [super setAge:age]; //僞代碼,調用監聽者的方法,實現監聽到屬性改變後的邏輯操做,而且傳遞參數 [監聽者 observeValueForKeyPath:@"age" ofObject:super change:@{ 監聽屬性的鍵值 } context:nil]; } @end
五、模型歸檔(遍歷成員屬性的應用)
(1)Runtime能夠動態獲取成員屬性名列表。
(2)下面代碼中,Ivar表示的就是成員屬性,ivars是指向屬性的指針或者說地址(也能夠理解爲數組,但不是數組)。
(3)歸檔的實現可能會遇到對象中有不少屬性,逐個手動去匹配歸檔哪些屬性很麻煩,因此使用運行時,經過循環實現。
(4)代碼示例:對於一個有不少屬性的Person類,遵照了NSCoding協議以後,咱們能夠利用RunTime遍歷模型對象的全部屬性進行歸檔,關鍵代碼以下:
// 利用runtime機制進行屬性的歸檔接檔 - (void)encodeWithCoder:(NSCoder *)aCoder { unsigned int count = 0; Ivar *ivars = class_copyIvarList([Person class], &count); for (int i = 0; i<count; i++) { // 取出i位置對應的成員變量 Ivar ivar = ivars[i]; // 查當作員變量 const char *name = ivar_getName(ivar); // 歸檔 NSString *key = [NSString stringWithUTF8String:name]; //KVC 得到對象屬性值 id value = [self valueForKey:key]; [aCoder encodeObject:value forKey:key]; } // 若是函數名中包含了copy\new\retain\create等字眼,那麼這個函數返回的數據就須要手動釋放 free(ivars); } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self) { unsigned int count = 0; Ivar *ivars = class_copyIvarList([Person class], &count); for (int i = 0; i<count; i++) { // 取出i位置對應的成員變量 Ivar ivar = ivars[i]; // 查當作員變量 const char *name = ivar_getName(ivar); // 歸檔 NSString *key = [NSString stringWithUTF8String:name]; id value = [aDecoder decodeObjectForKey:key]; // 設置到成員變量身上 [self setValue:value forKey:key]; } free(ivars); } return self; }
六、字典和對象模型之間的轉換,例如MJExtension
(1)如今流行的不少字典轉模型的框架,基本上都是利用Runtime原理實現(效率高):遍歷模型屬性列表—>根據得到的屬性名做爲key去字典中取出value —>而後用KVC給模型對象屬性賦值
(2)這只是簡單原理,框架中還包含了不少狀況的判斷,好比模型套模型的狀況,這就須要檢測成員屬性的類型是不是對象類型,那就繼續轉模型。
(3)一個簡單的示例代碼
-(void)modelToolWithDict:(NSDictionary *)dict andModel:(Model*)model { // Ivar : 成員變量 unsigned int count = 0; // 得到全部的成員變量 Ivar *ivars = class_copyIvarList([HMPerson class], &count); for (int i = 0; i<count; i++) { // 取得i位置的成員變量 Ivar ivar = ivars[i]; const char *name = ivar_getName(ivar); // 得到成員變量的類型,若是須要根據類型判斷是否有模型嵌套,能夠經過這個變量 const char *type = ivar_getTypeEncoding(ivar); //得到字典中的值 id value = dict[[NSString stringWithUTF8String:name]]; //使用KVC給模型賦值(KVC底層也是Runtime) [model setValue:value forKeyPath:[NSString stringWithUTF8String:name]]; NSLog(@"%d %s %s", i, name, type); } }
七、避免數組越界
開發中,數組在訪問時若是越界會形成崩潰,爲了不這種潛在的崩潰風險,咱們能夠採用多種方法「強制」它不越界,好比重寫get方法進行內部判斷,這裏咱們用Runtime來作一個比較完全的解決。
(1)越界的狀況:names數組有10個元素, 調用 self.names[10] ,崩潰 ;
這行代碼的本質是: [self.names objectAtIndex:10],因此,咱們用運行時進行這個方法的交換。
(2)添加以下分類,後面出現數組訪問越界的狀況,將返回nil;
(3)由於是在load方法中實現的交換,因此,程序啓動內存中加載了這個分類後,自動執行交換,不須要導入任何頭文件了。
(4)核心代碼
@implementation NSArray(Extension) + (void)load { Method otherMehtod = class_getInstanceMethod(class, otherSelector); Method originMehtod = class_getInstanceMethod(class, originSelector); // 交換2個方法的實現 method_exchangeImplementations(otherMehtod, originMehtod); } - (id)new_ObjectAtIndex:(NSUInteger)index { if (index < self.count) { return [self new_ObjectAtIndex:index]; } else { return nil; } } @end
八、自動顯示「空」的tableView
當tableView的數據源爲空時,咱們通常會將tableView隱藏,同時貼上去一個「沒有加載到內容」之類的提示視圖;或者編寫一個cell來顯示「空」,來給用戶一個友善的交互提示。這樣寫能夠,可是比較麻煩,如今用運行時,直接給tableView添加特性,能夠自動判斷數據源是否爲空,而且展現出用戶想要展現的「空」視圖。
@interface UIScrollView (YWEmptyView) /** 空頁面 ,開發者自定義*/ @property (nonatomic, strong) UIView *emptyView; @end
#import "UIScrollView+YWEmptyView.h" #import <objc/runtime.h> static const char emptyKey; @implementation UIScrollView (YWEmptyView) - (UIView *)emptyView { return objc_getAssociatedObject(self, &emptyKey); } - (void)setEmptyView:(UIView *)emptyView { objc_setAssociatedObject(self, &emptyKey, emptyView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [self addSubview:self.emptyView]; } //獲取當前數據源數量 - (NSInteger)getTotalCount { NSInteger totalCount = 0; if ([self isKindOfClass:[UITableView class]]) { UITableView *tableView = (UITableView *)self; for (NSInteger section = 0; section < tableView.numberOfSections; section++) { totalCount += [tableView numberOfRowsInSection:section]; } } else if ([self isKindOfClass:[UICollectionView class]]) { UICollectionView *collectionView = (UICollectionView *)self; for (NSInteger section = 0; section < collectionView.numberOfSections; section++) { totalCount += [collectionView numberOfItemsInSection:section]; } } return totalCount; }
//判斷是否顯示「空」視圖 - (void)showEmptyView { [self bringSubviewToFront:self.emptyView]; if ([self getTotalCount] > 0) { self.emptyView.hidden = YES; }else { self.emptyView.hidden = NO; } } @end @implementation UITableView (YWEmptyView) + (void)load { SEL reloadSEL = @selector(reloadData); SEL shareReloadSEL = @selector(shareReloadData); Method reloadData = class_getInstanceMethod(self, reloadSEL); Method shareReloadData = class_getInstanceMethod(self, shareReloadSEL); BOOL success = class_addMethod(self, reloadSEL, method_getImplementation(shareReloadData), method_getTypeEncoding(shareReloadData)); if (success) { class_replaceMethod(self, shareReloadSEL, method_getImplementation(reloadData), method_getTypeEncoding(reloadData)); } else { method_exchangeImplementations(reloadData, shareReloadData); } }
//新的刷新方法,和原來的reload進行交換 - (void)shareReloadData { [self shareReloadData]; [self showEmptyView]; } @end @implementation UICollectionView (YWEmptyView) + (void)load { Method reloadData = class_getInstanceMethod(self, @selector(reloadData)); Method shareReloadData = class_getInstanceMethod(self, @selector(shareReloadData)); method_exchangeImplementations(reloadData, shareReloadData); } - (void)shareReloadData { [self shareReloadData]; [self showEmptyView]; } @end
九、按鈕的防暴力點擊
就是給按鈕設置防暴力點擊的方法,原理:設置一個時間clickInterval和一個標記位ignoreClick,點擊按鈕的時候標記位被設置「YES」,以後任何點擊事件再也不響應,直到過了clickInterval時長以後,還原標記位爲「No」,而且按鈕能夠再次響應點擊事件。
@interface UIControl (ClickRepeatedly) /** * 設置點擊的間隔(防止反覆點擊) */ @property (nonatomic, assign)NSTimeInterval clickInterval; @property (nonatomic, assign)BOOL ignoreClick; @end
#import "UIControl+ClickRepeatedly.h" #import <objc/runtime.h> static const char *ClickIntervalKey; static const char *IgnoreClick; @implementation UIControl (ClickRepeatedly) - (void)setClickInterval:(NSTimeInterval)clickInterval{ objc_setAssociatedObject(self, &ClickIntervalKey, @(clickInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSTimeInterval)clickInterval{ return [objc_getAssociatedObject(self, &ClickIntervalKey) doubleValue]; } - (void)setIgnoreClick:(BOOL)ignoreClick{ objc_setAssociatedObject(self, &IgnoreClick, @(ignoreClick), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (BOOL)ignoreClick{ return [objc_getAssociatedObject(self, &IgnoreClick) boolValue]; } + (void)load{ //替換點擊事件 Method a = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:)); Method b = class_getInstanceMethod(self, @selector(rc_sendAction:to:forEvent:)); method_exchangeImplementations(a, b); } - (void)rc_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{ if (self.ignoreClick) { return; } else{ [self rc_sendAction:action to:target forEvent:event]; } if (self.clickInterval > 0) { self.ignoreClick = YES; [self performSelector:@selector(setIgnoreClick:) withObject:@(NO) afterDelay:self.clickInterval]; } } @end
5、總結
一、Runtime是OC代碼能夠編譯運行的關鍵,自己是純C的函數庫,它的存在賦予了OC動態特性。
二、Runtime提供的一系列方法,能夠在程序運行時操做類以及它的方法和屬性,使用Runtime進行代碼構建,效率較高。
三、Runtime是底層運行機制,實際開發中咱們使用Runtime的時候並很少,可是關鍵時刻,仍是能很好的解決不少問題。
四、熟悉Runtime須要瞭解幾項東西:
(1)OC的對象模型
(2)isa指針
(3)消息機制
五、Runtime水很深,各位請根據本身的水性選擇區域。。。。。
參考:http://honglu.me/2014/12/29/淺談OC運行時-RunTime/
https://www.ianisme.com/ios/2019.html?sukey=ecafc0a7cc4a741bfade6848774c88b7eeefdecd843c4a6c6f7da7fc65d2ed4ef4a188f7f68ab13e9ee80007d7ffb919