OC是運行時語言,只有在程序運行時,纔會去肯定對象的類型,並調用類與對象相應的方法。利用runtime機制讓咱們能夠在程序運行時動態修改類、對象中的全部屬性、方法,就算是私有方法以及私有屬性都是能夠動態修改的。本文旨在對runtime的部分特性小試牛刀,更多更全的方法能夠參考系統API文件<objc/runtime.h>。api
先看一個很是日常的Father類:函數
1atom 2spa 3.net 4code 5orm |
#import <Foundation/Foundation.h> 對象 @interface Father : NSObject ip @property ( nonatomic , assign) int age; ci @end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#import "Father.h" @interface Father () {<br> NSString *_name;<br>}<br> - ( 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均可以讓他們無所遁形!先看代碼,看不懂沒關係,後面會有解釋:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- ( 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); } } |
顯示以下:
1 2 3 |
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贊成偷偷修改它呢?咱們繼續往下作:(接着上面的代碼)
1 2 3 |
Ivar m_name = members[0]; object_setIvar(father, m_name, @ "zhanfen" ); NSLog (@ "after runtime:%@" , [father description]); |
顯示以下:
1 |
2015-03-17 16:10:28.004 WZLCodeLibrary[38574:3149577] after runtime:name:zhanfen, age:27 |
咱們發現,_name屬性被強制改過來了,有wengzilin改成如今zhanfen。
(2)控制私有函數
對於私有變量,咱們能作的頂多修改變量的值,但對於私有函數,咱們能夠玩很是多的花樣,好比:在運行時動態添加新的函數、修改私有函數、交換其中兩個私有函數的實現、替換私有函數...
一樣地,控制的第一步是得到Father類的全部私有方法,咱們能夠獲得.m文件中全部有顯式實現的方法以及屬性變量的setter+getter方法都會被找到:
1 2 3 4 5 6 7 8 9 10 |
- ( 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); } } |
顯示以下:
1 2 3 4 5 6 7 |
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<br><br> |
Method:runtime聲明的一個宏,表示一個方法,typedef struct objc_method *Method;
class_copyMethodList:獲取全部方法
method_getName:讀取一個Method類型的變量,輸出咱們在上層中很熟悉的SEL
=========
接下來咱們試着添加新的方法試試(這種方法等價於對Father類添加Category對方法進行擴展):
1 2 3 4 5 6 7 8 9 10 11 |
- ( 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; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
- ( 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]; } |
輸入結果:
1 2 3 4 5 6 7 8 |
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方法嗎?答案是能夠的,我試驗過,儘管沒法得到代碼提示,但請堅決不移地敲入[father method:xx :xx]方法!
接下來,咱們拿系統函數玩玩,目標是讓NSString函數的大小寫轉換功能對調,讓APPLE亂套:
1 2 3 4 5 6 7 8 |
- ( 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]); } |
輸出結果:
1 2 |
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 |