iOS動態性(二):運行時runtime初探(強制獲取並修改私有變量,強制增加及修改私有方法等)

轉自:http://www.cnblogs.com/wengzilin/p/4344952.html?utm_source=tuicool


OC是運行時語言,只有在程序運行時,纔會去確定對象的類型,並調用類與對象相應的方法。利用runtime機制讓我們可以在程序運行時動態修改類、對象中的所有屬性、方法,就算是私有方法以及私有屬性都是可以動態修改的。本文旨在對runtime的部分特性小試牛刀,更多更全的方法可以參考系統API文件<objc/runtime.h>,demo例子可以參見CSDN的runtime高級編程系列文章

我們出發吧!

先看一個非常平常的Father類:

 

複製代碼
#import <Foundation/Foundation.h>

@interface Father : NSObject
@property (nonatomic, assign) int age;
@end
複製代碼

 

複製代碼
#import "Father.h"

@interface Father ()
{
  NSString *_name;
}

- (void)sayHello;

@end

@implementation Father

- (id)init
{
    if (self = [super init]) {
        _name = @"wengzilin";
        [_name copy];
        self.age = 27;
    }
    return self;
}
- (void)dealloc
{
    [_name release];
    _name = nil;
    [super dealloc];
}
- (NSString *)description
{
    return [NSString stringWithFormat:@"name:%@, age:%d", _name, self.age];
}
- (void)sayHello
{
    NSLog(@"%@ says hello to you!", _name);
}
- (void)sayGoodbay
{
    NSLog(@"%@ says goodbya to you!", _name);
}
複製代碼

 

 

 

如果你沒接觸過runtime,那當我問你:“Father之外的類能控制的屬性有哪些?能控制的方法有哪些?”時,你估計會回答:“我們可以訪問age屬性,不能訪問_name變量;可以訪問age的setter/getter方法,其他方法都不行”。這種回答是OK的,因爲教科書上以及面向對象的思想告訴我們,事實如此。但是,我會說,有一種方法是APPLE允許的而且可以不受這些規則限制的途徑可以做到想訪問什麼就訪問什麼、想修改什麼就修改什麼,那就是本文的主題:RUNTIME!

現在我們簡單地將本文的主題分爲兩部分:(1)控制私有變量  (2)控制私有函數,因爲二者所用的runtime差異較大,函數部分會複雜一些

(1)控制變量

想要控制一個類的私有變量,那第一步就要知道這個類到底有哪些隱藏的變量,以及這些隱藏的變量類型是什麼。或許你會說:“這不是很顯然嗎?.h文件都寫着呢!”。如果你真這麼想就特錯特錯了,很多正規的寫法都是儘量避免在.h文件中出現私有變量,絕大部分都會選擇方法.m文件的extension中,extension就是匿名的category。我猜測這也是一種防止hack的措施吧。不管這些變量放在何處,runtime都可以讓他們無所遁形!先看代碼,看不懂不要緊,後面會有解釋:

 

複製代碼
- (void)tryMember
{
    Father *father = [[Father alloc] init];
    NSLog(@"before runtime:%@", [father description]);
    
    unsigned int count = 0;
    Ivar *members = class_copyIvarList([Father class], &count);
    for (int i = 0 ; i < count; i++) {
        Ivar var = members[i];
        const char *memberName = ivar_getName(var);
        const char *memberType = ivar_getTypeEncoding(var);
        NSLog(@"%s----%s", memberName, memberType);
    }
}
複製代碼

 

 

 

 顯示如下:

 

複製代碼
2015-03-17 16:10:28.003 WZLCodeLibrary[38574:3149577] before runtime:name:wengzilin, age:27
2015-03-17 16:10:28.003 WZLCodeLibrary[38574:3149577] _name----@"NSString"
2015-03-17 16:10:28.003 WZLCodeLibrary[38574:3149577] _age----i
複製代碼

 

從log中我們知道了,Father類有兩個變量,一個公開的包裝成屬性的age, 類型是int,一個花括號{}內的私有變量_name,類型是NSString。代碼中標紅色的部分就是runtime.h的api,

class_copyIvarList:獲取類的所有屬性變量,count記錄變量的數量IVar是runtime聲明的一個宏,是實例變量的意思,instance variable,在runtime中定義爲 typedef struct objc_ivar *Ivari

var_getName:將IVar變量轉化爲字符串

ivar_getTypeEncoding:獲取IVar的類型

如果我們現在想對_name動手,不經過Father同意偷偷修改它呢?我們繼續往下做:(接着上面的代碼)

 

    Ivar m_name = members[0];
    object_setIvar(father, m_name, @"zhanfen");
    NSLog(@"after runtime:%@", [father description]);

 

 顯示如下:

 

2015-03-17 16:10:28.004 WZLCodeLibrary[38574:3149577] after runtime:name:zhanfen, age:27

 

 

 

我們發現,_name屬性被強制改過來了,有wengzilin改爲現在zhanfen。

(2)控制私有函數

對於私有變量,我們能做的頂多修改變量的值,但對於私有函數,我們可以玩非常多的花樣,比如:在運行時動態添加新的函數、修改私有函數、交換其中兩個私有函數的實現、替換私有函數...

同樣地,控制的第一步是獲得Father類的所有私有方法,我們可以得到.m文件中所有有顯式實現的方法以及屬性變量的setter+getter方法都會被找到:

 

複製代碼
- (void)tryMemberFunc
{
    unsigned int count = 0;
    Method *memberFuncs = class_copyMethodList([Father class], &count);//所有在.m文件顯式實現的方法都會被找到
    for (int i = 0; i < count; i++) {
        SEL name = method_getName(memberFuncs[i]);
        NSString *methodName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
        NSLog(@"member method:%@", methodName);
    }
}
複製代碼

 

 顯示如下:

 

複製代碼
2015-03-17 17:02:33.343 WZLCodeLibrary[38748:3170794] member method:setAge:
2015-03-17 17:02:33.343 WZLCodeLibrary[38748:3170794] member method:age
2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:sayHello
2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:sayGoodbay
2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:description
2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:dealloc
2015-03-17 17:02:33.344 WZLCodeLibrary[38748:3170794] member method:init
複製代碼

 

Method:runtime聲明的一個宏,表示一個方法,typedef struct objc_method *Method;

class_copyMethodList:獲取所有方法

method_getName:讀取一個Method類型的變量,輸出我們在上層中很熟悉的SEL

=========

接下來我們試着添加新的方法試試(這種方法等價於對Father類添加Category對方法進行擴展):

複製代碼
- (void)tryAddingFunction
{
    class_addMethod([Father class], @selector(method::), (IMP)myAddingFunction, "i@:i@");
    
}
//具體的實現,即IMP所指向的方法
int myAddingFunction(id self, SEL _cmd, int var1, NSString *str)
{
    NSLog(@"I am added funciton");
    return 10;
}
複製代碼

 

複製代碼
- (void)tryMemberFunc
{
    //動態添加方法
    [self tryAddingFunction];
    count = 0;
    memberFuncs = class_copyMethodList([Father class], &count);//所有在.m文件顯式實現的方法都會被找到
    for (int i = 0; i < count; i++) {
        SEL name = method_getName(memberFuncs[i]);
        NSString *methodName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
        NSLog(@"member method:%@", methodName);
    }
    //嘗試調用新增的方法
    Father *father = [[Father alloc] init];
    [father method:10 :@"111"];//當你敲入father實例後,是無法獲得method的提示的,只能靠手敲。而且編譯器會給出"-method" not found的警告,可以忽略
    [father release];
}
複製代碼

 

 

 

輸出結果:

 

複製代碼
2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:method::
2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:setAge:
2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:age
2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:sayHello
2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:sayGoodbay
2015-03-17 17:02:33.345 WZLCodeLibrary[38748:3170794] member method:description
2015-03-17 17:02:33.346 WZLCodeLibrary[38748:3170794] member method:dealloc
2015-03-17 17:02:33.346 WZLCodeLibrary[38748:3170794] member method:init
複製代碼

 

 

我們可以看到,method::方法的確被添加進類中了。有童鞋會問,如果在其他類文件中實例化Father類,還能調用到-method方法嗎?答案是可以的,我試驗過,在MRC下儘管無法獲得代碼提示,但請堅定不移地敲入[father method:xx :xx]方法!(在ARC下會報no visible @interface 錯誤)

接下來,我們拿系統函數玩玩,目標是讓NSString函數的大小寫轉換功能對調,讓APPLE亂套:

 

複製代碼
- (void)tryMethodExchange
{
    Method method1 = class_getInstanceMethod([NSString class], @selector(lowercaseString));
    Method method2 = class_getInstanceMethod([NSString class], @selector(uppercaseString));
    method_exchangeImplementations(method1, method2);
    NSLog(@"lowcase of WENG zilin:%@", [@"WENG zilin" lowercaseString]);
    NSLog(@"uppercase of WENG zilin:%@", [@"WENG zilin" uppercaseString]);
}
複製代碼

 

 

 

輸出結果:

 

2015-03-17 17:20:16.073 WZLCodeLibrary[38861:3180978] lowcase of WENG zilin:WENG ZILIN
2015-03-17 17:20:16.290 WZLCodeLibrary[38861:3180978] uppercase of WENG zilin:weng zilin
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章