iOS --runtime理解與應用

1.什么是Runtime?

我所理解的runtime是一個使用C編寫的庫,為C添加了面向對象的特性,它是一個庫(Runtime Library中文:運行時庫).在這個庫中可以用C函數(shù)來實現(xiàn)方法,對象也可以用C語言的結構體來表示…所有oc的方法的背后都是通過runtime來運行的.

2.runtime的使用

(1)利用runtime的消息發(fā)送機制調用方法

首先新建一個類RuntimeModel,并實現(xiàn)對象方法eat,在RuntimeViewController中調用eat方法逊谋,使用oc來語言來實現(xiàn)很簡單了

RuntimeModel * model=[[RuntimeModel alloc]init];
[model eat];

接下來一點點用runtime實現(xiàn)上面的代碼,導入runtime的頭文件#import <objc/message.h>龙填,由于xcode5.0開始蘋果不建議我們使用底層的代碼捡鱼,所以target->build setting->搜索msg->將YES改為NO,這樣接下來我們用runtime的時候才會出現(xiàn)提示毫深。
我們使用objc_msgSend(<#id self#>, <#SEL op, ...#>)這個方法吩坝,可以看到需要兩個參數(shù),第一個參數(shù)是id類型哑蔫,代表誰要發(fā)送消息钉寝,第二個參數(shù)是要把消息發(fā)送給誰,我們用runtime來實現(xiàn)[model eat];這個方法闸迷。

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

而初始化對象同樣是調用了alloc init這兩個方法嵌纲。將導入的頭文件RuntimeModel去掉,用純c語言的代碼實現(xiàn)上面的功能腥沽。

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

運行逮走,我們可以看到在eat方法中的nslog被調用了,雖然我們實現(xiàn)了功能今阳,但是怎么才能知道我們的oc語言在運行時確實是被轉換成了c語言的代碼呢师溅?
新建工程,創(chuàng)建命令行工具盾舌。

4BEFBE2C-70AE-490F-BE08-926725DC0C2B.png

新建一個person類险胰,然后進入main.m中。
調用頭文件矿筝,在main中實例化person。person * p=[[person alloc]init];關閉項目棚贾,打開終端窖维,進入剛才建的文件夾下榆综,ls打開可以看到剛才我們新建的類和main.m文件,接下來執(zhí)行命令行clang -rewrite-objc main.m
這時我們可以看到铸史,在剛才的工程中出現(xiàn)一個main.cpp的文件鼻疮,打開并且拖到最下面。

783A655E-6F04-41F8-8E16-DAE6C21468E2.png
teatime.gif

(2)交換方法

做為oc的程序員最悲慘的就是琳轿,運行--崩潰在main里面判沟,我尼瑪!U复邸挪哄! 例如:

NSURL * url=[NSURL URLWithString:@"www.baidu.com.啦啦"];

當我們沒有進行編碼的時候,這個url在編譯的時候是有效的琉闪,但是一旦運行起來迹炼,這個url就會變?yōu)閚il。因為檢測不到這是一個無效的url颠毙,會繼續(xù)發(fā)送網(wǎng)絡請求斯入。
怎么辦呢?在下面用if做一個判斷蛀蜜?可是要在每一個url下面都做判斷刻两。這時候最先想到的一定是重寫。
創(chuàng)建一個url的分類滴某,重寫URLWithString:<#(nonnull NSString *)#>但是磅摹,我們看:

+(instancetype)URLWithString:(NSString *)URLString
{
    //  首先創(chuàng)建一個URL
    NSURL * url= ????????
    if (url==nil) {
        NSLog(@"有問題");
    }
    return url;
}

我們該怎么創(chuàng)建呢,死循環(huán)了是不是壮池,所以runtime就起作用了偏瓤。
在分類中自定義一個方法

+(instancetype)TY_URLWithStr:(NSString *)Str;
{
    NSURL * url =[NSURL URLWithString:Str];
    if (url == nil) {
        NSLog(@"是空!R铩L恕!");
    }
    return url;
}

但是我們并不是要每一次創(chuàng)建url都調用這個方法橙依,因為程序里有很多創(chuàng)建url的地方证舟,我們還繼續(xù)使用系統(tǒng)自帶的方法。在分類中實現(xiàn)load方法窗骑,當程序加載這個類的時候最先調用這個方法女责。導入頭文件,開始進行方法交換创译。

+(void)load
{
    //  Method : 成員方法
    //class_getClassMethod   拿到類方法
    //class_getInstanceMethod   拿到對象方法
    Method URLWithStr = class_getClassMethod([NSURL class], @selector(URLWithString:));
    Method TYURLWithStr = class_getClassMethod([NSURL class], @selector(TY_URLWithStr:));
    //  開始交換方法
    method_exchangeImplementations(URLWithStr, TYURLWithStr);
}

記得將上面我們自定義的方法中創(chuàng)建url的方法改變回去抵知,不然再次死循環(huán),改為

+(instancetype)TY_URLWithStr:(NSString *)Str;
{
    NSURL * url =[NSURL TY_URLWithStr:Str];
    if (url == nil) {
        NSLog(@"是空!K⑾病2兄啤!");
    }
    return url;
}

是不是有種一葉落而知天下秋的感覺掖疮。

teatime.gif

(3)遍歷屬性列表簡化序列化

oc的序列化在這里就不多說了初茶,讓我們來說一種常見的情況,當需要歸檔的屬性過多時浊闪,我們需要一條條的寫出來恼布,十分繁瑣,有沒有可能簡化一些呢搁宾,如果單純的用for循環(huán)去做折汞,那么不同的類型該怎么處理呢,這時候我們的runtime又來了猛铅。首先創(chuàng)建一個Person類字支,多弄一些虛擬屬性。

//  .h
@property(nonatomic,strong) NSString * name;
@property(nonatomic,strong) NSString * name1;
@property(nonatomic,assign) int age;
@property(nonatomic,assign) int age1;
@property(nonatomic,assign) double age2;

// .m
-(void)encodeWithCoder:(NSCoder *)Coder
{
    
}

-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self=[super init];
    if (self) {
        
    }
    return self;
}

實現(xiàn)思路:

-(void)encodeWithCoder:(NSCoder *)Coder
{
    for (int i = 0; i < 屬性數(shù)量; i++) {
    [Coder encodeObject:屬性值 forKey:屬性名稱];
    }
}

那么 回到controller中奸忽,導入runtime頭文件堕伪,使用一個方法class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)獲取屬性列表,第一個參數(shù)栗菜,傳入一個類[Person class]欠雌,第二個參數(shù),傳入一個指針疙筹,在上面定義unsigned int count = 0;富俄,然后傳入&count。這個count就是獲取的屬性的數(shù)量而咆。同時在c語言中是不分.h.m的霍比,所以無論是在哪個文件中定義的屬性,都可以取到暴备。

unsigned int count = 0;
class_copyIvarList([Person class], &count);

然后我們需要定義一個Ivar類型的指針悠瞬,這個指針會指向每一個屬性,下面這個圖說明一下涯捻,他并不是同時指向每一個屬性浅妆,而是一個一個分別指向來獲取屬性。

DCF79208-E155-4D1B-8797-DDFC8C6F83B5.png

我們使用一個方法通過這個ivars去獲取屬性名稱障癌。

unsigned int count = 0;
    Ivar * ivars = class_copyIvarList([Person class], &count);
    Ivar ivar = ivars[0];
    const char * name = ivar_getName(ivar);
    NSLog(@"%s",name);

打印看到第一個屬性名凌外,可以改變ivars[第幾個],去獲取第幾個涛浙。而且即使角標越界康辑,依然不會崩潰摄欲。
那么我們回到person類中,直接用剛才的代碼實現(xiàn)我們最開始提出的問題疮薇。

//  歸檔
-(void)encodeWithCoder:(NSCoder *)Coder
{
    unsigned int count = 0;
    Ivar * ivars = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar= ivars[i];
        const char * name = ivar_getName(ivar);
        NSString * key = [[NSString alloc]initWithUTF8String:name];
        //  使用KVC  拿出屬性的值
        [Coder encodeObject:[self valueForKey:key] forKey:key];
    }
}
//  解檔
-(instancetype)initWithCoder:(NSCoder *)Decoder
{
    self=[super init];
    if (self) {
        unsigned int count = 0;
        Ivar * ivars = class_copyIvarList([Person class], &count);
        for (int i = 0; i < count; i++) {
            Ivar ivar= ivars[i];
            const char * name = ivar_getName(ivar);
            NSString * key = [[NSString alloc]initWithUTF8String:name];
            //  使用KVC  拿出屬性的值
            id value = [Decoder decodeObjectForKey:key];
            //  設置屬性
            [self setValue:value forKey:key];
        }

    }
    return self;
}

通過上面的講述蒿涎,這段代碼就很容易理解了。我們用的是kvc的賦值和取值惦辛,所以任何類型的歸檔解檔都是沒有問題的。

teatime.gif

(4)刨析KVO底層實現(xiàn)

我們先用oc實現(xiàn)一個簡單的KVO監(jiān)聽仓手。

//   controller.m
self.c=[[Cat alloc]init];
self.d=[[Dog alloc]init];
    //  注冊監(jiān)聽
[self.d addObserver:self.c forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

//   cat.m
//  監(jiān)聽到了object的對象keyPath屬性變化為樂change
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"監(jiān)聽到了%@的對象%@屬性變化為樂%@",object,keyPath,change);
}

這是KVO最簡單的應用胖齐,那么接下來我們看一下KVO底層到底是怎么實現(xiàn)的呢?
首先在KVO運行的時候會動態(tài)的添加一個類嗽冒,繼承與被觀察者的類呀伙。名字叫做NSKVONotifying_Dog這個類。類名可不是瞎編的哦添坊。然后在.m文件中剿另,調用父類的set方法:

-(void)setAge:(int)age
{
    [super setAge:age];
    // 在子類中調用這兩個方法
    //  這個是 將要被改變的值是什么
    [self willChangeValueForKey:@"age"];
    //   這個是   改變之后的新值是什么
    [self didChangeValueForKey:@"age"];
}

這樣就會監(jiān)聽到改變并且傳值,但是為什么說KVO是這樣實現(xiàn)的呢贬蛙?
將剛才新建的NSKVONotifying_Dog類刪掉雨女,在controller中實現(xiàn)點擊改變值的方法

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.d.age=99;
}

在賦值的地方打斷點,如果程序運行到這里阳准,d的類型變?yōu)閯偛拍莻€方法的類型氛堕,那么就說明KVO就是這樣實現(xiàn)的。


B528C61F-E2C4-46BD-9F23-2A06A67867BA.png
teatime.gif

(5)動態(tài)添加方法

首先創(chuàng)建一個Person類野蝇,然后在controller中實例化person直接可以這樣直接調用一個不存在的方法

    Person * p=[[Person alloc]init];
    [p performSelector:@selector(eat)];

這樣雖然編譯可以過讼稚,但是運行起來就會崩潰
這時候我沒回到person.m中,來看兩個方法

//  當這個類被調用沒有實現(xiàn)的類方法  就會來到這里
+(BOOL)resolveClassMethod:(SEL)sel
{
    return [super resolveClassMethod:sel];
}
//  當這個類被調用沒有實現(xiàn)的對象方法  就會來到這里
+(BOOL)resolveInstanceMethod:(SEL)sel
{
    return [super resolveInstanceMethod:sel];
}

方法中的參數(shù)就是被調用的方法名绕沈,然后我們需要實現(xiàn)一個名為eat的函數(shù)

void eat(){
    NSLog(@"lalal");
}

這時候锐想,我們將要用到一個方法class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)第一個參數(shù):類類型,第二個參數(shù):方法編號乍狐,第三個參數(shù):方法實現(xiàn)(函數(shù)指針)赠摇,第四個參數(shù):返回值類型。關于這第四個參數(shù)澜躺,這是c語音蝉稳,我們該怎么寫返回類型呢?去查一下官方文檔關于第四個參數(shù)的描述掘鄙。

047E0D3E-7A83-4E78-8EC4-CE7880A0B008.png

那么我們來實現(xiàn)代碼

+(BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(eat)) {
        class_addMethod([Person class], sel, (IMP)eat, "v");
    }
    return [super resolveClassMethod:sel];
    return [super resolveInstanceMethod:sel];
}

void eat(){
    NSLog(@"lalal");
}

這樣就完成了一個動態(tài)添加方法耘戚,然后我們接著看文檔,文檔中有一段代碼示例

BB20A099-E697-4F05-9580-0ECBB4BFD995.png

我們可以看到操漠,當動態(tài)添加方法是會傳入兩個參數(shù)收津,實際上每一個函數(shù)被調用時都會傳入這兩個參數(shù)饿这,叫做隱式參數(shù)。參數(shù)一:調用了哪個類的撞秋,參數(shù)二:調用了哪個方法长捧,我們用nslog打印一下這兩個參數(shù),在這之前需要改一下上面的第四個參數(shù)為"v@:"吻贿,因為我們返回值類型改變了串结。打印一下:

43DBB6BD-18C3-4D6B-9C7B-C46D0F3B235C.png

接下來,就是如何傳遞參數(shù)舅列,我們在調用方法的時候傳入一個參數(shù)

 Person * p=[[Person alloc]init];
    [p performSelector:@selector(eat:) withObject:@"6666"];

回去Person類肌割,將判斷的方法名改為eat:,并將eat函數(shù)增加一個參數(shù):

+(BOOL)resolveInstanceMethod:(SEL)sel
{
    //  方法名的判斷
    if (sel == @selector(eat:)) {
        class_addMethod([Person class], sel, (IMP)eat, "v@:");
    }
    return [super resolveClassMethod:sel];
    return [super resolveInstanceMethod:sel];
}

void eat(id self, SEL _cmd ,id obj){
    NSLog(@"%@ ",obj);
}

控制臺:

76FA3A6D-BF28-48B2-A3A4-B8C3BAFAD621.png

參數(shù)完美傳遞過來帐要。

teatime.gif
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末把敞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子榨惠,更是在濱河造成了極大的恐慌奋早,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赠橙,死亡現(xiàn)場離奇詭異耽装,居然都是意外死亡,警方通過查閱死者的電腦和手機简烤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門剂邮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人横侦,你說我怎么就攤上這事挥萌。” “怎么了枉侧?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵引瀑,是天一觀的道長。 經常有香客問我榨馁,道長憨栽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任翼虫,我火速辦了婚禮屑柔,結果婚禮上,老公的妹妹穿的比我還像新娘珍剑。我一直安慰自己掸宛,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布招拙。 她就那樣靜靜地躺著唧瘾,像睡著了一般措译。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饰序,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天领虹,我揣著相機與錄音,去河邊找鬼求豫。 笑死塌衰,一個胖子當著我的面吹牛,可吹牛的內容都是我干的蝠嘉。 我是一名探鬼主播猾蒂,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼是晨!你這毒婦竟也來了?” 一聲冷哼從身側響起舔箭,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤罩缴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后层扶,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體箫章,經...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年镜会,在試婚紗的時候發(fā)現(xiàn)自己被綠了檬寂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡戳表,死狀恐怖桶至,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情匾旭,我是刑警寧澤镣屹,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站价涝,受9級特大地震影響女蜈,放射性物質發(fā)生泄漏。R本人自食惡果不足惜色瘩,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一伪窖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧居兆,春花似錦覆山、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽佩伤。三九已至,卻和暖如春晦毙,著一層夾襖步出監(jiān)牢的瞬間生巡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工见妒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留孤荣,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓须揣,卻偏偏與公主長得像盐股,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子耻卡,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容

  • 對于從事 iOS 開發(fā)人員來說疯汁,所有的人都會答出【runtime 是運行時】什么情況下用runtime?大部分人能...
    夢夜繁星閱讀 3,697評論 7 64
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,125評論 29 470
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,131評論 0 9
  • 哈,剛才敲長大時多敲了一個 n 出來了 賬單 長大后卵酪,學會的最深遠的一種方法 就是:趨利避害 是件好事 但是幌蚊,也就...
    AmNobody閱讀 170評論 1 0
  • 由河正宇執(zhí)導并與河智苑主演的喜劇片《許三觀》于1月14日在韓國上映,當天就吸引了7.3626萬觀影人次溃卡。這部電影改...
    耳東陳閱讀 3,559評論 4 29