項目中常常會有一些的功能模塊用到runtime,最近也在學習它.對於要不要閱讀runtime的源碼,我以爲僅僅是處理正常的開發,那真的沒有必要,只要把經常使用的一些函數看下和原理理解下就能夠了.
可是若是真能靜下心好好閱讀源碼,真的能幫你更加深刻理解objc自己以及通過高階包裝出來的那些特性。ios
runtime就是運行時,每一個語言都有它的runtime.通俗點講就是程序運行時發生的事情.
好比C語言,在編譯的時候就決定了調用哪些函數,經過編譯後就一步步執行下去,沒有任何二義性,因此它是靜態語言.
而objc的函數調用則能夠理解爲發消息,在編譯的時候徹底不能決定哪一個函數執行,只有在運行的時候纔會根據函數名找到函數調用,因此在運行的時候它能動態地添加調換屬性,函數.因此它是動態語言.
動態和靜態語言沒有明顯的界限,我感受它們就是以runtime來區分的,看它在runtime時,有多靈活,那麼它就有多動態.git
typedef struct objc_method *Method struct objc_method { SEL method_name; char *method_types; IMP method_imp; }
SEL是char*,能夠理解爲函數的姓名.
IMP就是函數指針,指向函數的實現.
==在objc_class中method list保存了一個SEL<>IMP的映射.因此經過SEL能夠找到函數的實現==github
typedef struct objc_ivar *Ivar; struct objc_ivar { char *ivar_name; char *ivar_type; int ivar_offset; #ifdef __LP64__ int space; #endif }
實例變量,跟某個對象關聯,不能被靜態方法使用,與之想對應的是類變量數組
typedef struct objc_category *Category; struct objc_category { char *category_name; char *class_name; struct objc_method_list *instance_methods; struct objc_method_list *class_methods; struct objc_protocol_list *protocols; }
Catagory能夠動態地爲已經存在的類添加新的行爲。好比類方法,實例方法,協議.
==根據結構可知,不能添加屬性,實例變量==緩存
struct objc_method_list { struct objc_method_list *obsolete; int method_count; int space; struct objc_method method_list[1]; } struct objc_ivar_list { int ivar_count; int space; struct objc_ivar ivar_list[1]; }
==簡單地理解爲存有方法和實例變量的數組==框架
//類在runtime中的表示 struct objc_class { Class isa;//指針,顧名思義,表示是一個什麼, //實例的isa指向類對象,類對象的isa指向元類 #if !__OBJC2__ Class super_class; //指向父類 const char *name; //類名 long version; long info; long instance_size struct objc_ivar_list *ivars //成員變量列表 struct objc_method_list **methodLists; //方法列表 struct objc_cache *cache;//緩存 //一種優化,調用過的方法存入緩存列表,下次調用先找緩存 struct objc_protocol_list *protocols //協議列表 #endif }; struct objc_cache { unsigned int mask; unsigned int occupied; Method buckets[1]; };
==objc_cache能夠理解爲存最近調用過的方法的數組,每次調用先訪問它,提升效率==函數
class_copyPropertyList //獲取屬性列表 class_copyMethodList //獲取方法列表 class_copyIvarList //獲取成員變量列表 class_copyProtocolList //獲取協議列表
常見用於字典轉模型的需求中:性能
@interface LYUser : NSObject @property (nonatomic,strong)NSString *userId; @property (nonatomic,strong)NSString *userName; @property (nonatomic,strong)NSString *age; @end - (void)viewDidLoad { [super viewDidLoad]; //利用runtime遍歷一個類的所有成員變量 NSDictionary *userDict = @{@"userId":@"1",@"userName":@"levi",@"age":@"20"}; unsigned int count; LYUser *newUser = [LYUser new]; objc_property_t *propertyList = class_copyPropertyList([LYUser class], &count); for (int i = 0; i < count; i++) { const char *propertyName = property_getName(propertyList[i]); NSString *key = [NSString stringWithUTF8String:propertyName]; [newUser setValue:userDict[key] forKey:key]; } NSLog(@"%@--%@--%@",newUser.userId,newUser.userName,newUser.age); }
==這只是最簡單的轉化,還要考慮容錯,轉換效率,如今有不少開源框架作的很不錯.這是一些開源框架的性能對比:==模型轉換庫評測結果學習
class_getInstanceMethod() //類方法和實例方法存在不一樣的地方,因此兩個不一樣的方法得到 class_getClassMethod() //以上兩個函數傳入返回Method類型 method_exchangeImplementations //()交換兩個方法的實現
==這個用到的地方不少,能夠大大減小咱們的代碼量,經常使用的有防錯措施,統計打點,統一更新界面效果==優化
防錯措施
-(void)viewDidLoad { NSMutableArray *testArray = [NSMutableArray new]; [testArray addObject:@"1"]; NSString *a = nil; [testArray addObject:a]; for (NSInteger i = 0; i < testArray.count; i++) { NSLog(@"%@",testArray[i]); } } @implementation NSMutableArray(ErrorLog) +(void)load { Method originAddMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(addObject:)); Method newAddMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(el_addObject:)); method_exchangeImplementations(originAddMethod, newAddMethod); } /* * 本身寫的方法實現 */ -(void)el_addObject:(id)object { if (object != nil) { [self el_addObject:object]; } else { //能夠添加錯誤日誌 NSLog(@"數組添加nil"); } } @end
統計打點
和上面的實現方式一致.在對應類的Category的load方法裏交換.
// 統計頁面出現 Method originAddMethod = class_getInstanceMethod([self class], @selector(viewDidLoad)); Method newAddMethod = class_getInstanceMethod([self class], @selector(el_ViewDidLoad)); method_exchangeImplementations(originAddMethod, newAddMethod); // 統計Button點擊 Method originAddMethod = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:)); Method newAddMethod = class_getInstanceMethod([self class],@selector(el_sendAction:to:forEvent:))); method_exchangeImplementations(originAddMethod, newAddMethod);
統一更新界面效果
不少時候咱們作項目都是先作邏輯,一些頁面顏色,細節都是最後作.這就遇到了一些問題,可能只是改個cell右邊箭頭邊距,placeholder默認顏色.若是一個個改過來又麻煩又有可能有疏漏,這個時候runtime就能夠大顯神通了.
//這個就能夠統一cell右邊箭頭格式,很是方便 + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(layoutSubviews); SEL swizzledSelector = @selector(swizzling_layoutSubviews); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); method_exchangeImplementations(originalMethod, swizzledMethod); }); } //設置cell右邊箭頭 - (void)setAccessoryType:(UITableViewCellAccessoryType)accessoryType { if (accessoryType == UITableViewCellAccessoryDisclosureIndicator) { UIImageView *accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"about_arrow_icon"]]; accessoryView.centerY = self.centerY; accessoryView.right = self.width-16; self.accessoryView = accessoryView; } else if (accessoryType == UITableViewCellAccessoryNone) { self.accessoryView = nil; } } //設置cell右邊箭頭間距 - (void)swizzling_layoutSubviews { [self swizzling_layoutSubviews]; if (self.imageView.image) { self.imageView.origin = CGPointMake(16, self.imageView.origin.y); self.textLabel.origin = CGPointMake(CGRectGetMaxX(self.imageView.frame)+10, self.textLabel.origin.y); } else { self.textLabel.origin = CGPointMake(16, self.textLabel.origin.y); } self.textLabel.width = MIN(self.textLabel.width, 180); self.accessoryView.right = self.width-16; }
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) objc_getAssociatedObject(id object, const void *key)
前面已經講過,Category不能添加屬性,經過關聯對象就能夠在運行時動態地添加屬性.
這但是神器,對於封裝代碼頗有用,例如很常見的,textField限制長度.每一個都在delegate裏重複代碼確定不行.本身寫個自定義textField,better,不過仍是有點麻煩.而runtime就能夠很優雅地解決問題.
.h @interface UITextField (TextRange) @property (nonatomic, assign) NSInteger maxLength; //每次限制的長度設置下就好了 @end .m - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)setMaxLength:(NSInteger)maxLength { objc_setAssociatedObject(self, KTextFieldMaxLength, @(maxLength), OBJC_ASSOCIATION_RETAIN_NONATOMIC); [self textField_addTextDidChangeObserver]; } - (NSInteger)maxLength { return [objc_getAssociatedObject(self, KTextFieldMaxLength) integerValue]; } #pragma mark - Private method - (void)textField_addTextDidChangeObserver { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textField_textDidChange:) name:UITextFieldTextDidChangeNotification object:self]; } #pragma mark - NSNotificationCenter action - (void)textField_textDidChange:(NSNotification *)notification { UITextField *textField = notification.object; NSString *text = textField.text; MYTitleInfo titleInfo = [text getInfoWithMaxLength:self.maxLength]; if (titleInfo.length > self.maxLength) { UITextRange *selectedRange = [textField markedTextRange]; UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0]; if (!position) { UITextRange *textRange = textField.selectedTextRange; textField.text = [textField.text subStringWithMaxLength:self.maxLength]; textField.selectedTextRange = textRange; } } }
以上就是關於runtime最經常使用的介紹,我還在學習當中,會不停地完善,和你們分享進步.
最後給你們一個學習runtime的小技巧,畢竟看源碼真的很枯燥,能夠去github上輸入import <objc/runtime.h>
,就能夠看到用到runtime的實例,使學習更有目標和動力.