例子Demo 歡迎給我star!我會繼續分享的。
Objc Runtime使得C具有了面向對象能力,在程序運行時創建,檢查,修改類、對象和它們的方法。Runtime是C和匯編編寫的,這里http://www.opensource.apple.com/source/objc4/可以下到蘋果維護的開源代碼,GNU也有一個開源的runtime版本,他們都努力的保持一致。
Method An opaque type that rePResents a method in a class definition. Declaration typedef struct objc_method *Method;
代表類定義中的方法的不透明類型。
Class An opaque type that represents an Objective-C class. Declaration typedef struct objc_class *Class;
代表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.
指向方法實現的開始的內存地址的指針。
SEL Defines an opaque type that represents a method selector. Declaration typedef struct objc_selector *SEL;
代表方法的選擇器
runtime有很多的函數可以操作類和對象。類相關的是class為前綴,對象相關操作是objc或object_為前綴。
// 獲取類的類名
const char * class_getName ( Class cls );// 獲取類的父類
Class class_getSuperclass ( Class cls );// 判斷給定的Class是否是一個meta class
BOOL class_isMetaClass ( Class cls );// 獲取實例大小
size_t class_getInstanceSize ( Class cls );使用實例
Class cls = objc_allocateClassPair(MyClass.class, "MySubClass", 0);class_addMethod(cls, @selector(submethod1), (IMP)imp_submethod1, "v@:");class_replaceMethod(cls, @selector(method1), (IMP)imp_submethod1, "v@:");class_addIvar(cls, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");objc_property_attribute_t type = {"T", "@/"NSString/""};objc_property_attribute_t ownership = { "C", "" };objc_property_attribute_t backingivar = { "V", "_ivar1"};objc_property_attribute_t attrs[] = {type, ownership, backingivar};class_addProperty(cls, "property2", attrs, 3);objc_registerClassPair(cls);id instance = [[cls alloc] init];[instance performSelector:@selector(submethod1)];[instance performSelector:@selector(method1)];測試下效果
//可以看出class_createInstance和alloc的不同id theObject = class_createInstance(NSString.class, sizeof(unsigned));id str1 = [theObject init];NSLog(@"%@", [str1 class]);id str2 = [[NSString alloc] initWithString:@"test"];NSLog(@"%@", [str2 class]);這些函數是針對創建的實例對象的一系列操作函數。
應用場景
//把a轉換成占用更多空間的子類bNSObject *a = [[NSObject alloc] init];id newB = object_copy(a, class_getInstanceSize(MyClass.class));object_setClass(newB, MyClass.class);object_dispose(a);演示如何使用
int numClasses;Class * classes = NULL;numClasses = objc_getClassList(NULL, 0);if (numClasses > 0) { classes = malloc(sizeof(Class) * numClasses); numClasses = objc_getClassList(classes, numClasses); NSLog(@"number of classes: %d", numClasses); for (int i = 0; i < numClasses; i++) { Class cls = classes[i]; NSLog(@"class name: %s", class_getName(cls)); } free(classes);}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.
object 指定的對象const void *key keyvalue 值policy 存儲策略| 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的關聯值 - object 關聯的源對象 - key 關聯的key - Return Value 與對象的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.
刪除給定對象的所有關聯。 - object 對象(關聯了許多值) - 這個函數的主要目的是使對象返回一個“原始狀態”,你不應該使用這個函數從對象中刪除關聯,因為它也刪除了其他客戶端可能添加到對象的關聯 。通常應該使用帶有nil值的objc_setAssociatedObject來清除關聯。
添加私有屬性用于更好地去實現細節。當擴展一個內建類的行為時,保持附加屬性的狀態可能非常必要。注意以下說的是一種非常教科書式的關聯對象的用例: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可以代表下列含義:
當繼承比擴展原有的類更方便時用子類化。為事件的響應者添加響應動作。當響應動作不方便使用時使用的手勢動作捕捉。行為可以在其他對象中被代理實現時要用代理(delegate)。用NSNotification 和 NSNotificationCenter進行松耦合化的跨系統的事件通知。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).
給一個類添加方法 - cls 被添加方法的類 - name 添加的方法的名稱的SEL - imp 方法的實現。該函數必須至少要有兩個參數,self,_cmd.
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);}@endswizzling應該只在+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 Method
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 字符串,描述方法的參數和返回類型. - method Method
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.
替換指定方法的實現 - cls class - name selector - imp 新的IMP - types 類型編碼
此函數以兩種不同的方式運行: 1. 如果通過名稱標識的方法不存在,則會像調用class_addMethod一樣添加它。 由類型指定的類型編碼按給定使用。 2. 如果按名稱標識的方法存在,那么將替換其IMP,就好像調用了method_setImplementation。 將忽略由types指定的類型編碼。
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(typedef struct objc_selector *SEL):在運行時 Selectors 用來代表一個方法的名字。Selector 是一個在運行時被注冊(或映射)的C類型字符串。Selector由編譯器產生并且在當類被加載進內存時由運行時自動進行名字和實現的映射。Method(typedef struct objc_method *Method):方法是一個不透明的用來代表一個方法的定義的類型。Implementation(typedef id (*IMP)(id, SEL,…)):這個數據類型指向一個方法的實現的最開始的地方。該方法為當前CPU架構使用標準的C方法調用來實現。該方法的第一個參數指向調用方法的自身(即內存中類的實例對象,若是調用類方法,該指針則是指向元類對象metaclass)。第二個參數是這個方法的名字selector,該方法的真正參數緊隨其后。理解 selector, method, implementation 這三個概念之間關系的最好方式是:在運行時,類(Class)維護了一個消息分發列表來解決消息的正確發送。每一個消息列表的入口是一個方法(Method),這個方法映射了一對鍵值對,其中鍵是這個方法的名字 selector(SEL),值是指向這個方法實現的函數指針 implementation(IMP)。 Method swizzling 修改了類的消息分發列表使得已經存在的 selector 映射了另一個實現 implementation,同時重命名了原生方法的實現為一個新的 selector。
很多人認為交換方法實現會帶來無法預料的結果。然而采取了以下預防措施后, method swizzling 會變得很可靠:
在交換方法實現后記得要調用原生方法的實現(除非你非常確定可以不用調用原生方法的實現):APIs 提供了輸入輸出的規則,而在輸入輸出中間的方法實現就是一個看不見的黑盒。交換了方法實現并且一些回調方法不會調用原生方法的實現這可能會造成底層實現的崩潰。避免沖突:為分類的方法加前綴,一定要確保調用了原生方法的所有地方不會因為你交換了方法的實現而出現意想不到的結果。理解實現原理:只是簡單的拷貝粘貼交換方法實現的代碼而不去理解實現原理不僅會讓 App 很脆弱,并且浪費了學習 Objective-C 運行時的機會。閱讀 Objective-C Runtime Reference 并且瀏覽 能夠讓你更好理解實現原理。持續的預防:不管你對你理解 swlzzling 框架,UIKit 或者其他內嵌框架有多自信,一定要記住所有東西在下一個發行版本都可能變得不再好使。做好準備,在使用這個黑魔法中走得更遠,不要讓程序反而出現不可思議的行為。
|
新聞熱點
疑難解答