runtime的理解和應(yīng)用
項(xiàng)目中經(jīng)常會(huì)有一些的功能模塊用到runtime,最近也在學(xué)習(xí)它.對(duì)于要不要閱讀runtime的源碼,我覺(jué)得僅僅是處理正常的開(kāi)發(fā),那真的沒(méi)有必要,只要把常用的一些函數(shù)看下和原理理解下就可以了.但是如果真能靜下心好好閱讀源碼菠发,真的能幫你更加深入理解objc本身以及經(jīng)過(guò)高階包裝出來(lái)的那些特性铭若。
什么是runtimeruntime就是運(yùn)行時(shí),每個(gè)語(yǔ)言都有它的runtime.通俗點(diǎn)講就是程序運(yùn)行時(shí)發(fā)生的事情.比如C語(yǔ)言,在編譯的時(shí)候就決定了調(diào)用哪些函數(shù),通過(guò)編譯后就一步步執(zhí)行下去,沒(méi)有任何二義性,所以它是靜態(tài)語(yǔ)言.而objc的函數(shù)調(diào)用則可以理解為發(fā)消息,在編譯的時(shí)候完全不能決定哪個(gè)函數(shù)執(zhí)行,只有在運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)名找到函數(shù)調(diào)用,所以在運(yùn)行的時(shí)候它能動(dòng)態(tài)地添加調(diào)換屬性,函數(shù).所以它是動(dòng)態(tài)語(yǔ)言.動(dòng)態(tài)和靜態(tài)語(yǔ)言沒(méi)有明顯的界限,我感覺(jué)它們就是以runtime來(lái)區(qū)分的,看它在runtime時(shí),有多靈活,那么它就有多動(dòng)態(tài).
相關(guān)定義
typedef struct objc_method *Method
struct objc_method {SEL method_name; ?
?char *method_types;
IMP method_imp;}?
SEL是char*,可以理解為函數(shù)的姓名.
IMP就是函數(shù)指針,指向函數(shù)的實(shí)現(xiàn)
.==在objc_class中method list保存了一個(gè)SEL<>IMP的映射.所以通過(guò)SEL可以找到函數(shù)的實(shí)現(xiàn)==
typedef struct objc_ivar *Ivar;
struct objc_ivar { ??
?char *ivar_name;??
? char *ivar_type;? ??
int ivar_offset;
#ifdef __LP64__? ? int space;
#endif}? ? ? ? ? ? ? ? ? ? ??
? ?實(shí)例變量仔拟,跟某個(gè)對(duì)象關(guān)聯(lián),不能被靜態(tài)方法使用,與之想對(duì)應(yīng)的是類變量
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可以動(dòng)態(tài)地為已經(jīng)存在的類添加新的行為宵蛀。比如類方法,實(shí)例方法,協(xié)議.
==根據(jù)結(jié)構(gòu)可知,不能添加屬性,實(shí)例變量==
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];
}? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ==簡(jiǎn)單地理解為存有方法和實(shí)例變量的數(shù)組==
//類在runtime中的表示
struct objc_class {
Class isa;//指針坐桩,顧名思義惧辈,表示是一個(gè)什么翁垂,
//實(shí)例的isa指向類對(duì)象铆遭,類對(duì)象的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;//緩存
//一種優(yōu)化,調(diào)用過(guò)的方法存入緩存列表沿猜,下次調(diào)用先找緩存
struct objc_protocol_list *protocols //協(xié)議列表
#endif
};
struct objc_cache {
unsigned int mask;
unsigned int occupied;
Method buckets[1];
};
==objc_cache可以理解為存最近調(diào)用過(guò)的方法的數(shù)組,每次調(diào)用先訪問(wèn)它,提高效率==
runtime常用方法
獲取列表
我們可以通過(guò)runtime的一系列方法獲取類的一些信息(包括屬性列表枚荣,方法列表,成員變量列表啼肩,和遵循的協(xié)議列表)
class_copyPropertyList? ? ? //獲取屬性列表
class_copyMethodList? ? ? ? //獲取方法列表
class_copyIvarList? ? ? ? ? //獲取成員變量列表
class_copyProtocolList? ? ? //獲取協(xié)議列表
常見(jiàn)用于字典轉(zhuǎn)模型的需求中:
@interface LYUser : NSObject
@property (nonatomic,strong)NSString *userId;
@property (nonatomic,strong)NSString *userName;
@property (nonatomic,strong)NSString *age;
@end
- (void)viewDidLoad {
[super viewDidLoad];
//利用runtime遍歷一個(gè)類的全部成員變量
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);
}
==這只是最簡(jiǎn)單的轉(zhuǎn)化,還要考慮容錯(cuò),轉(zhuǎn)換效率,現(xiàn)在有很多開(kāi)源框架做的很不錯(cuò).這是一些開(kāi)源框架的性能對(duì)比:==模型轉(zhuǎn)換庫(kù)評(píng)測(cè)結(jié)果
交換方法
class_getInstanceMethod() //類方法和實(shí)例方法存在不同的地方,所以兩個(gè)不同的方法獲得
class_getClassMethod()? ? //以上兩個(gè)函數(shù)傳入返回Method類型
method_exchangeImplementations? ? //()交換兩個(gè)方法的實(shí)現(xiàn)
==這個(gè)用到的地方很多,可以大大減少我們的代碼量,常用的有防錯(cuò)措施,統(tǒng)計(jì)打點(diǎn),統(tǒng)一更新界面效果==
防錯(cuò)措施
-(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);
}
/*
* 自己寫(xiě)的方法實(shí)現(xiàn)
*/
-(void)el_addObject:(id)object
{
if (object != nil) {
[self el_addObject:object];
}
else
{
//可以添加錯(cuò)誤日志
NSLog(@"數(shù)組添加nil");
}
}
@end
統(tǒng)計(jì)打點(diǎn)
和上面的實(shí)現(xiàn)方式一致.在對(duì)應(yīng)類的Category的load方法里交換.
//? 統(tǒng)計(jì)頁(yè)面出現(xiàn)
Method originAddMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
Method newAddMethod = class_getInstanceMethod([self class], @selector(el_ViewDidLoad));
method_exchangeImplementations(originAddMethod, newAddMethod);
//? 統(tǒng)計(jì)Button點(diǎn)擊
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);
統(tǒng)一更新界面效果
很多時(shí)候我們做項(xiàng)目都是先做邏輯,一些頁(yè)面顏色,細(xì)節(jié)都是最后做.這就遇到了一些問(wèn)題,可能只是改個(gè)cell右邊箭頭邊距,placeholder默認(rèn)顏色.如果一個(gè)個(gè)改過(guò)來(lái)又麻煩又有可能有疏漏,這個(gè)時(shí)候runtime就可以大顯神通了.
//這個(gè)就可以統(tǒng)一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);
});
}
//設(shè)置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;
}
}
//設(shè)置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;
}
關(guān)聯(lián)對(duì)象
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
objc_getAssociatedObject(id object, const void *key)
前面已經(jīng)講過(guò),Category不能添加屬性,通過(guò)關(guān)聯(lián)對(duì)象就可以在運(yùn)行時(shí)動(dòng)態(tài)地添加屬性.
這可是神器,對(duì)于封裝代碼很有用,例如很常見(jiàn)的,textField限制長(zhǎng)度.每個(gè)都在delegate里重復(fù)代碼肯定不行.自己寫(xiě)個(gè)自定義textField,better,不過(guò)還是有點(diǎn)麻煩.而runtime就可以很優(yōu)雅地解決問(wèn)題.
.h
@interface UITextField (TextRange)
@property (nonatomic, assign) NSInteger maxLength;? //每次限制的長(zhǎng)度設(shè)置下就行了
@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;
}
}
}