在FDFullscreenPopGesture
中給UIViewController的分類裏有這麼一個屬性:docker
@property (nonatomic, copy) _FDViewControllerWillAppearInjectBlock fd_willAppearInjectBlock;
這是一個block的屬性,block定義以下:安全
typedef void (^_FDViewControllerWillAppearInjectBlock)(UIViewController *viewController, BOOL animated);
看到這裏也許你會提問,OC中不是不能給分類添加屬性麼?正常狀況下,OC是不容許給OC添加屬性的。可是利用Runtime的特性,這是能夠辦到的。實現方法以下:bash
- (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock { return objc_getAssociatedObject(self, _cmd);// 根據關聯的key,獲取關聯的值。這裏的key等於_cmd,_cmd等於fd_willAppearInjectBlock } - (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block { // 第一個參數:給哪一個對象添加關聯 // 第二個參數:關聯的key,經過這個key獲取 // 第三個參數:關聯的value // 第四個參數:關聯的策略 objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);//關聯對象 }
動態給分類添加屬性的方法是:less
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
獲取這個屬性的方法是:函數
objc_getAssociatedObject(id object, const void *key)
還有一個方法是移除屬性:atom
objc_removeAssociatedObjects(id object)
是的,這樣就動態的給UIViewController
的分類添加了fd_willAppearInjectBlock
這麼一個屬性。spa
NOTE:在使用Runtime的這些方法的時候不要忘了導入
objc/runtime.h
這個頭文件哦!3d
要想動態添加方法咱們必須瞭解方法是如何執行的,一般咱們調用方法是經過[object message]
這種方法,除了這種方法還有一種是比較少用的,就是[object performSelector:@selector(message)]
這種方式。經過下面這張圖咱們能夠了解一下他們對消息的處理的不一樣之處。指針
經過上圖,咱們能夠得知,要想動態添加方法必須是經過[object performSelector:@selector(message)]
這種方式調用方法才能在運行時階段經過Runtime的一些方法達到動態的添加方法。若是如今有一個Person
類,在其它地方經過performSelector
的方式調用Person
的run
方法。可是Person
類中並無實現這個方法。code
Person p = [Person alloc] init];
// 這個時候即便Person類沒有實現run方法編譯器也不會報錯 [p performSelector:@selector(run)];
這時候只須要在Person
中實現resolveInstanceMethod:
方法就能夠達到動態添加方法的目的。
//首先咱們要在Person類裏面實現咱們要動態添加的方法 // 要注意,默認方法都有兩個隱式參數 void run(id self,SEL sel){ NSLog(@"%@ %@",self,NSStringFromSelector(sel)); } // 當一個對象調用未實現的方法,會調用這個方法處理,而且會把對應的方法列表傳過來. // 恰好能夠用來判斷未實現的方法是否是咱們想要動態添加的方法 + (BOOL)resolveInstanceMethod:(SEL)sel{ //先判斷一下傳過來的是否是run方法 if (sel == @selector(run)){ //若是是run方法就動態添加run方法 class_addMethod(self.class, @selector(run),(IMP)run, "v@:"); // 第一個參數:給哪一個類添加方法 // 第二個參數:添加方法的方法編號 // 第三個參數:添加方法的函數實現(函數地址),若是是OC方法 //能夠用+(IMP)instanceMethodForSelector:(SEL)aSelector;得到方法的實現。 // 第四個參數:方法的簽名,(返回值+參數類型) v:void @:對象->self :表示SEL->_cmd } }
這樣就達到了給一個類動態添加方法的效果了,若是想把方法轉發給其餘的類實現,須要處理消息轉發的第二或第三個函數了。
當一些時候,系統自帶效果知足不了咱們的時候,要麼咱們自定義,要麼直接替換系統的方法。在公有的API是沒有方法辦到的。咱們來看一段FDFullscreenPopGesture
的代碼(註釋是我加的):
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class]; //獲取系統方法的SEL SEL originalSelector = @selector(viewWillAppear:); //獲取替換方法的SEL SEL swizzledSelector = @selector(fd_viewWillAppear:); //爲了獲取IMP指針,得到方法的Method Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); //爲了安全起見,先判斷是否已經存在要交換的方法 BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (success) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } - (void)fd_viewWillAppear:(BOOL)animated { //不要認爲這句代碼有錯,其實很好理解,在調用這句的時候方法已經交換了 // Forward to primary implementation. [self fd_viewWillAppear:animated]; if (self.fd_willAppearInjectBlock) { self.fd_willAppearInjectBlock(self, animated); } }
經過上面的代碼咱們能夠看出來,替換系統自帶的方式實現須要用到的重要方法是method_exchangeImplementations()
方法,而且要注意替換方法裏面對本身的調用。這個方法也就是人們常說的Method Swizzling
黑魔法,用的時候要注意,這是一把雙刃劍!
Runtime在項目中不多用,可是要理解它,理解了以後用起來也不危險。若是你喜歡個人文章,不妨掃一掃下面的二維碼請我喝杯茶。祝你們在iOS開發的道路上玩得愉快!