RunTime的應(yīng)用和理解

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;

}

}

}

以上就是關(guān)于runtime最常用的介紹,我還在學(xué)習(xí)當(dāng)中,會(huì)不停地完善,和大家分享進(jìn)步.最后給大家一個(gè)學(xué)習(xí)runtime的小技巧,畢竟看源碼真的很枯燥,可以去github上輸入import,就可以看到用到runtime的實(shí)例,使學(xué)習(xí)更有目標(biāo)和動(dòng)力.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末橄妆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子祈坠,更是在濱河造成了極大的恐慌害碾,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赦拘,死亡現(xiàn)場(chǎng)離奇詭異慌随,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)另绩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)儒陨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人笋籽,你說(shuō)我怎么就攤上這事蹦漠。” “怎么了车海?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵笛园,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我侍芝,道長(zhǎng)研铆,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任州叠,我火速辦了婚禮棵红,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘咧栗。我一直安慰自己逆甜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布致板。 她就那樣靜靜地躺著交煞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪斟或。 梳的紋絲不亂的頭發(fā)上素征,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼御毅。 笑死根欧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的端蛆。 我是一名探鬼主播咽块,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼欺税!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起揭璃,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤晚凿,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后瘦馍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體歼秽,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年情组,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了燥筷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡院崇,死狀恐怖肆氓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情底瓣,我是刑警寧澤谢揪,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站捐凭,受9級(jí)特大地震影響拨扶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茁肠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一患民、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧垦梆,春花似錦匹颤、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至站刑,卻和暖如春另伍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工摆尝, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留温艇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓堕汞,卻偏偏與公主長(zhǎng)得像勺爱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子讯检,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉琐鲁,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,679評(píng)論 0 9
  • 項(xiàng)目中經(jīng)常會(huì)有一些的功能模塊用到runtime,最近也在學(xué)習(xí)它.對(duì)于要不要閱讀runtime的源碼,我覺(jué)得僅...
    A_sura閱讀 503評(píng)論 1 1
  • 我們常常會(huì)聽(tīng)說(shuō) Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢人灼?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,174評(píng)論 0 7
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí)围段,它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 792評(píng)論 0 4
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 727評(píng)論 0 2