Runtime应用
1.典型應用場景:
2.相關技術鏈接
整理runtime文檔
蘋果維護的開源代碼
蘋果和GNU各自維護一個開源的Runtime開源
3.基本介紹
Runtime?基本是用?C?和匯編寫的,它使用來處理OC轉換C的中間結構體。
高級編程語言 -> 匯編語言 - > 機器語言
OC(面向對象)-> 過渡runtime實現->C語言(面向過程)
[obj foo] 編譯器轉換objc_msgSend(obj, foo),runtime執行流程:
- 首先,通過obj的isa指針找到它的?class?;
- 在?class?的?method list?找?foo?;
- 如果?class?中沒到?foo,繼續往它的?superclass?中找 ;
- 一旦找到?foo?這個函數,就去執行它的實現IMP?。
- 再找到foo?之后,把foo?的method_name?作為key?,method_imp作為value?給存起來。當再次收到foo?消息的時候,可以直接在cache?里找到,避免去遍歷objc_method_list
4.數據結構
//實例(objc_object) struct objc_object {Class isa OBJC_ISA_AVAILABILITY; };//類 struct object_class{Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__Class super_class OBJC2_UNAVAILABLE; // 父類const char *name OBJC2_UNAVAILABLE; // 類名long version OBJC2_UNAVAILABLE; // 類的版本信息,默認為0long info OBJC2_UNAVAILABLE; // 類信息,供運行期使用的一些位標識long instance_size OBJC2_UNAVAILABLE; // 該類的實例變量大小struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表struct objc_method_list *methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協議鏈表 #endif }OBJC2_UNAVAILABLE;//方法列表 struct objc_method_list {struct objc_method_list *obsolete OBJC2_UNAVAILABLE;int method_count OBJC2_UNAVAILABLE; #ifdef __LP64__int space OBJC2_UNAVAILABLE; #endif/* variable length structure */struct objc_method method_list[1] OBJC2_UNAVAILABLE; } // Method(objc_method) typedef struct objc_method *Method; struct objc_method {SEL method_name OBJC2_UNAVAILABLE; 方法名char *method_types OBJC2_UNAVAILABLE; 方法類型IMP method_imp OBJC2_UNAVAILABLE; 方法實現 }元類(Meta Class) 是從isa指針指向的結構體創建,類對象的isa指針指向的我們稱之為元類(metaclass), 元類中保存了創建類對象以及類方法 所需的所有信息。 objc_object結構體實例它的isa指針指向了類對象objc_class,它的isa指針指向了metaclass,元類isa指向了NSobject元類。類緩存(objc_cache) 當objc_msgSend查找一個類的選擇器,它首先搜索類緩存。這是基于這樣的理論:如果你在類上調用一個消息,你可能以后再次調用該消息就放在上述的objc_cache,所以在實際運行中,大部分常用的方法都是會被緩存起來的,Runtime系統實際上非常快,接近直接執行內存地址的程序速度。Category(objc_category) Category是表示一個指向分類的結構體的指針,其定義如下: struct objc_category{char *category_name OBJC2_UNAVAILABLE; // 分類名char *class_name OBJC2_UNAVAILABLE; // 分類所屬的類名struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 實例方法列表struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 類方法列表struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分類所實現的協議列表 } 這個結構體主要包含了分類定義的實例方法與類方法,其中instance_methods列表是objc_class中方法列表的一個子集,而class_methods列表是元類方法列表的一個子集。 可發現,類別中沒有ivar成員變量指針,也就意味著:類別中不能夠添加實例變量和屬性復制代碼5.方法與消息
通過下面三種方法可以獲取SEL:
a、sel_registerName函數
b、Objective-C編譯器提供的@selector()
SEL deallocSelector = @selector(dealloc); 復制代碼c、NSSelectorFromString()方法
SEL selector = NSSelectorFromString(@"setOrientation:"); 復制代碼第一個參數:是指向self的指針(如果是實例方法,則是類實例的內存地址;如果是類方法,則是指向元類的指針)
第二個參數:是方法選擇器(selector)接下來的參數:方法的參數列表。
如果消息中還有其它參數,則該方法的形式如下所示:
objc_msgSend(receiver, selector, arg1, arg2,...) 復制代碼動態方法解析
對象在接收到未知的消息時,首先會調用所屬類的類方法 +resolveInstanceMethod:(實例方法)或者+resolveClassMethod:(類方法)。 在這個方法中,我們有機會為該未知消息新增一個“處理方法”,通過運行時class_addMethod函數動態添加到類里面就可以了。 這種方案更多的是為了實現@dynamic屬性。
// 測試消息轉發 - (void)runtimeTestMethodForward {//執行foo函數[self performSelector:@selector(foo:) withObject:@10 withObject:@20]; // 最多傳遞2個參數 }+ (BOOL)resolveInstanceMethod:(SEL)sel {if (sel == @selector(foo:)) {//如果是執行foo函數,就動態解析,指定新的IMPclass_addMethod([self class], sel, (IMP)newFoo, “@@:”); /// ”v@:”意思就是這已是一個void類型的方法,沒有參數傳入,, ?“i@:”就是說這是一個int類型的方法,沒有參數傳入,,”i@:@”就是說這是一個int類型的方法,又一個參數傳入;@@:傳遞對象return YES;}return NO; // 運行時就會移到下一步:forwardingTargetForSelector }void newFoo(id obj, SEL _cmd, id params) {NSLog(@"doing new foo"); } 復制代碼備用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector 如果在上一步無法處理消息,則Runtime會繼續調以下方法: 如果一個對象實現了這個方法,并返回一個非nil的結果,則這個對象會作為消息的新接收者,且消息會被分發到這個對象。當然這個對象不能是self自身,否則就是出現無限循環。當然,如果我們沒有指定相應的對象來處理aSelector,則應該調用父類的實現來返回結果。
完整消息轉發 如果在上一步還不能處理未知消息,則唯一能做的就是啟用完整的消息轉發機制了。 我們首先要通過,指定方法簽名,若返回nil,則表示不處理。 如下代碼:
- (id)forwardingTargetForSelector:(SEL)aSelector {return nil;//返回nil,進入下一步轉發 }// 完整消息轉發 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {if ([NSStringFromSelector(aSelector) isEqualToString:@"foo:"]) {return [NSMethodSignature signatureWithObjCTypes:"@@:"];//簽名,進入forwardInvocation}return [super methodSignatureForSelector:aSelector]; }- (void)forwardInvocation:(NSInvocation *)anInvocation {SEL sel = anInvocation.selector;Person *p = Person.new;if ([p respondsToSelector:sel]) {[anInvocation invokeWithTarget:p];}else{[self doesNotRecognizeSelector:sel];} }void newFoo(id obj, SEL _cmd, id params) {NSLog(@"doing new foo %@", params); } 復制代碼應用 1.關聯對象(Objective-C Associated Objects)給分類增加屬性 說明:分類是不能自定義屬性和變量的 方法:
//關聯對象 void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) //獲取關聯的對象 id objc_getAssociatedObject(id object, const void *key) //移除關聯的對象 void objc_removeAssociatedObjects(id object) 參數說明: id object:被關聯的對象 const void *key:關聯的key,要求唯一 id value:關聯的對象 objc_AssociationPolicy policy:內存管理的策略內存管理策略: typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy){ OBJC_ASSOCIATION_ASSIGN = 0, // 表示弱引用關聯,通常是基本數據類型 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 表示強引用關聯對象,是線程安全的 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 表示關聯對象copy,是線程安全的 OBJC_ASSOCIATION_RETAIN = 01401, // 表示強引用關聯對象,不是線程安全的 OBJC_ASSOCIATION_COPY = 01403 // 表示關聯對象copy,不是線程安全的 };@interface UIView (DefaultColor) @property (nonatomic, strong) UIColor *defaultColor; @end#import <objc/runtime.h> @implementation UIView (DefaultColor) @dynamic defaultColor; static char kDefaultColorKey;- (void)setDefaultColor:(UIColor *)defaultColor {objc_setAssociatedObject(self, &kDefaultColorKey, defaultColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }- (UIColor *)defaultColor {return objc_getAssociatedObject(self, &kDefaultColorKey); }@end 復制代碼通過關聯對象實現的屬性的內存管理也是有ARC管理的,所以我們只需要給定適當的內存策略就行了,不需要操心對象的釋放。
2.方法魔法(Method Swizzling)方法添加和替換和KVO實現 方法添加
class_addMethod([self class], sel, (IMP)fooMethod, "v@:"); 參數說明:cls 被添加方法的類name 添加的方法的名稱的SELimp 方法的實現。該函數必須至少要有兩個參數,self,_cmd類型編碼 方法替換 // 方法替換 + (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{Class class = [self class];SEL originalSelector = @selector(viewDidLoad);SEL swizzledSelector = @selector(crviewDidLoad);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, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));}else{method_exchangeImplementations(originalMethod, swizzledMethod);}}); }- (void)crviewDidLoad {NSLog(@"自定義的方法"); } 復制代碼+load?是在一個類被初始裝載時調用,+initialize?是在應用第一次調用該類的類方法或實例方法前調用的。兩個方法都是可選的,并且只有在方法被實現的情況下才會被調用. swizzling應該只在dispatch_once 中完成,由于swizzling 改變了全局的狀態,所以我們需要確保每個預防措施在運行時都是可用的。原子操作就是這樣一個用于確保代碼只會被執行一次的預防措施,就算是在不同的線程中也能確保代碼只執行一次。Grand Central Dispatch 的 dispatch_once滿足了所需要的需求,并且應該被當做使用swizzling 的初始化單例方法的標準。
查找消息的唯一依據是selector的名字。所以,我們可以利用Objective-C的runtime機制,實現在運行時交換selector對應的方法實現以達到我們的目的。 即運行時交換方法名。
每一個SEL與一個IMP一一對應,正常情況下通過SEL可以查找到對應消息的IMP實現。 交換后
實現NSCoding的自動歸檔和自動解檔 原理描述:用runtime提供的函數遍歷Model自身所有屬性,并對屬性進行encode和decode操作。 核心方法:在Model的基類中重寫方法:
- (instancetype)initWithCoder:(NSCoder *)aDecoder {if (self = [super init]) {unsigned int outCount;Ivar *ivars = class_copyIvarList([self class], &outCount);for (int i = 0; i < outCount; i++) {Ivar ivar = ivars[i];NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];}free(ivars);}return self; }- (void)encodeWithCoder:(NSCoder *)aCoder {unsigned int outCount;Ivar * ivars = class_copyIvarList([self class], &outCount);for (int i = 0; i < outCount; i ++) {Ivar ivar = ivars[i];NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];[aCoder encodeObject:[self valueForKey:key] forKey:key];}free(ivars); } 復制代碼測試整形,數據沒有都可以正常存儲數據
實現字典和模型的自動轉換(MJExtension)
- (instancetype)initWithDic:(NSDictionary *)dic {if (self = [super init]) {unsigned int outCount;//獲取類的屬性及屬性對應的類型objc_property_t * properties = class_copyPropertyList([self class], &outCount);for (int i = 0; i < outCount; i ++) {objc_property_t property = properties[i];//通過property_getName函數獲得屬性的名字NSString * key = [NSString stringWithUTF8String:property_getName(property)];NSString *type = [NSString stringWithUTF8String:property_getAttributes(property)];NSLog(@"type %@, key %@", type, key);if ([dic valueForKey:key] == nil) continue;[self setValue:[dic valueForKey:key] forKey:key];}//立即釋放properties指向的內存free(properties);}return self; } 復制代碼總結:ivar和property區別 ivar = instanc variable 實例變量,僅僅是一個變量;必須存在;帶下劃線的變量”_name” property = objc聲明的屬性,操作變量函數setter和getter;是可選的;沒有帶下滑線的變量“name” 復制代碼轉載于:https://juejin.im/post/5afbcca26fb9a07aa43c62ab
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
- 上一篇: [BZOJ2152]聪聪可可(点分治)
- 下一篇: day13-递归函数、匿名函数、内置函数