概述
Objc Runtime使得C具有了面向?qū)ο竽芰κ仙诔绦蜻\行時創(chuàng)建届囚,檢查,修改類是尖、對象和它們的方法奖亚。Runtime是C和匯編編寫的,這里http://www.opensource.apple.com/source/objc4/可以下到蘋果維護的開源代碼析砸,GNU也有一個開源的runtime版本昔字,他們都努力的保持一致。
應(yīng)用場景
- 將某些OC代碼轉(zhuǎn)為運行時代碼首繁,探究底層作郭,比如block的實現(xiàn)原理
- 攔截系統(tǒng)自帶的方法調(diào)用(Swizzle 黑魔法),比如攔截imageNamed:弦疮、viewDidLoad夹攒、alloc
- 實現(xiàn)分類也可以增加屬性
- 實現(xiàn)NSCoding的自動歸檔和自動解檔
- 實現(xiàn)字典和模型的自動轉(zhuǎn)換。(MJExtension)
- 修BUG神器胁塞,如果大型框架的BUG 通過Runtime來解決咏尝,非常好用。
一些常用類型
Method
Method
An opaque type that represents a method in a class definition.
Declaration
typedef struct objc_method *Method;
代表類定義中的方法的不透明類型啸罢。
Class
Class
An opaque type that represents an Objective-C class.
Declaration
typedef struct objc_class *Class;
代表Objective-C中的類
Ivar
An opaque type that represents an instance variable.
Declaration
typedef struct objc_ivar *Ivar;
代表實例變量
IMP
IMP
A pointer to the start of a method implementation.
指向方法實現(xiàn)的開始的內(nèi)存地址的指針编检。
SEL
SEL
Defines an opaque type that represents a method selector.
Declaration
typedef struct objc_selector *SEL;
代表方法的選擇器
設(shè)置關(guān)聯(lián)值
Example : 在category 中添加對象
//.h
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
@interface UIView (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end
//.m
#import "UIView+AssociatedObject.h"
@implementation UIView (AssociatedObject)
static char kAssociatedObjectKey;
- (void)setAssociatedObject:(id)associatedObject {
objc_setAssociatedObject(self, &kAssociatedObjectKey, associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, &kAssociatedObjectKey);
}
objc_setAssociatedObject,給指定的對象設(shè)置關(guān)聯(lián)值。
objc_setAssociatedObject
Sets an associated value for a given object using a given key and association policy.
Declarationvoid
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
Parameters
object
The source object for the association.
key
The key for the association.
value
The value to associate with the key key for object. Pass nil to clear an existing association.
policy
The policy for the association. For possible values, see Associative Object Behaviors.
- object 指定的對象
- const void *key
- value 值
- policy 存儲策略
Behavior | @property Equivalent | Description |
---|---|---|
OBJC_ASSOCIATION_ASSIGN | @property (assign) 或 @property (unsafe_unretained) | 指定一個關(guān)聯(lián)對象的弱引用扰才。 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, strong) | 指定一個關(guān)聯(lián)對象的強引用允懂,不能被原子化使用。 |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | 指定一個關(guān)聯(lián)對象的copy引用衩匣,不能被原子化使用蕾总。 |
OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | 指定一個關(guān)聯(lián)對象的強引用,能被原子化使用琅捏。 |
OBJC_ASSOCIATION_COPY | @property (atomic, copy) | 指定一個關(guān)聯(lián)對象的copy引用生百,能被原子化使用。 |
objc_getAssociatedObject
Returns the value associated with a given object for a given key.
Declarationid
objc_getAssociatedObject(id object, const void *key);
Parameters
object
The source object for the association.
key
The key for the association.
Return Value
The value associated with the key key for object.
objc_getAssociatedObject
- 返回給定對象的key的關(guān)聯(lián)值
- object 關(guān)聯(lián)的源對象
- key 關(guān)聯(lián)的key
- Return Value 與對象的key相關(guān)聯(lián)的值柄延。
objc_removeAssociatedObjects
objc_removeAssociatedObjects
Removes all associations for a given object.
Declarationvoid
objc_removeAssociatedObjects(id object);
Parameters
object
An object that maintains associated objects.
Discussion
The main purpose of this function is to make it easy to return an object to a "pristine state”. You should not use this function for general removal of associations from objects, since it also removes associations that other clients may have added to the object. Typically you should use objc_setAssociatedObject with a nil value to clear an association.
刪除給定對象的所有關(guān)聯(lián)蚀浆。
- object 對象(關(guān)聯(lián)了許多值)
- 這個函數(shù)的主要目的是使對象返回一個“原始狀態(tài)”,你不應(yīng)該使用這個函數(shù)從對象中刪除關(guān)聯(lián)拦焚,因為它也刪除了其他客戶端可能添加到對象的關(guān)聯(lián) 蜡坊。通常應(yīng)該使用帶有nil值的objc_setAssociatedObject來清除關(guān)聯(lián)杠输。
優(yōu)秀樣例
添加私有屬性用于更好地去實現(xiàn)細節(jié)赎败。當擴展一個內(nèi)建類的行為時,保持附加屬性的狀態(tài)可能非常必要蠢甲。注意以下說的是一種非常教科書式的關(guān)聯(lián)對象的用例:AFNetworking在 UIImageView 的category上用了關(guān)聯(lián)對象來保持一個operation對象僵刮,用于從網(wǎng)絡(luò)上某URL異步地獲取一張圖片。
添加public屬性來增強category的功能。有些情況下這種(通過關(guān)聯(lián)對象)讓category行為更靈活的做法比在用一個帶變量的方法來實現(xiàn)更有意義搞糕。在這些情況下勇吊,可以用關(guān)聯(lián)對象實現(xiàn)一個一個對外開放的屬性∏涎觯回到上個AFNetworking的例子中的 UIImageView category汉规,它的 imageResponseSerializer方法允許圖片通過一個濾鏡來顯示、或在緩存到硬盤之前改變圖片的內(nèi)容驹吮。
創(chuàng)建一個用于KVO的關(guān)聯(lián)觀察者针史。當在一個category的實現(xiàn)中使用KVO時,建議用一個自定義的關(guān)聯(lián)對象而不是該對象本身作觀察者碟狞。ng an associated observer for KVO**. When using KVO in a category implementation, it is recommended that a custom associated-object be used as an observer, rather than the object observing itself.
反例
當值不需要的時候建立一個關(guān)聯(lián)對象啄枕。一個常見的例子就是在view上創(chuàng)建一個方便的方法去保存來自model的屬性、值或者其他混合的數(shù)據(jù)族沃。如果那個數(shù)據(jù)在之后根本用不到频祝,那么這種方法雖然是沒什么問題的,但用關(guān)聯(lián)到對象的做法并不可取脆淹。
當一個值可以被其他值推算出時建立一個關(guān)聯(lián)對象常空。例如:在調(diào)用 cellForRowAtIndexPath: 時存儲一個指向view的 UITableViewCell 中accessory view的引用,用于在 tableView:accessoryButtonTappedForRowWithIndexPath: 中使用盖溺。
用關(guān)聯(lián)對象替代X窟绷,這里的X可以代表下列含義:
1.當繼承比擴展原有的類更方便時用子類化。
?2.為事件的響應(yīng)者添加響應(yīng)動作咐柜。
?3.當響應(yīng)動作不方便使用時使用的手勢動作捕捉兼蜈。
?4.行為可以在其他對象中被代理實現(xiàn)時要用代理(delegate)。
?5.用NSNotification 和 NSNotificationCenter進行松耦合化的跨系統(tǒng)的事件通知拙友。
動態(tài)添加方法
Example:
- (IBAction)addMethod:(id)sender {
[self addMethodForPerson];
if ([self.xjy respondsToSelector:@selector(speakMyName)]) {
[self.xjy performSelector:@selector(speakMyName)];
}else{
NSLog(@"未添加成功");
}
}
- (void)addMethodForPerson {
class_addMethod([self.xjy class], @selector(speakMyName),(IMP)speakMyName, "v@:*");
}
void speakMyName(id self,SEL _cmd) {
NSLog(@"添加成功啊QAQ");
}
class_addMethod
class_addMethod
Adds a new method to a class with a given name and implementation.
Declaration
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
Parameters
cls
The class to which to add a method.nameA selector that specifies the name of the method being added.
imp
A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
types
An array of characters that describe the types of the arguments to the method. For possible values, see Objective-C Runtime Programming Guide > Type Encodings. Since the function must take at least two arguments—self and _cmd, the second and third characters must be “@:” (the first character is the return type).
Return Value
YES if the method was added successfully, otherwise NO (for example, the class already contains a method implementation with that name).
給一個類添加方法
- cls 被添加方法的類
- name 添加的方法的名稱的SEL
- imp 方法的實現(xiàn)为狸。該函數(shù)必須至少要有兩個參數(shù),self,_cmd.
class_addMethod添加實現(xiàn)將覆蓋父類的實現(xiàn)遗契,但不會替換此類中的現(xiàn)有實現(xiàn)辐棒。 要更改現(xiàn)有實現(xiàn),請使用method_setImplementation牍蜂。Objective-C方法只是一個C函數(shù)漾根,至少需要兩個參數(shù) - self和_cmd。
例如鲫竞,給定以下函數(shù):
void myMethodIMP(id self辐怕,SEL _cmd){
// implementation ....
}
你可以動態(tài)地將它添加到類作為一個方法(稱為resolveThisMethodDynamically)像這樣:
class_addMethod([self class],@selector(resolveThisMethodDynamically)从绘,(IMP)myMethodIMP寄疏,“v @:”);
類型編碼
Type Encodings
To assist the runtime system, the compiler encodes the return and argument types for each method in a character string and associates the string with the method selector.
為了輔助運行時系統(tǒng)是牢,編譯器對字符串中每個方法的返回和參數(shù)類型進行編碼,并將字符串與方法選擇器相關(guān)聯(lián)陕截。 它使用的編碼方案在其他上下文中也很有用驳棱,因此可以通過@encode()編譯器指令公開獲得。 當給定類型規(guī)范時农曲,@encode()返回該類型的字符串編碼社搅。 類型可以是基本類型,例如int乳规,指針罚渐,標記結(jié)構(gòu)或聯(lián)合,或類名 - 實際上可以用作C sizeof()運算符的參數(shù)的任何類型驯妄。
具體內(nèi)容參見 Objective-C Runtime Programming Guide
動態(tài)交換方法實現(xiàn)
Example:
#import "UIViewController+LogTracking.h"
#import <objc/runtime.h>
@implementation UIViewController (LogTracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xjy_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class,originalSelector);
Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);
//judge the method named swizzledMethod is already existed.
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// if swizzledMethod is already existed.
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)xjy_viewWillAppear:(BOOL)animated {
[self xjy_viewWillAppear:animated];
NSLog(@"viewWillAppear : %@",self);
}
@end
+load vs +initialize
swizzling應(yīng)該只在+load中完成荷并。 在 Objective-C 的運行時中,每個類有兩個方法都會自動調(diào)用青扔。+load 是在一個類被初始裝載時調(diào)用源织,+initialize 是在應(yīng)用第一次調(diào)用該類的類方法或?qū)嵗椒ㄇ罢{(diào)用的。兩個方法都是可選的微猖,并且只有在方法被實現(xiàn)的情況下才會被調(diào)用谈息。
dispatch_once
swizzling 應(yīng)該只在 dispatch_once 中完成
由于 swizzling 改變了全局的狀態(tài),所以我們需要確保每個預防措施在運行時都是可用的凛剥。原子操作就是這樣一個用于確保代碼只會被執(zhí)行一次的預防措施侠仇,就算是在不同的線程中也能確保代碼只執(zhí)行一次。Grand Central Dispatch 的 dispatch_once 滿足了所需要的需求犁珠,并且應(yīng)該被當做使用 swizzling 的初始化單例方法的標準逻炊。
method_getImplementation
method_getImplementation
Returns the implementation of a method.
Declaration
IMP method_getImplementation(Method m);
Parametersmethod
The method to inspect.
Return Value
A function pointer of type IMP.
返回方法的實現(xiàn)
- method Method
method_getTypeEncoding
method_getTypeEncoding
Returns a string describing a method's parameter and return types.
Declaration
const char * method_getTypeEncoding(Method m);
Parameters
method
The method to inspect.
Return Value
A C string. The string may be NULL.
返回一個C 字符串,描述方法的參數(shù)和返回類型.
- method Method
class_replaceMethod
class_replaceMethod
Replaces the implementation of a method for a given class.
Declaration
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);
Parameters
cls
The class you want to modify.
name
A selector that identifies the method whose implementation you want to replace.
imp
The new implementation for the method identified by name for the class identified by
cls.
types
An array of characters that describe the types of the arguments to the method. For possible values, see Objective-C Runtime Programming Guide > Type Encodings. Since the function must take at least two arguments—self and _cmd, the second and third characters must be “@:” (the first character is the return type).
Return Value
The previous implementation of the method identified by name for the class identified by cls.
替換指定方法的實現(xiàn)
- cls class
- name selector
- imp 新的IMP
- types 類型編碼
此函數(shù)以兩種不同的方式運行:
1犁享、如果通過名稱標識的方法不存在余素,則會像調(diào)用class_addMethod一樣添加它。 由類型指定的類型編碼按給定使用炊昆。
??2桨吊、如果按名稱標識的方法存在,那么將替換其IMP凤巨,就好像調(diào)用了method_setImplementation视乐。 將忽略由types指定的類型編碼。
method_exchangeImplementations
method_exchangeImplementations
Exchanges
the implementations of two methods.
Declaration
void method_exchangeImplementations(Method m1, Method m2);
交換兩個方法的實現(xiàn).
原子版本的實現(xiàn):
IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);
Selectors, Methods, & Implementations
在 Objective-C 的運行時中敢茁,selectors, methods, implementations 指代了不同概念佑淀,然而我們通常會說在消息發(fā)送過程中,這三個概念是可以相互轉(zhuǎn)換的卷要。 下面是蘋果 Objective-C Runtime Reference中的描述:
- Selector(typedef struct objc_selector *SEL):在運行時 Selectors 用來代表一個方法的名字渣聚。Selector 是一個在運行時被注冊(或映射)的C類型字符串。Selector由編譯器產(chǎn)生并且在當類被加載進內(nèi)存時由運行時自動進行名字和實現(xiàn)的映射僧叉。
- Method(typedef struct objc_method *Method):方法是一個不透明的用來代表一個方法的定義的類型奕枝。
- Implementation(typedef id (*IMP)(id, SEL,...)):這個數(shù)據(jù)類型指向一個方法的實現(xiàn)的最開始的地方。該方法為當前CPU架構(gòu)使用標準的C方法調(diào)用來實現(xiàn)瓶堕。該方法的第一個參數(shù)指向調(diào)用方法的自身(即內(nèi)存中類的實例對象隘道,若是調(diào)用類方法,該指針則是指向元類對象metaclass)郎笆。第二個參數(shù)是這個方法的名字selector谭梗,該方法的真正參數(shù)緊隨其后。
理解 selector, method, implementation 這三個概念之間關(guān)系的最好方式是:在運行時宛蚓,類(Class)維護了一個消息分發(fā)列表來解決消息的正確發(fā)送激捏。每一個消息列表的入口是一個方法(Method),這個方法映射了一對鍵值對凄吏,其中鍵是這個方法的名字 selector(SEL)远舅,值是指向這個方法實現(xiàn)的函數(shù)指針 implementation(IMP)。 Method swizzling 修改了類的消息分發(fā)列表使得已經(jīng)存在的 selector 映射了另一個實現(xiàn) implementation痕钢,同時重命名了原生方法的實現(xiàn)為一個新的 selector图柏。
思考
很多人認為交換方法實現(xiàn)會帶來無法預料的結(jié)果。然而采取了以下預防措施后, method swizzling 會變得很可靠:
- 在交換方法實現(xiàn)后記得要調(diào)用原生方法的實現(xiàn)(除非你非常確定可以不用調(diào)用原生方法的實現(xiàn)):APIs 提供了輸入輸出的規(guī)則任连,而在輸入輸出中間的方法實現(xiàn)就是一個看不見的黑盒蚤吹。交換了方法實現(xiàn)并且一些回調(diào)方法不會調(diào)用原生方法的實現(xiàn)這可能會造成底層實現(xiàn)的崩潰。
- 避免沖突:為分類的方法加前綴随抠,一定要確保調(diào)用了原生方法的所有地方不會因為你交換了方法的實現(xiàn)而出現(xiàn)意想不到的結(jié)果裁着。
- 理解實現(xiàn)原理:只是簡單的拷貝粘貼交換方法實現(xiàn)的代碼而不去理解實現(xiàn)原理不僅會讓 App 很脆弱,并且浪費了學習 Objective-C 運行時的機會拱她。閱讀 Objective-C Runtime Reference 并且瀏覽 能夠讓你更好理解實現(xiàn)原理跨算。
- 持續(xù)的預防:不管你對你理解 swlzzling 框架,UIKit 或者其他內(nèi)嵌框架有多自信椭懊,一定要記住所有東西在下一個發(fā)行版本都可能變得不再好使诸蚕。做好準備,在使用這個黑魔法中走得更遠氧猬,不要讓程序反而出現(xiàn)不可思議的行為背犯。
通過Method Swizzling可以把事件代碼或Logging,Authentication盅抚,Caching等跟主要業(yè)務(wù)邏輯代碼解耦漠魏。這種處理方式叫做Cross Cutting Concernshttp://en.wikipedia.org/wiki/Cross-cutting_concern 用Method Swizzling動態(tài)給指定的方法添加代碼解決Cross Cutting Concerns的編程方式叫Aspect Oriented Programming http://en.wikipedia.org/wiki/Aspect-oriented_programming 目前有些第三方庫可以很方便的使用AOP,比如Aspects https://github.com/steipete/Aspects 這里是使用Aspects的范例https://github.com/okcomp/AspectsDemo
消息分發(fā)
// 第一步
// 在沒有找到方法時妄均,會先調(diào)用此方法柱锹,可用于動態(tài)添加方法
// 返回 YES 表示相應(yīng) selector 的實現(xiàn)已經(jīng)被找到并添加到了類中哪自,否則返回 NO
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES;
}
// 第二步
// 如果第一步的返回 NO 或者直接返回了 YES 而沒有添加方法,該方法被調(diào)用
// 在這個方法中禁熏,我們可以指定一個可以返回一個可以響應(yīng)該方法的對象
// 如果返回 self 就會死循環(huán)
- (id)forwardingTargetForSelector:(SEL)aSelector{
if(aSelector == @selector(xxx:)){
return self.alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
// 第三步
// 如果 `forwardingTargetForSelector:` 返回了 nil壤巷,則該方法會被調(diào)用,系統(tǒng)會詢問我們要一個合法的『類型編碼(Type Encoding)』
// 若返回 nil瞧毙,則不會進入下一步胧华,而是無法處理消息- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {return [NSMethodSignature signatureWithObjCTypes:"v@:"];}
// 當實現(xiàn)了此方法后,-doesNotRecognizeSelector: 將不會被調(diào)用
// 如果要測試找不到方法宙彪,可以注釋掉這一個方法
// 在這里進行消息轉(zhuǎn)發(fā)
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 我們還可以改變方法選擇器[anInvocation setSelector:@selector(notFind)];
// 改變方法選擇器后矩动,還需要指定是哪個對象的方法
[anInvocation invokeWithTarget:self];
}
- (void)notFind {
NSLog(@"沒有實現(xiàn) -mysteriousMethod 方法,并且成功的轉(zhuǎn)成了 -notFind 方法");
}
部分內(nèi)容引用自
http://nshipster.cn/method-swizzling/
http://nshipster.cn/associated-objects/
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html
https://github.com/ming1016/study/wiki/Objc-Runtime