runtime運行時介紹與運用(一)

接下來的幾篇文章會講解到以下內容:

1.消息機制

2.獲取類的屬性和方法列表

3.方法交換(方法欺騙)

4.動態(tài)方法決議(或攔截調用)

5.關聯(lián)屬性(associative)

一.runtime簡單介紹.

????????? Objective-C語言是一門動態(tài)語言扮念,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了運行時來處理碧库。這種動態(tài)語言的優(yōu)勢在于:我們寫代碼時能夠更具靈活性巧勤,如我們可以把消息轉發(fā)給我們想要的對象,或者隨意交換一個方法的實現(xiàn)等颅悉。??

????????? 這種特性意味著Objective-C不僅需要一個編譯器迁匠,還需要一個運行時系統(tǒng)來執(zhí)行編譯的代碼剩瓶。對于Objective-C來說城丧,這個運行時系統(tǒng)就像一個操作系統(tǒng)一樣:它讓所有的工作可以正常的運行。這個運行時系統(tǒng)即Objc Runtime亡哄。Objc Runtime其實是一個Runtime庫,它基本上是用C和匯編寫的愿卸,這個庫使得C語言有了面向對象的能力。

Runtime庫主要做下面幾件事:

??????????? 封裝:在這個庫中趴荸,對象可以用C語言中的結構體表示宦焦,而方法可以用C函數(shù)來實現(xiàn)赊舶,另外再加上了一些額外的特性赶诊。這些結構體和函數(shù)被runtime函數(shù)封裝后园骆,我們就可以在程序運行時創(chuàng)建,檢查锌唾,修改類、對象和它們的方法了晌涕。

??????????? 找出方法的最終執(zhí)行代碼:當程序執(zhí)行[object doSomething]時,會向消息接收者(object)發(fā)送一條消息(doSomething)余黎,runtime會根據(jù)消息接收者是否能響應該消息而做出不同的反應惧财。這將在后面詳細介紹扭仁。

Objective-C

runtime目前有兩個版本:Modern runtime和Legacy runtime厅翔。Modern Runtime 覆蓋了64位的Mac

OS X Apps,還有 iOS Apps刀闷,Legacy Runtime 是早期用來給32位 Mac OS X Apps

用的,也就是可以不用管就是了


? ? ? OC中一切都被設計成了對象顽分,我們都知道一個類被初始化成一個實例筒扒,這個實例是一個對象。實際上一個類本質上也是一個對象悬秉,在runtime中用結構體表示冰蘑。

<objc/runtime.h>框架中相關的定義:

typedef struct objc_method *Method;? //描述類中的一個方法

typedef struct objc_ivar *Ivar; //實例變量

typedef struct objc_category *Category; //類別Category

typedef struct objc_property *objc_property_t; //類中聲明的屬性

類在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;//緩存, 一種優(yōu)化剂桥,調用過的方法存入緩存列表,下次調用先找緩存

struct objc_protocol_list *protocols//協(xié)議列表

#endif

} OBJC2_UNAVAILABLE;

/* Use `Class` instead of `struct objc_class *` */


二.簡單運用.(待補充)

1.消息機制

例:自定義Person類美尸,并聲明實現(xiàn)方法- (void)eat, 我們在控制器中調用oc代碼如下:

Person *p1 = [[Person alloc]init];

//1.1 消息發(fā)送

[p1 eat];


oc代碼在Runtime庫中轉化成底層c代碼如下:

Class PersonClass = objc_getClass("Person");

//注意Class實際上也是對象斟薇,所以同樣能夠接受消息,向Class發(fā)送alloc消息

Person *p1 = objc_msgSend(PersonClass, sel_registerName("alloc"));

//發(fā)送init消息給Person實例p1

p1 = objc_msgSend(p1, sel_registerName("init"));

//發(fā)送eat消息給p1胯陋,即調用eat方法

objc_msgSend(p1, sel_registerName("eat"));

//合并以上消息傳遞過程

objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("eat"));

如何證明?(如何能看到編譯后的文件寿弱?)

1). 打開終端按灶。cd 到工程目錄下;

cd /Users/qinlun/Desktop/runtime\ -\ 02\ 消息發(fā)送的副本/runtime-消息發(fā)送

2). 執(zhí)行 ls 查看工程目錄下的文件,找到創(chuàng)建person對象的控制器文件ViewController.m;

3). 執(zhí)行rewriteoc ViewController.m(或者clang -rewrite-objc ViewController.m)等一會就會在工程目錄得到ViewConreoller.cpp文件(該文件即為編譯過的文件)鸯旁;

4.)用Xcode打開此.cpp文件大概在57077行可以找到如下代碼;

// @interface ViewController ()

/* @end */

// @implementation ViewController

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {

((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

Person *p1 = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));

((void (*)(id, SEL))(void *)objc_msgSend)((id)p1, sel_registerName("eat"));

注意:在執(zhí)行rewriteoc時應該會報錯UIKit.h file not found, 處理方法:

1.進入終端艇挨,鍵入命令vim ~/.bash_profile韭赘;

2.在vim界面輸入i進入編輯編輯狀態(tài)并且鍵入:alias rewriteoc='clang -x objective-c -rewrite-objc -isysroot/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk'泉瞻;

3.鍵入完畢,點esc退出編輯狀態(tài)袖牙,再鍵入:wq退出vim并保存,再執(zhí)行source ~/.bash_profile司忱;

4.再執(zhí)行rewriteoc xxx.m即可成功畴蹭;

解決辦法參見文章( http://blog.csdn.net/majiakun1/article/details/52842010)解決



2.獲取類的屬性和方法列表

例:自定義類Person

Person.h文件

@interface Person : NSObject

@property (nonatomic, strong)NSString *name;

@property (nonatomic, assign) int age;

- (void)sayHello;

@end


Person.m文件

#import "Person.h"

@interface Person ()

//私有屬性

@property (nonatomic, strong)NSString *address; //地址

@end

@implementation Person

- (instancetype)init{

if (self = [super init]) {

? ? ? _address = @"北戴河";

? ? ? ? self.name = @"Beijing";

? ? ? ? self.age = 5000;

}

return self;

}

- (NSString *)description {

? ? ? ? return [NSString stringWithFormat:@"address:%@, age:%f, name: %@", self.address,self.age ,self.name];

}

- (void)sayHello{

? ? ? ? NSLog(@"-----說:hello--%@", self.address);

}

//內部私有方法

- (void)interface{

? ? ? ? ? NSLog(@"我是: %@", self.name);

}

@end


ViewController.m中操作如下:

1).獲取成員變量(包括私有)

Person *p = [[Person alloc]init];

NSLog(@"--更改前:%@--",[oneperson description]);

unsigned int count = 0;

//這是個指針,地址為所有成員變量的數(shù)組集合桨踪,函數(shù)第二個參數(shù)傳遞的是count的地址芹啥,函數(shù)執(zhí)行完畢后會將成員變量個數(shù)存到改地址下

Ivar *members = class_copyIvarList([Person class], &count);

for (int i = 0; i < count; i++) {

? ? //循環(huán)獲取每一個成員變量

? ? ? Ivar var = members[i];

? ? ? //獲取對象的成員變量名稱

? ? ? const char *memberName = ivar_getName(var);

? ? ? //獲取對象的成員變量類型

? ? ? const char *memberType = ivar_getTypeEncoding(var);

? ? ? NSLog(@"--members: %p; memberName: %s; type: %s",members, memberName, memberType);

}

日志輸入:

發(fā)現(xiàn)在Person.m中聲明的屬性address也拿到了墓怀。

2).修改屬性值

Ivar address = members[2];

object_setIvar(p1, address, @"圓明園");

NSLog(@"--更改后:%@--",[oneperson description]);

日志輸入:

可以看到對象p1初始化地址北戴河被修改為了圓明園

3).獲取方法(包括私有)

unsigned int count = 0;

Method *memberFuncs = class_copyMethodList([Person class], &count);

for (int i = 0; i < count; i++) {

? ? //獲取對象的方法名

? ? SEL address= method_getName(memberFuncs[i]);

? ? NSString *methodName = [NSString stringWithCString:sel_getName(address)? ? encoding:NSUTF8StringEncoding];

? ? //獲取對象方法的返回值

? ? char *backParamType =? method_copyReturnType(memberFuncs[i]);

? ? NSLog(@"對象的方法有:---%d----%@----%s",i, methodName, backParamType);

}

日志輸入:



3.方法交換(方法欺騙)

Objective-C 提供了一下API用于動態(tài)替換類方法或者實例方法的實現(xiàn):

class_replaceMethod 替換類方法的定義傀履;

method_exchangeImplementations 交換兩個方法的實現(xiàn)(具體使用案例如下);

method_setImplementation 設置一個方法的實現(xiàn)钓账;

注:class_replaceMethod 試圖替換一個不存在的方法時候,會調用class_addMethod為該類增加一個新方法

示例:開發(fā)中經(jīng)常運到數(shù)組數(shù)據(jù)個數(shù)不確定的情況服协,一旦數(shù)組越界就會出現(xiàn)崩潰啦粹,對此問題可以采取給系統(tǒng)的NSArray原生的方法- (ObjectType)objectAtIndex:(NSUInteger)index替換掉;

#import "NSArray+LXZArray.h"

/運行時相關的文件

#import <objc/runtime.h>

@implementation NSArray (LXZArray)

//load方法在此文件加載進內存后就會調用

+ (void)load{

? ? [super load];

? ? //獲取系統(tǒng)自動的方法

? ? Method fromeMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"),? ? @selector(objectAtIndex:));

? ? ? //獲取用來替換的方法

? ? ? Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(lxz_objectAtIndex:));

? ? ? //方法交換

? ? ? method_exchangeImplementations(fromeMethod, toMethod);

}

- (id)lxz_objectAtIndex:(NSInteger)index {

? ? ? if (self.count - 1 < index) {

? ? ? ? ? ? @try {

? ? ? ? ? ? ? ? ? return [self lxz_objectAtIndex:index];

? ? ? ? ? } @catch (NSException *exception) {

? ? ? ? ? // 在崩潰后會打印崩潰信息跳纳,方便我們調試贪嫂。

? ? ? ? ? NSLog(@"---崩潰------- %s Crash Because Method %s? ----------\n", class_getName(self.class),? __func__);

? ? ? ? ? NSLog(@"---崩潰---%@", [exception callStackSymbols]);

? ? ? ? ? return nil;

? ? ? ? } @finally {}

? }else {

? ? ? ? ? return [self lxz_objectAtIndex:index];}

}

疑問

class_getInstanceMethod(objc_getClass("__NSArrayI"),? ? @selector(objectAtIndex:))
為什么寫objc_getClass("__NSArrayI")而不是objc_getClass("NSArray")?

解答

NSArray是基于C底層CFArray/CFArrayRef實現(xiàn)的,NSArray 可以看做是一個 CFArrayRef 的 Wrapper類.

NSArray的真正類型是__NSArrayI(Immutable)

NSMutableArray的真正類型是__NSArrayM(_internal)

這里涉及到類簇概念《诽粒可以參考【iOS】類簇(class cluster) http://www.reibang.com/p/c60d9ffcde4b



未完待續(xù)餐曹。。台猴。


參考:

http://www.reibang.com/p/99af00237cb8;

http://www.cocoachina.com/ios/20150901/13173.html曹步;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末休讳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子筹麸,更是在濱河造成了極大的恐慌雏婶,老刑警劉巖物赶,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件留晚,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機橄唬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門参歹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缸血,你說我怎么就攤上這事∩有海” “怎么了埋哟?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長闯狱。 經(jīng)常有香客問我抛计,道長,這世上最難降的妖魔是什么吹截? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮晨逝,結果婚禮上懦铺,老公的妹妹穿的比我還像新娘。我一直安慰自己冬念,他們只是感情好,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布醒陆。 她就那樣靜靜地躺著叔汁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪据块。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天像屋,我揣著相機與錄音边篮,去河邊找鬼。 笑死凌受,一個胖子當著我的面吹牛思杯,可吹牛的內容都是我干的胜蛉。 我是一名探鬼主播色乾,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼暖璧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了澎办?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤悍汛,失蹤者是張志新(化名)和其女友劉穎至会,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奉件,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡县貌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了煤痕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片接谨。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡脓豪,死狀恐怖,靈堂內的尸體忽然破棺而出扫夜,到底是詐尸還是另有隱情驰徊,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站飘言,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鹃锈。 院中可真熱鬧,春花似錦屎债、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至廉丽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間欣福,已是汗流浹背焦履。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工雏逾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留郑临,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓牧抵,卻偏偏與公主長得像侨把,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子获枝,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內容

  • 轉至元數(shù)據(jù)結尾創(chuàng)建: 董瀟偉骇笔,最新修改于: 十二月 23, 2016 轉至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,686評論 0 9
  • 前言 runtime其實在我們日常開發(fā)過程中很少使用到笨触,尤其是像我現(xiàn)在比較初級的程序猿就更用不到了懦傍。但是去面試很多...
    WolfTin閱讀 616評論 0 2
  • 轉載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 729評論 0 2
  • 對于從事 iOS 開發(fā)人員來說粗俱,所有的人都會答出【runtime 是運行時】什么情況下用runtime?大部分人能...
    夢夜繁星閱讀 3,700評論 7 64
  • jarsigner -digestalg SHA1 -sigalg MD5withRSA -keystore an...
    HaRun閱讀 709評論 0 0