iOS開發(fā)-- Runtime的幾個小例子
字數(shù)2756閱讀1867評論22喜歡88
一畔裕、什么是runtime(也就是所謂的“運行時”宵睦,因為是在運行時實現(xiàn)的仲器。)
1.runtime是一套底層的c語言API(包括很多強大實用的c語言類型,c語言函數(shù));? [runtime運行系統(tǒng)]
2.實際上,平時我們編寫的oc代碼,底層都是基于runtime實現(xiàn)的;? ? ? ? ? ? ? ? ? ? ? ? ? ? [OC語言的動態(tài)性]
運行時系統(tǒng) (runtime system)滥壕,對于C語言拌阴,函數(shù)的調用在編譯的時候會決定調用哪個函數(shù)湿刽。對于OC的函數(shù)的烁,屬于動態(tài)調用過程,在編譯的時候并不能決定真正調用哪個函數(shù)诈闺,只有在真正運行的時候才會根據(jù)函數(shù)的名稱找到對應的函數(shù)來調用渴庆。runtime就是OC辛苦的幕后工作人員。(編譯器會自動幫助我們編譯成runtime代碼雅镊。)
動態(tài)特性:使得它在語言層面上支持程序的可擴展性襟雷。只有在程序運行時,才會去確定對象的類型仁烹,并調用類與對象相應的方法耸弄。利用runtime機制讓我們可以在程序運行時動態(tài)修改類的具體實現(xiàn)、包括類中的所有私有屬性卓缰、方法计呈。這也是本文runtime例子的出發(fā)點砰诵。
我們所敲入的代碼轉化為運行時的runtime函數(shù)代碼,最終在程序運行時轉成了底層的runtime的c語言代碼;
舉例:
當某個對象使用語法[receiver message]來調用某個方法時,其實[receiver message]被編譯器轉化為:
idobjc_msgSend (idself, SEL op, ... );
也就是說,我們平時編寫的oc代碼,方法調用的本質些椒,就是在編譯階段,編譯器轉化為向對象發(fā)送消息理肺。
【本次開發(fā)環(huán)境: Xcode:7.2? ? iOS Simulator:iphone6? ? By: 啊左
本文Demo下載鏈接:runtime-Demo】
二、runtime的幾種使用方法
我們通過繼承于NSObject的person類善镰,來對runtime進行學習妹萨。
本文共有6個關于runtime機制方法的小例子,分別是:
1.獲取person類的所有變量炫欺;
2.獲取person類的所有方法乎完;
3.改變person類的私有變量name的值;
4.為person的category類增加一個新屬性竣稽;
5.為person類添加一個方法囱怕;
6.交換person類的2個方法的功能霍弹;
(個人習慣毫别,喜歡為6個例子添加按鈕各自的行為方法,并分別執(zhí)行相應的行為典格,以此看清各個runtime函數(shù)的具體功能所帶來的效果岛宦。)
首先,創(chuàng)建新的項目耍缴,并在項目中新建一個普通的OC類:person類(繼承于NSObject)砾肺,為了避免后面與其他方法函數(shù)搞混,我們把完整的person類編寫齊全防嗡,用于后面使用runtime的幾種方法:
①person.h如下:
#import@interfaceperson:NSObject@property(nonatomic,assign)intage;//屬性變量-(void)func1;-(void)func2;@end
②person.m如下:
#import"person.h"@implementationperson{NSString*name;//實例變量}//初始化person屬性-(instancetype)init{self= [superinit];if(self) {? name =@"Tom";self.age=12; }returnself;}//person的2個普通方法-(void)func1{NSLog(@"執(zhí)行func1方法变汪。");}-(void)func2{NSLog(@"執(zhí)行func2方法。");}//輸出person對象時的方法:-(NSString*)description{return[NSStringstringWithFormat:@"name:%@ age:%d",name,self.age];}@end
從person類的描述中蚁趁,我們可以看到person類含有一個可供外類使用的共有屬性age裙盾,以及一個外界不可以訪問私有屬性name,但是他嫡,有木有想過番官,其實在外類,name也是可以訪問的钢属。OC里面徘熔,通過runtime系統(tǒng),蘋果允許不受這些私有屬性的限制淆党,對私有屬性私有方法等進行訪問酷师、添加讶凉、修改、甚至替換系統(tǒng)的方法山孔。
那么缀遍,為項目的故事板添加6個按鈕;
在使用runtime的地方饱须,我們都需要包含頭文件:
#import//(在需要使用runtime的實現(xiàn)文件.m中包含即可.)
1.獲取person類的所有變量
將第一個按鈕關聯(lián)到ViewController.h域醇,添加行為并命名其方法為:“getAllVariable”:
- (IBAction)getAllVariable:(UIButton*)sender;//獲取所有變量
在ViewController.m中的實現(xiàn)如下:
/*1.獲取person所有的成員變量*/- (IBAction)getAllVariable:(UIButton*)sender {unsignedintcount =0;//獲取類的一個包含所有變量的列表,IVar是runtime聲明的一個宏蓉媳,是實例變量的意思.Ivar *allVariables = class_copyIvarList([person class], &count);for(inti =0;i
點擊按鈕后譬挚,得到的輸出如下:(i表示類型為int)
2016-05-1817:17:10.502runtime運行時[10164:452725] (Name: name) ----- (Type:@"NSString")2016-05-1817:17:10.503runtime運行時[10164:452725] (Name: _age) ----- (Type:i)
分析:Ivar,一個指向objc_ivar結構體指針,包含了變量名酪呻、變量類型等信息减宣。
可以看到,私有屬性name能夠訪問到了玩荠。 在有些項目中漆腌,為了對某些私有屬性進行隱藏,某些.h文件中沒有出現(xiàn)相應的顯式創(chuàng)建阶冈,而是如上面的person類中闷尿,在.m中進行私有創(chuàng)建,但是我們可以通過runtime這個有效的方法女坑,訪問到所有包括這些隱藏的私有變量填具。
拓展:
①class_copyIvarList能夠獲取一個含有類中所有成員變量的列表,列表中包括屬性變量和實例變量匆骗。需要注意的是劳景,如果如本例中,age返回的是"_age"碉就,但是如果在person.m中加入:@synthesize age;
那么控制臺第二行返回的是"(Name: age) ----- (Type:i) 盟广;"
(因為@property是生成了"_age",而@synthesize是執(zhí)行了"@synthesize age = _age;"瓮钥,關于OC屬性變量與實例變量的區(qū)別筋量、@property、@synthesize的作用等具體的知識骏庸,有興趣的童鞋可以自行了解毛甲。)
②如果單單需要獲取屬性列表的話,可以使用函數(shù):class_copyPropertyList();只是返回的屬性變量僅僅是“age”具被,做為實例變量的name是不被獲取的玻募。
而class_copyIvarList()函數(shù)則能夠返回實例變量和屬性變量的所有成員變量。
2.獲取person類的所有方法
將第二個按鈕關聯(lián)到ViewController.h一姿,添加行為并命名其方法為:“getAllMethod”:
- (IBAction)getAllMethod:(UIButton*)sender;//獲取所有方法
在ViewController.m中的實現(xiàn)如下:
/*2.獲取person所有方法*/- (IBAction)getAllMethod:(UIButton*)sender {unsignedintcount;//獲取方法列表七咧,所有在.m文件顯式實現(xiàn)的方法都會被找到跃惫,包括setter+getter方法;Method *allMethods = class_copyMethodList([person class], &count);for(inti =0;i
點擊按鈕后艾栋,控制臺輸出:
2016-05-1917:05:19.880runtime運行時[14054:678124] (Method:func1)2016-05-1917:05:19.881runtime運行時[14054:678124] (Method:func2)2016-05-1917:05:19.881runtime運行時[14054:678124] (Method:setAge:)2016-05-1917:05:19.881runtime運行時[14054:678124] (Method:age)2016-05-1917:05:19.881runtime運行時[14054:678124] (Method:.cxx_destruct)2016-05-1917:05:19.882runtime運行時[14054:678124] (Method:description)2016-05-1917:05:19.882runtime運行時[14054:678124] (Method:init)
控制臺輸出了包括set和get等方法名稱爆存。【備注:.cxx_destruct方法是關于系統(tǒng)自動內存釋放工作的一個隱藏的函數(shù)蝗砾,當ARC下先较,且本類擁有實例變量時,才會出現(xiàn)悼粮;】
分析:Method是一個指向objc_method結構體指針闲勺,表示對類中的某個方法的描述。在API中的定義:typedef struct objc_method Method;
而objc_method結構體如下:
truct objc_method { SEL method_name OBJC2_UNAVAILABLE;char*method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE;}
method_name :方法選擇器@selector()扣猫,類型為SEL菜循。 相同名字的方法下,即使在不同類中定義申尤,它們的方法選擇器也相同癌幕。
method_types:方法類型,是個char指針昧穿,存儲著方法的參數(shù)類型和返回值類型勺远。
method_imp:指向方法的具體實現(xiàn)的指針,數(shù)據(jù)類型為IMP粤咪,本質上是一個函數(shù)指針谚中。 在第五個按鈕行為“增加一個方法”部分會提到渴杆。
SEL:數(shù)據(jù)類型寥枝,表示方法選擇器,可以理解為對方法的一種包裝磁奖。在每個方法都有一個與之對應的SEL類型的數(shù)據(jù)囊拜,根據(jù)一個SEL數(shù)據(jù)“@selector(方法名)”就可以找到對應的方法地址,進而調用方法比搭。
因此可以通過:獲取Method結構體->得到SEL選擇器名稱->得到對應的方法名冠跷,這樣的方式,便于認識OC中關于方法的定義身诺。
3.改變person類的私有變量name的值.
將第三個按鈕關聯(lián)到ViewController.h蜜托,添加行為并命名其方法為:“changeVariable”:
- (IBAction)changeVariable:(UIButton*)sender;//改變其中name變量
在ViewController.m中創(chuàng)建一個person對象,記得初始化
@implementationViewController{? person *per;//創(chuàng)建一個person實例}- (void)viewDidLoad {? [superviewDidLoad];? per = [[person alloc]init];//記得要初始化...不然后果自己嘗試下}
在ViewController.m中的實現(xiàn)如下:
/*3.改變person的name變量屬性*/- (IBAction)changeVariable:(UIButton*)sender {NSLog(@"改變前的person:%@",per);unsignedintcount =0;Ivar *allList = class_copyIvarList([person class], &count); Ivar ivv = allList[0];//從第一個方法getAllVariable中輸出的控制臺信息霉赡,我們可以看到name為第一個實例屬性橄务。object_setIvar(per, ivv,@"Mike");//name屬性Tom被強制改為Mike。NSLog(@"改變之后的person:%@",per);}
點擊按鈕后穴亏,控制臺輸出:
2016-05-1922:45:05.125runtime運行時[1957:34730] 改變前的person:name:Tom age:122016-05-1922:45:05.126runtime運行時[1957:34730] 改變之后的person:name:Mike age:12
4.為person的category類增加一個新屬性:
如何在不改動某個類的前提下蜂挪,添加一個新的屬性呢重挑?
答:可以利用runtime為分類添加新屬性。
在iOS中棠涮,category谬哀,也就是分類,是不可以為本類添加新的屬性的严肪,但是在runtime中我們可以使用對象關聯(lián)史煎,為person類進行分類的新屬性創(chuàng)建:
①新建一個新的OC類:
命名為:PersonCategory,點擊next:
②在出現(xiàn)的新類“person+PersonCategory.h”中驳糯,添加“height”:
#import"person.h"@interfaceperson(PersonCategory)@property(nonatomic,assign)floatheight;//新屬性@end
“person+PersonCategory.m”類的代碼如下:
#import"person+PersonCategory.h"#import//runtime API的使用需要包含此頭文件constchar* str ="myKey";//做為key劲室,字符常量 必須是C語言字符串;@implementationperson(PersonCategory)-(void)setHeight:(float)height{NSNumber*num = [NSNumbernumberWithFloat:height];/*
第一個參數(shù)是需要添加屬性的對象结窘;
第二個參數(shù)是屬性的key;
第三個參數(shù)是屬性的值,類型必須為id很洋,所以此處height先轉為NSNumber類型;
第四個參數(shù)是使用策略隧枫,是一個枚舉值喉磁,類似@property屬性創(chuàng)建時設置的關鍵字,可從命名看出各枚舉的意義官脓;
*/objc_setAssociatedObject(idobject,constvoid*key,idvalue, objc_AssociationPolicy policy);}//提取屬性的值:-(float)height{NSNumber*number = objc_getAssociatedObject(self, str);return[number floatValue];}@end
接下來协怒,我們可以在ViewController.m中對person的一個對象進行height的訪問了,
將第四個按鈕關聯(lián)到ViewController.h添加行為并命名其方法為:“addVariable:”(記得:#import "person+PersonCategory.h")
-(IBAction)addVariable:(UIButton *)sender;
在ViewController.m中的實現(xiàn)如下:
/* 4.添加新的屬性*/- (IBAction)addVariable:(UIButton*)sender { per.height=12;//給新屬性height賦值NSLog(@"%f",[per height]);//訪問新屬性值}
點擊按鈕四卑笨、再點擊按鈕一孕暇、二獲取類的屬性、方法赤兴。
2016-05-2015:39:54.432runtime運行時[4605:178974]12.0000002016-05-2015:39:56.295runtime運行時[4605:178974] (Name: name) ----- (Type:@"NSString")2016-05-2015:39:56.296runtime運行時[4605:178974] (Name: _age) ----- (Type:i)2016-05-2015:39:57.195runtime運行時[4605:178974] (Method:func1)2016-05-2015:39:57.196runtime運行時[4605:178974] (Method:func2)2016-05-2015:39:57.196runtime運行時[4605:178974] (Method:setAge:)2016-05-2015:39:57.196runtime運行時[4605:178974] (Method:age)2016-05-2015:39:57.196runtime運行時[4605:178974] (Method:.cxx_destruct)2016-05-2015:39:57.197runtime運行時[4605:178974] (Method:description)2016-05-2015:39:57.197runtime運行時[4605:178974] (Method:init)2016-05-2015:39:57.197runtime運行時[4605:178974] (Method:height)2016-05-2015:39:57.197runtime運行時[4605:178974] (Method:setHeight:)
分析:可以看到分類的新屬性可以在per對象中對新屬性height進行訪問賦值妖滔。
獲取到person類屬性時,依然沒有height的存在桶良,但是卻有height和setHeight這兩個方法座舍;因為在分類中,即使使用@property定義了陨帆,也只是生成set+get方法曲秉,而不會生成_變量名,分類中是不允許定義變量的疲牵。
使用runtime中objc_setAssociatedObject()和objc_getAssociatedObject()方法承二,本質上只是為對象per添加了對height的屬性關聯(lián),但是達到了新屬性的作用纲爸;
使用場景:假設imageCategory是UIImage類的分類亥鸠,在實際開發(fā)中,我們使用UIImage下載圖片或者操作過程需要增加一個URL保存一段地址缩焦,以備后期使用读虏。這時可以嘗試在分類中動態(tài)添加新屬性MyURL進行存儲责静。
5.為person類添加一個新方法;
將第五個按鈕關聯(lián)到ViewController.h盖桥,添加行為并命名其方法為:“addMethod”:
-(IBAction)addMethod:(UIButton *)sender;
在ViewController.m中的實現(xiàn)如下:
/*5.添加新的方法試試(這種方法等價于對Father類添加Category對方法進行擴展):*/- (IBAction)addMethod:(UIButton*)sender {/* 動態(tài)添加方法:
第一個參數(shù)表示Class cls 類型灾螃;
第二個參數(shù)表示待調用的方法名稱;
第三個參數(shù)(IMP)myAddingFunction揩徊,IMP一個函數(shù)指針腰鬼,這里表示指定具體實現(xiàn)方法myAddingFunction;
第四個參數(shù)表方法的參數(shù)塑荒,0代表沒有參數(shù)熄赡;
*/class_addMethod([per class],@selector(NewMethod), (IMP)myAddingFunction,0);//調用方法 【如果使用[per NewMethod]調用方法,在ARC下會報“no visible @interface"錯誤】[per performSelector:@selector(NewMethod)];}//具體的實現(xiàn)(方法的內部都默認包含兩個參數(shù)Class類和SEL方法齿税,被稱為隱式參數(shù)彼硫。)intmyAddingFunction(idself, SEL _cmd){NSLog(@"已新增方法:NewMethod");return1;}
點擊按鈕后,控制臺輸出:
2016-05-2014:08:55.822runtime運行時[1957:34730] 已新增方法:NewMethod
6.交換person類的2個方法的功能:
將第六個按鈕關聯(lián)到ViewController.h凌箕,添加行為并命名其方法為:“replaceMethod”:
-(IBAction)replaceMethod:(UIButton *)sender;
在ViewController.m中的實現(xiàn)如下:
/* 6.交換兩種方法之后(功能對調)拧篮,可以試試讓蘋果亂套... */- (IBAction)replaceMethod:(UIButton*)sender { Method method1 = class_getInstanceMethod([person class],@selector(func1)); Method method2 = class_getInstanceMethod([person class],@selector(func2));//交換方法method_exchangeImplementations(method1, method2); [per func1];//輸出交換后的效果,需要對比的可以嘗試下交換前運行func1牵舱;}
點擊按鈕后串绩,控制臺輸出:
2016-05-2014:11:57.381runtime運行時[1957:34730] 執(zhí)行func2方法。
交換方法的使用場景:項目中的某個功能芜壁,在項目中需要多次被引用礁凡,當項目的需求發(fā)生改變時,要使用另一種功能代替這個功能慧妄,且要求不改變舊的項目(也就是不改變原來方法實現(xiàn)的前提下)顷牌。那么,我們可以在分類中腰涧,再寫一個新的方法(符合新的需求的方法)韧掩,然后交換兩個方法的實現(xiàn)。這樣窖铡,在不改變項目的代碼,而只是增加了新的代碼的情況下坊谁,就完成了項目的改進费彼,很好地體現(xiàn)了該項目的封裝性與利用率。
注:交換兩個方法的實現(xiàn)一般寫在類的load方法里面口芍,因為load方法會在程序運行前加載一次箍铲。
(轉載請標明原文出處,謝謝支持 ~ ^-^ ~)
? by:啊左~
如果覺得我的文章對您有用鬓椭,請隨意打賞颠猴。您的支持將鼓勵我繼續(xù)創(chuàng)作关划!
88
喜歡的用戶
鑫胖2016.11.03 10:42
congdufs2016.11.02 17:53
是刺猬2016.11.01 05:34
CarlXu2016.10.29 00:21
竹內溪風2016.10.27 09:40
sq_882016.10.13 18:04
康龍歸海2016.10.08 10:37
上發(fā)條的樹2016.09.20 16:54
花花你個花花呦2016.08.21 22:40
Peter_時2016.08.17 23:25
沒駱駝de祥子2016.08.16 17:59
常義2016.08.14 22:23
SunshineAU2016.08.12 19:50
iOS_Job2016.08.09 18:47
于_先笙2016.08.08 19:33
2 樓 ·2016.05.24 09:52
不錯,好好學習一下
3 樓 ·2016.05.24 18:51
mark
4 樓 ·2016.05.26 22:47
說的明白翘瓮,還有例子贮折,??
5 樓 ·2016.05.28 21:47
寫了一下,在添加新方法那里资盅,
class_addMethod([_per class], @selector(sayHi), (IMP)myAddingFunction, 0);
selector()里面的方法名好像沒什么用调榄?,只會執(zhí)行IMP類型的C語言函數(shù)呵扛?
啊左:@簡單也好第二個參數(shù)是讓對象調用的方法名稱每庆。
[per performSelector:@selector(NewMethod)];
簡單也好:@啊左[per performSelector:@selector(NewMethod)]; 卻沒有執(zhí)行NewMethod方法,執(zhí)行的是(IMP)myAddingFunction里面的內容
啊左:@簡單也好NewMetho只是一個方法名今穿,可隨意命名缤灵,主要是給對象用來調用的。但是實際上具體方法實現(xiàn)是在(IMP)函數(shù)指針指向的內容里面蓝晒,那是“實現(xiàn)這個方法的函數(shù)”凤价。
雖然一般都說class_addMethod是添加方法,但個人覺得說是添加函數(shù)更貼切些拔创。
6 樓 ·2016.06.28 15:58
寫的很好,重點是有代碼能自己試一下剩燥,學習了慢逾。
7 樓 ·2016.07.04 16:51
樓主,額能請教你些問題嗎灭红?
啊左:@0ef0376dc7d1你可以直接問呀侣滩。
8 樓 ·2016.08.08 16:54
給樓主點個贊。
啊左:@butterflyer木有看到你的贊哦~
9 樓 ·2016.08.16 14:41
??
10 樓 ·2016.08.24 10:04
這里獲取所有對象的所有方法得到的是其所有的實例方法
11 樓 ·2016.10.10 11:12
什么是編譯变擒,什么是運行君珠?
編譯:編譯器幫你把源代碼翻譯成機器能識別的代碼,或者說識別語法等代碼錯誤并產(chǎn)生能夠識別的指令以便讓運行時能夠進行相應的內存分配娇斑。
運行時:代碼跑(run)起來了策添,進行內存的分配與操作。
以上可能不太準確毫缆,純屬個人見解唯竹。具體的可以網(wǎng)上查找這兩個的意義與區(qū)別。
12 樓 ·2016.10.28 08:06
獲取的實例變量方法中苦丁,name沒有下劃線是因為樓主寫的不規(guī)范浸颓,實例變量name沒有加下劃線,所以打印出來就沒有,而系統(tǒng)自動生成的age實例變量就有下劃線
其實這里一直重點強調的是产上,age會因為屬性變量機制中默認的@synthesize使得它自動補全為_age棵磷,所以很明顯把name命名為沒有下劃線是為了區(qū)分出來:屬性變量age在@property后會補全下劃線,在這里沒有強調name的下劃線晋涣,是為了避免混淆;
你可以直接說這種命名不規(guī)范仪媒,但不是“因為不規(guī)范”所以才有下劃線??,那是因為:在語法正確情況下不管怎么命名實例變量姻僧,它都就只輸出原來的名字规丽,即使我加了2個下劃線,它也照樣輸出2個下劃線撇贺。
另外赌莺,實例變量這種語法屬于比較老派的用法,以前為了防止內存管理泄露松嘶,與屬性變量調用訪問方法區(qū)分開來艘狭,”_變量名”往往意味著實例變量,包括我公司舊的項目也是實例變量加_翠订、屬性變量加“@synthesize name = _name;”但是在現(xiàn)在ARC后巢音,不存在上述問題,一年多前我身邊的挺多同行也是常常加下劃線尽超,包括后來看《精通iOS開發(fā)》這些書介紹的時候官撼,也是發(fā)現(xiàn)里面的實例變量也沒有加下劃線,所以我想現(xiàn)在更多的已經(jīng)是習慣問題而不是規(guī)范似谁,當然也會有相關資料建議下劃線做為編碼規(guī)范傲绣。這就看開發(fā)者怎么去處理了。
?+Return 發(fā)表
被以下專題收入,發(fā)現(xiàn)更多相似內容:
如果你是程序員塞琼,或者有一顆喜歡寫程序的心菠净,喜歡分享技術干貨、項目經(jīng)驗彪杉、程序員日常囧事等等毅往,歡迎投稿《程序員》專題。
專題主編:小...
26612篇文章· 209657人關注
玩轉簡書的第一步在讶,從這個專題開始煞抬。
想上首頁熱門榜么?好內容想被更多人看到么构哺?來投稿吧!如果被拒也不要灰心哦~入選文章會進一個隊...
113720篇文章· 138152人關注
分享 iOS 開發(fā)的知識,解決大家遇到的問題曙强,討論iOS開發(fā)的前沿残拐,歡迎大家投稿~
14867篇文章· 27901人關注