Objective-C中runtime機制的應用

來源:互聯網
上載者:User

標籤:

Objective-C中runtime機制的應用 一、初識runtime

        Objective-C是一種動態語言,所謂動態語言,是在程式執行時動態確定變數類型,執行變數類型對應的方法的。因此,在Object-C中常用字串映射類的技巧來動態建立類對象。因為OC的動態語言特性,我們可以通過一些手段,在程式運行時動態更改對象的變數甚至方法,這就是我們所說的runtime機制。

二、你還有什麼辦法操作這樣的變數嗎?

        首先,我們先來看一個例子,這裡有我建立的一個MyObject類:

//.h===========================@interface MyObject : NSObject{    @private    int privateOne;    NSString * privateTow;;}@end//=============================//.m===========================@interface MyObject(){    @private    NSString * privateThree;}@end@implementation MyObject- (instancetype)init{    self = [super init];    if (self) {        privateOne=1;        [email protected]"Tow";        [email protected]"Three";    }    return self;}-(NSString *)description{    return [NSString stringWithFormat:@"one=%d\ntow=%@\nthree=%@\n",privateOne,privateTow,privateThree];}@end//=============================

這個類是相當的安全,首先,在標頭檔中沒有提供任何的方法介面,我們沒有辦法使用點文法做任何操作,privateOne和PrivateTow兩個變數雖然聲明在了標頭檔中,卻是私人類型的,通過指標的方式我們雖然可以看到他們,卻不能做任何讀取修改的操作,xcode中的提示如下:

他會告訴我們,這是一個私人的變數,我們不能使用。對於privateThree,我們更是束手無策,不僅不能使用,我們甚至都看不到它的存在。那麼對於這種情況,你有什麼辦法操作這些變數嗎?對,是時候展現真正的技術了:runtime!

三、通過runtime擷取對象的變數列表

        要操作對象的變數,我們首先應該要捕獲這些變數,讓他們無處遁形。無論聲明在標頭檔或是實現檔案,無論類型是公開的還是私人的,只要聲明了這個變數,系統就會為其分配空間,我們就可以通過runtime機制捕獲到它,代碼如下:

#import "ViewController.h"#import "MyObject.h"//包含runtime標頭檔#import <objc/runtime.h>@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    //我們先聲明一個unsigned int型的指標,並為其分配記憶體    unsigned int * count = malloc(sizeof(unsigned int));    //調用runtime的方法    //Ivar:方法返回的對象內容對象,這裡將返回一個Ivar類型的指標    //class_copyIvarList方法可以捕獲到類的所有變數,將變數的數量存在一個unsigned int的指標中    Ivar * mem = class_copyIvarList([MyObject class], count);    //進行遍曆    for (int i=0; i< *count ; i++) {        //通過移動指標進行遍曆        Ivar var = * (mem+i);        //擷取變數的名稱        const char * name = ivar_getName(var);        //擷取變數的類型        const char * type = ivar_getTypeEncoding(var);        NSLog(@"%s:%s\n",name,type);    }    //釋放記憶體    free(count);    //注意處理野指標    count=nil;}- (void)didReceiveMemoryWarning {    [super didReceiveMemoryWarning];    // Dispose of any resources that can be recreated.}@end

列印結果如下,其中i表示int型:


是不是小吃驚了一下,無論變數在哪裡,只要它在,就讓它無處遁形。

四、讓我找到你,就讓我改變你!

        僅僅能夠獲得變數的類型和名字或許並沒有什麼卵用,沒錯,我們擷取變數的目的不是為了觀賞,而是為了操作它,這對runtime來說,也是小事一碟。代碼如下:

- (void)viewDidLoad {    [super viewDidLoad];    //擷取變數    unsigned int  count;    Ivar * mem = class_copyIvarList([MyObject class],&count);    //建立對象    MyObject * obj = [[MyObject alloc]init];    NSLog(@"before runtime operate:%@",obj);    //進行變數的設定    object_setIvar(obj, mem[0],10);    object_setIvar(obj, mem[1], @"isTow");    object_setIvar(obj, mem[2], @"isThree");    NSLog(@"after runtime operate:%@",obj);    }

Tip:在修改int型變數的時候,你或許會遇到一個問題,ARC下,編譯器不允許你將int類型的值賦值給id,在buildset中將Objective-C Automatic Reference Counting修改為No即可。

列印效果如下:


可以看到,那些看似非常安全的變數被我們修改了。

五、讓我看看你的方法吧

        變數通過runtime機制我們可以取到和改變值,那麼我們再大膽一點,試試那些私人的方法,首先我們在MyObject類中添加一些方法,我們只實現,並不聲明他們:

@interface MyObject(){    @private    NSString * privateThree;}@end@implementation MyObject- (instancetype)init{    self = [super init];    if (self) {        privateOne=1;        [email protected]"Tow";        [email protected]"Three";    }    return self;}-(NSString *)description{    return [NSString stringWithFormat:@"one=%d\ntow=%@\nthree=%@\n",privateOne,privateTow,privateThree];}-(NSString *)method1{    return @"method1";}-(NSString *)method2{    return @"method2";}

這樣的方法我們在外面是無法調用他們的,和操作變數的思路一樣,我們先要捕獲這些方法:

    //擷取所有成員方法    Method * mem = class_copyMethodList([MyObject class], &count);    //遍曆    for(int i=0;i<count;i++){        SEL name = method_getName(mem[i]);        NSString * method = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];        NSLog(@"%@\n",method);    }

列印如下:


得到了這些方法名,我們大膽的調用即可:

    MyObject * obj = [[MyObject alloc]init];    NSLog(@"%@",[obj method1]);

Tip:這裡編譯器不會給我們方法提示,放心大膽的調用即可。

六、動態為類添加方法

        這個runtime機制最強大的部分要到了,試想,如果我們可以動態向類中添加方法,那將是一件多麼令人激動的事情,注意,這裡是動態添加,和類別的最大不同在於這種方式是運行時才決定是否添加方法的。

- (void)viewDidLoad {    [super viewDidLoad];    //添加一個新的方法,第三個參數是傳回值的類型v是void,i是int,:是SEL,對象是@等    class_addMethod([MyObject class], @selector(method3), (IMP)logHAHA, "v");        unsigned int count = 0;    Method * mem = class_copyMethodList([MyObject class], &count);    for(int i=0;i<count;i++){        SEL name = method_getName(mem[i]);        NSString * method = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];        NSLog(@"%@\n",method);    }        MyObject * obj = [[MyObject alloc]init];    //運行這個方法    [obj method3];    }//方法的實現void logHAHA(){    NSLog(@"HAHA");}

運行結果如下:


從前五行可以看出,方法已經加進去了,從最後一行可以看出,執行沒有問題。

七、做點小手腳

        程式員總是得寸進尺的,現在,我們要做點事情,用我們的函數替換掉類中的函數:

- (void)viewDidLoad {    [super viewDidLoad];    MyObject * obj = [[MyObject alloc]init];    //替換之前的方法    NSLog(@"%@", [obj method1]);    //替換    class_replaceMethod([MyObject class], @selector(method1), (IMP)logHAHA, "v");    [obj method1];    }void logHAHA(){    NSLog(@"HAHA");}

列印如下:


這次夠cool吧,通過這個方法,我們可以把系統的函數都搞亂套。當然,runtime還有許多很cool的方法:

id object_copy(id obj, size_t size)

拷貝一個對象


id object_dispose(id obj)

釋放一個對象


const char *object_getClassName(id obj)

擷取對象的類名

ive

void method_exchangeImplementations(Method m1, Method m2)
交換兩個方法的實現

Objective-C中runtime機制的應用

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.