以前在看一些第三方源碼的時候,時不時的能碰到一些關於運行時相關的代碼。因而乎,就閱讀了一些關於運行時的文章,感受寫的都不錯,寫此篇文章爲了記錄一下,同時也從新學習一遍。app
objc_msgSend
,只有對象才能發送消息,所以以objc
開頭。注意:在oc中,不管是實例對象仍是Class,都是id類型的對象讓咱們來看看方法調用轉化成運行時的代碼,看看調用方法的真面目吧。函數
而後在main方法裏面寫上學習
int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *obj = [[NSObject alloc] init]; } return 0; }
clang -rewrite-objc main.m
而後ls查看一下當前目錄能夠看到有一個main.cpp
文件,咱們用open main.cpp
打開該文件,能夠看到一大串代碼,咱們能夠直接翻到底部,能夠看到這樣的代碼:atom
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")); } return 0; }
刪除掉一些強制轉換,將上面的代碼簡化後:spa
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; NSObject *obj = objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")); } return 0; }
總結:到這裏,咱們能夠看到調用方法的本質就是發送消息了,而且能夠看到咱們寫的命令行
NSObject *obj = [[NSObject alloc] init];上面這條語句發送了兩次消息,第一次發送了
alloc
消息,第二次發送了init
消息。code
相信你們都知道分類是不能夠添加屬性的,不過咱們能夠經過運行時,給分類動態的添加屬性。orm
原理:給一個分類聲明屬性,其本質就是給這個類添加關聯,並非直接把這個值的內存空間添加到類存空間。對象
咱們給NSObject
添加一個分類,而後聲明一個name
屬性。繼承
#import <Foundation/Foundation.h> @interface NSObject (Extension) @property (nonatomic, copy) NSString *name; @end
NSObject
對象,而後給name
屬性賦值,而且打印name
的值。接下來咱們在.m文件重寫name
屬性的setter
以及getter
方法。
#import "NSObject+Extension.h" #import <objc/runtime.h> static const char *key = "name"; @implementation NSObject (Extension) -(NSString *)name { // 根據關聯的key,獲取關聯的值 return objc_getAssociatedObject(self, key); } -(void)setName:(NSString *)name { // 第一個參數:給哪一個對象添加關聯 // 第二個參數:關聯的key,經過這個key獲取 // 第三個參數:關聯的value // 第四個參數:關聯的策略 objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_COPY_NONATOMIC); } @end
運行程序,編譯成功,運行也成功。
建立一個繼承NSObject
的Student
類,聲明一個study
方法。
#import <Foundation/Foundation.h> @interface Student : NSObject -(void)study; @end
建立一個Student
對象,調用study
方法。
Student *s = [[Student alloc] init]; [s performSelector:@selector(study)];
此時會出現一個經典的報錯:
objc -[Student study]: unrecognized selector sent to instance 0x7fd719cbb2f0
錯誤緣由:調用一個未實現的實例方法
咱們在Student
類.m文件動態添加study方法的實現。
#import "Student.h" #import <objc/runtime.h> @implementation Student // void(*)() // 默認方法都有兩個隱式參數, void studyStudent(id self, SEL sel){ NSLog(@"%@--%@",self,NSStringFromSelector(sel)); } // 當一個對象調用未實現的方法,會調用該方法處理,而且會把對應的方法列表傳進來,咱們能夠在這個方法裏判斷,未實現的方法是否是咱們想要動態添加的方法。 +(BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(study)) { // 第一個參數:給哪一個類添加方法 // 第二個參數:添加方法的方法編號 // 第三個參數:添加方法的函數實現(函數地址) // 第四個參數:函數的類型,(返回值+參數類型) v:void @:對象->self :表示SEL->_cmd class_addMethod(self, @selector(study), studyStudent, "v@:"); // 注意:此處須要立刻結束此方法(不然,若是存在繼承關係的話,會調用到父類去) return YES; } return [super resolveInstanceMethod:sel]; } @end
Method Swizzling
。場景二:系統自帶的方法功能不夠,給系統自帶的方法擴展一些功能,而且保持原有功能。
場景一:統計整個項目每個頁面停留時長。(解決方法也不是惟一的)
方式一:找到全部的控制器,而後寫上:
-(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [MobClick beginLogPageView:NSStringFromClass([self class])]; } -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [MobClick endLogPageView:NSStringFromClass([self class])]; }
而後咱們一個項目可能有幾十個甚至上百個頁面須要統計,咱們總不可能每一個頁面都這樣寫吧。因而乎,就有了方式二。
方式二:全部的界面都繼承於一個基類,而後在基類中寫上
-(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [MobClick beginLogPageView:NSStringFromClass([self class])]; } -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [MobClick endLogPageView:NSStringFromClass([self class])]; }
但是一開始寫項目的時候,並無使用到繼承,因此又papapa地就整個項目的控制器都繼承於一個基類,重複地將每個控制器的繼承都該成了咱們建立的基類。可是,這樣解決真的好麼,有可能咱們有些界面是繼承自UITableViewController
的,UICollectionViewController
,等等。那麼你就可能會對這些控制器再單獨的寫上面的代碼了。
好不容易將整個項目改過來了,而後某天,公司來了一位新人,你告訴他全部的類都要繼承自你寫的那個基類,新手老是會不經意地犯錯誤(也有多是人家尚未習慣),有些類忘記繼承了,後期排查起來費力費時。那麼有沒有更好地解決方式呢?方式三就能夠處理這種問題。
方式三:使用Method Swizzling
實現,給UIViewController
寫一個分類。
#import "UIViewController+Help.h" #import <objc/runtime.h> @implementation UIViewController (Help) +(void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; methodSwizzling(class, @selector(viewWillAppear:), @selector(scott_viewWillAppear:)); methodSwizzling(class, @selector(viewWillDisappear:), @selector(scott_viewWillDisappear:)); }); } void methodSwizzling(Class class, SEL originSelector, SEL swizzSelector){ Method originMethod = class_getInstanceMethod(class, originSelector); Method swizzMethod = class_getInstanceMethod(class, swizzSelector); BOOL isAddMethod = class_addMethod(class, originSelector, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod)); if (isAddMethod) { class_replaceMethod(class, swizzSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod)); }else{ method_exchangeImplementations(originMethod, swizzMethod); } } -(void)scott_viewWillAppear:(BOOL)animated { [self scott_viewWillAppear:animated]; NSLog(@"調用自定義viewWillAppear"); } -(void)scott_viewWillDisappear:(BOOL)animated { [self scott_viewWillDisappear:animated]; NSLog(@"調用自定義viewWillDissappear"); } @end
這個單獨開一篇給你們講講吧。
但願經過本文能讓你們學習到一些關於Runtime的知識,若是有什麼疑問,歡迎你們一塊兒討論。