例子Demojavascript
Objc Runtime使得C具備了面向對象能力,在程序運行時建立,檢查,修改類、對象和它們的方法。Runtime是C和彙編編寫的,這裏www.opensource.apple.com/source/objc…html
Method
An opaque type that represents a method in a class definition.
Declaration
typedef struct objc_method *Method;java
表明類定義中的方法的不透明類型。git
Class
An opaque type that represents an Objective-C class.
Declaration
typedef struct objc_class *Class;github
表明Objective-C中的類編程
An opaque type that represents an instance variable.
Declaration
typedef struct objc_ivar *Ivar;緩存
表明實例變量網絡
IMP
A pointer to the start of a method implementation.架構
指向方法實現的開始的內存地址的指針。app
SEL
Defines an opaque type that represents a method selector.
Declaration
typedef struct objc_selector *SEL;
表明方法的選擇器
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
Sets an associated value for a given object using a given key and association policy.
Declaration
void 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.
Behavior | @property Equivalent | Description |
---|---|---|
OBJC_ASSOCIATION_ASSIGN | @property (assign) 或 @property (unsafe_unretained) | 指定一個關聯對象的弱引用。 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, strong) | 指定一個關聯對象的強引用,不能被原子化使用。 |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | 指定一個關聯對象的copy引用,不能被原子化使用。 |
OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | 指定一個關聯對象的強引用,能被原子化使用。 |
OBJC_ASSOCIATION_COPY | @property (atomic, copy) | 指定一個關聯對象的copy引用,能被原子化使用。 |
objc_getAssociatedObject
Returns the value associated with a given object for a given key.
Declaration
id 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.
返回給定對象的key的關聯值
objc_removeAssociatedObjects
Removes all associations for a given object.
Declaration
void 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.
刪除給定對象的全部關聯。
添加私有屬性用於更好地去實現細節。當擴展一個內建類的行爲時,保持附加屬性的狀態可能很是必要。注意如下說的是一種很是教科書式的關聯對象的用例:AFNetworking在 UIImageView 的category上用了關聯對象來保持一個operation對象,用於從網絡上某URL異步地獲取一張圖片。
添加public屬性來加強category的功能。有些狀況下這種(經過關聯對象)讓category行爲更靈活的作法比在用一個帶變量的方法來實現更有意義。在這些狀況下,能夠用關聯對象實現一個一個對外開放的屬性。回到上個AFNetworking的例子中的 UIImageView category,它的 imageResponseSerializer方法容許圖片經過一個濾鏡來顯示、或在緩存到硬盤以前改變圖片的內容。
建立一個用於KVO的關聯觀察者。當在一個category的實現中使用KVO時,建議用一個自定義的關聯對象而不是該對象自己做觀察者。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.
當值不須要的時候創建一個關聯對象。一個常見的例子就是在view上建立一個方便的方法去保存來自model的屬性、值或者其餘混合的數據。若是那個數據在以後根本用不到,那麼這種方法雖然是沒什麼問題的,但用關聯到對象的作法並不可取。
當一個值能夠被其餘值推算出時創建一個關聯對象。例如:在調用 cellForRowAtIndexPath: 時存儲一個指向view的 UITableViewCell 中accessory view的引用,用於在 tableView:accessoryButtonTappedForRowWithIndexPath: 中使用。
用關聯對象替代X,這裏的X能夠表明下列含義:
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
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.
name
A 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).
給一個類添加方法
class_addMethod添加實現將覆蓋父類的實現,但不會替換此類中的現有實現。 要更改現有實現,請使用method_setImplementation。
Objective-C方法只是一個C函數,至少須要兩個參數 - self和_cmd。 例如,給定如下函數:
void myMethodIMP(id self,SEL _cmd)
{
// implementation ....
}}複製代碼
你能夠動態地將它添加到類做爲一個方法(稱爲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.
爲了輔助運行時系統,編譯器對字符串中每一個方法的返回和參數類型進行編碼,並將字符串與方法選擇器相關聯。 它使用的編碼方案在其餘上下文中也頗有用,所以能夠經過@encode()編譯器指令公開得到。 當給定類型規範時,@encode()返回該類型的字符串編碼。 類型能夠是基本類型,例如int,指針,標記結構或聯合,或類名 - 實際上能夠用做C sizeof()運算符的參數的任何類型。
具體內容參見 Objective-C Runtime Programming Guide
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複製代碼
swizzling應該只在+load中完成。 在 Objective-C 的運行時中,每一個類有兩個方法都會自動調用。+load 是在一個類被初始裝載時調用,+initialize 是在應用第一次調用該類的類方法或實例方法前調用的。兩個方法都是可選的,而且只有在方法被實現的狀況下才會被調用。
swizzling 應該只在 dispatch_once 中完成
因爲 swizzling 改變了全局的狀態,因此咱們須要確保每一個預防措施在運行時都是可用的。原子操做就是這樣一個用於確保代碼只會被執行一次的預防措施,就算是在不一樣的線程中也能確保代碼只執行一次。Grand Central Dispatch 的 dispatch_once 知足了所須要的需求,而且應該被當作使用 swizzling 的初始化單例方法的標準。
method_getImplementation
Returns the implementation of a method.
Declaration
IMP method_getImplementation(Method m);
Parameters
method
The method to inspect.
Return Value
A function pointer of type IMP.
返回方法的實現
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 字符串,描述方法的參數和返回類型.
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.
替換指定方法的實現
此函數以兩種不一樣的方式運行:
method_exchangeImplementations
Exchanges the implementations of two methods.
Declaration
void method_exchangeImplementations(Method m1, Method m2);
交換兩個方法的實現.
原子版本的實現:
IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);複製代碼
在 Objective-C 的運行時中,selectors, methods, implementations 指代了不一樣概念,然而咱們一般會說在消息發送過程當中,這三個概念是能夠相互轉換的。 下面是蘋果 Objective-C Runtime Reference中的描述:
理解 selector, method, implementation 這三個概念之間關係的最好方式是:在運行時,類(Class)維護了一個消息分發列表來解決消息的正確發送。每個消息列表的入口是一個方法(Method),這個方法映射了一對鍵值對,其中鍵是這個方法的名字 selector(SEL),值是指向這個方法實現的函數指針 implementation(IMP)。 Method swizzling 修改了類的消息分發列表使得已經存在的 selector 映射了另外一個實現 implementation,同時重命名了原生方法的實現爲一個新的 selector。
不少人認爲交換方法實現會帶來沒法預料的結果。然而採起了如下預防措施後, method swizzling 會變得很可靠:
經過Method Swizzling能夠把事件代碼或Logging,Authentication,Caching等跟主要業務邏輯代碼解耦。這種處理方式叫作Cross Cutting Concernsen.wikipedia.org/wiki/Cross-… 用Method Swizzling動態給指定的方法添加代碼解決Cross Cutting Concerns的編程方式叫Aspect Oriented Programming en.wikipedia.org/wiki/Aspect… 目前有些第三方庫能夠很方便的使用AOP,好比Aspects github.com/steipete/As… 這裏是使用Aspects的範例github.com/okcomp/Aspe…
// 第一步
// 在沒有找到方法時,會先調用此方法,可用於動態添加方法
// 返回 YES 表示相應 selector 的實現已經被找到並添加到了類中,不然返回 NO
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES;
}
// 第二步
// 若是第一步的返回 NO 或者直接返回了 YES 而沒有添加方法,該方法被調用
// 在這個方法中,咱們能夠指定一個能夠返回一個能夠響應該方法的對象
// 若是返回 self 就會死循環
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(xxx:)){
return self.alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
// 第三步
// 若是 `forwardingTargetForSelector:` 返回了 nil,則該方法會被調用,系統會詢問咱們要一個合法的『類型編碼(Type Encoding)』
// 若返回 nil,則不會進入下一步,而是沒法處理消息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
// 當實現了此方法後,-doesNotRecognizeSelector: 將不會被調用
// 若是要測試找不到方法,能夠註釋掉這一個方法
// 在這裏進行消息轉發
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 咱們還能夠改變方法選擇器
[anInvocation setSelector:@selector(notFind)];
// 改變方法選擇器後,還須要指定是哪一個對象的方法
[anInvocation invokeWithTarget:self];
}
- (void)notFind {
NSLog(@"沒有實現 -mysteriousMethod 方法,而且成功的轉成了 -notFind 方法");
}複製代碼
部份內容引用和翻譯自
nshipster.cn/method-swiz…
nshipster.cn/associated-…
developer.apple.com/library/con…
github.com/ming1016/st…
第二篇 深刻理解OC Runtime