歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

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;
        privateTow=@"Tow";
        privateThree=@"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;
        privateTow=@"Tow";
        privateThree=@"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 performSelector:@selector(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中@property的所有屬性詳解 http://www.linuxidc.com/Linux/2014-03/97744.htm

Objective-C 和 Core Foundation 對象相互轉換的內存管理總結 http://www.linuxidc.com/Linux/2014-03/97626.htm

使用 Objective-C 一年後我對它的看法 http://www.linuxidc.com/Linux/2013-12/94309.htm

10個Objective-C基礎面試題,iOS面試必備 http://www.linuxidc.com/Linux/2013-07/87393.htm

Objective-C適用C數學函數 <math.h> http://www.linuxidc.com/Linux/2013-06/86215.htm

好學的 Objective-C 高清PDF http://www.linuxidc.com/Linux/2014-09/106226.htm

Copyright © Linux教程網 All Rights Reserved