學(xué)習(xí)iOS開發(fā)劣欢,runtime這個知識點(diǎn)是繞不過去的杉畜,但對于我這種學(xué)習(xí)OC不是太久,寫OC的量不夠多的人來說蹭秋,抽象理解runtime的概念或者是看源代碼有點(diǎn)枯燥扰付,效果也不好,以例子的方法學(xué)習(xí)可能會更好感凤,隨著代碼量的上升悯周,對runtime的理解會越來越深入。
詳細(xì)代碼ARRuntimeDemo,開發(fā)環(huán)境Xcode9.4
Person.h為:
#import <Foundation/Foundation.h>
@interface Person : NSObject
{
NSString * firstName;
}
@property (nonatomic, assign) int age;
+(void)run;
+(void)study;
-(void)f1;
-(void)f2;
@end
Person.m為:
#import "Person.h"
@implementation Person
{
NSString *lastname;
float weight;
}
-(instancetype)init {
self = [super init];
if (self) {
firstName = @"Andy";
}
return self;
}
-(void)f1 {
NSLog(@"執(zhí)行f1");
}
-(void)f2 {
NSLog(@"執(zhí)行f2");
}
+ (void)run {
NSLog(@"跑");
}
+ (void)study {
NSLog(@"學(xué)習(xí)");
}
@end
1 獲取類的所有變量(包括成員變量和屬性變量)
// 1. 獲取所有變量陪竿,包括成員變量和屬性變量
- (IBAction)getAllVar:(UIButton *)sender {
unsigned int count = 0;
Ivar *allVariables = class_copyIvarList([Person class], &count);
for (int i=0; i<count; i++) {
Ivar ivar = allVariables[i];
const char *Variablename = ivar_getName(ivar);
const char *VariableType = ivar_getTypeEncoding(ivar);
NSLog(@"Name: %s Type: %s", Variablename, VariableType);
}
}
結(jié)果輸出(其中firstName禽翼、lastname屠橄、weight為成員變量,_age為屬性變量):
2018-06-01 17:04:56.275194+0800 ARRuntimeDemo[60670:5448260] Name: firstName Type: @"NSString"
2018-06-01 17:04:56.276120+0800 ARRuntimeDemo[60670:5448260] Name: lastname Type: @"NSString"
2018-06-01 17:04:56.276503+0800 ARRuntimeDemo[60670:5448260] Name: weight Type: f
2018-06-01 17:04:56.276614+0800 ARRuntimeDemo[60670:5448260] Name: _age Type: i
解釋:
-
Iva
闰挡,一個指向objc_ivar結(jié)構(gòu)體指針,包含了變量名锐墙、變量類型等信息 - 像lastname、weight這種定義在
@implementation
所謂的私有變量也可獲取 - 對應(yīng)
class_copyIvarList
還有一個class_copyPropertyList
只能獲得屬性變量的方法
2 獲取所有方法(不包括類方法)
// 2. 獲取所有方法长酗,不包括類方法
- (IBAction)getAllMethod:(UIButton *)sender {
unsigned int count;
//獲取方法列表溪北,所有在.m文件顯式實(shí)現(xiàn)的方法都會被找到,包括setter+getter方法夺脾;
Method *allMethods = class_copyMethodList([Person class], &count);
for(int i =0;i<count;i++) {
//Method之拨,為runtime聲明的一個宏,表示對一個方法的描述
Method md = allMethods[i];
//獲取SEL:SEL類型,即獲取方法選擇器@selector()
SEL sel = method_getName(md);
//得到sel的方法名:以字符串格式獲取sel的name咧叭,也即@selector()中的方法名稱
const char *methodname = sel_getName(sel);
NSLog(@"(Method:%s)",methodname);
}
}
結(jié)果輸出:
2018-06-01 16:54:50.433232+0800 ARRuntimeDemo[60482:5418318] (Method:f1)
2018-06-01 16:54:50.433465+0800 ARRuntimeDemo[60482:5418318] (Method:f2)
2018-06-01 16:54:50.433930+0800 ARRuntimeDemo[60482:5418318] (Method:.cxx_destruct)
2018-06-01 16:54:50.434335+0800 ARRuntimeDemo[60482:5418318] (Method:init)
2018-06-01 16:54:50.435163+0800 ARRuntimeDemo[60482:5418318] (Method:height)
2018-06-01 16:54:50.435788+0800 ARRuntimeDemo[60482:5418318] (Method:setHeight:)
2018-06-01 16:54:50.435990+0800 ARRuntimeDemo[60482:5418318] (Method:setAge:)
2018-06-01 16:54:50.436482+0800 ARRuntimeDemo[60482:5418318] (Method:age)
解釋:
- 獲得了像
height
,setHeight
這種隱藏的setter蚀乔、getter方法 -
Method
是一個指向objc_method結(jié)構(gòu)體指針,表示對類中的某個方法的描述菲茬。 -
.cxx_destruct
是關(guān)于系統(tǒng)自動內(nèi)存釋放的隱藏方法
3 為類添加新屬性
category只能為類添加新方法吉挣,不能添加新屬性,但通過runtime配合category就可以達(dá)到添加屬性效果婉弹。
首先新建一個類Person
的category:
.h
文件
// Person+Category.h
#import "Person.h"
@interface Person (Category)
@property (nonatomic, assign)float height;
@end
.m
文件
// Person+Category.m
#import "Person+Category.h"
#import <objc/runtime.h>
const char *key = "myKey";
@implementation Person (Category)
-(void)setHeight:(float)height {
NSNumber *num = [NSNumber numberWithFloat:height];
/*
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
第一個參數(shù)是需要添加屬性的對象睬魂;
第二個參數(shù)是屬性的key,是C字符串就可以;
第三個參數(shù)是屬性的值,類型必須為id镀赌,所以此處height先轉(zhuǎn)為NSNumber類型氯哮;
第四個參數(shù)是使用策略,是一個枚舉值佩脊,類似@property屬性創(chuàng)建時設(shè)置屬性修飾符蛙粘,可從命名看出各枚舉的意義;
*/
objc_setAssociatedObject(self, key, num, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(float)height {
NSNumber *number = objc_getAssociatedObject(self, key);
return [number floatValue];
}
@end
然后就能訪問新屬性height
了:
- (IBAction)addVar:(UIButton *)sender {
per = [[Person alloc] init];
per.height = 123;
NSLog(@"%f", [per height]);
}
此時雖然通過上面的獲取所有變量方法不能獲取height
,但通過上面的額獲取所有方法可以獲取height
和setHeight
方法了:
2018-06-01 17:14:36.945742+0800 ARRuntimeDemo[60892:5482950] Name: firstName Type: @"NSString"
2018-06-01 17:14:36.948330+0800 ARRuntimeDemo[60892:5482950] Name: lastname Type: @"NSString"
2018-06-01 17:14:36.948771+0800 ARRuntimeDemo[60892:5482950] Name: weight Type: f
2018-06-01 17:14:36.949166+0800 ARRuntimeDemo[60892:5482950] Name: _age Type: i
2018-06-01 17:15:02.198444+0800 ARRuntimeDemo[60892:5482950] (Method:f1)
2018-06-01 17:15:02.198620+0800 ARRuntimeDemo[60892:5482950] (Method:f2)
2018-06-01 17:15:02.198800+0800 ARRuntimeDemo[60892:5482950] (Method:.cxx_destruct)
2018-06-01 17:15:02.198917+0800 ARRuntimeDemo[60892:5482950] (Method:init)
2018-06-01 17:15:02.199048+0800 ARRuntimeDemo[60892:5482950] (Method:height)
2018-06-01 17:15:02.199150+0800 ARRuntimeDemo[60892:5482950] (Method:setHeight:)
2018-06-01 17:15:02.199239+0800 ARRuntimeDemo[60892:5482950] (Method:setAge:)
2018-06-01 17:15:02.199356+0800 ARRuntimeDemo[60892:5482950] (Method:age)
4 添加新方法
// 4. 添加新方法
- (IBAction)addMethod:(UIButton *)sender {
/* 動態(tài)添加方法:
第一個參數(shù)表示Class cls 類型威彰;
第二個參數(shù)表示待調(diào)用的方法名稱;
第三個參數(shù)(IMP)myAddingFunction穴肘,IMP一個函數(shù)指針歇盼,這里表示指定具體實(shí)現(xiàn)方法myAddingFunction;
第四個參數(shù)表方法的參數(shù)评抚,0代表沒有參數(shù)豹缀;
*/
class_addMethod([per class], @selector(NewMethod), (IMP)myAddingFunction, 0);
//調(diào)用方法 【如果使用[per NewMethod]調(diào)用方法,在ARC下會報“no visible @interface"錯誤】
[per performSelector:@selector(NewMethod)];
}
//具體的實(shí)現(xiàn)(方法的內(nèi)部都默認(rèn)包含兩個參數(shù)Class類和SEL方法慨代,被稱為隱式參數(shù)邢笙。)
int myAddingFunction(id self, SEL _cmd){
NSLog(@"已新增方法:NewMethod");
return 1;
}
輸出:
2018-06-01 17:31:56.113168+0800 ARRuntimeDemo[61295:5543319] 已新增方法:NewMethod
5 交換兩個方法
// 5. 交換兩個方法
- (IBAction)swapMethod:(UIButton *)sender {
[Person run];
[Person study];
Method m1 = class_getClassMethod([Person class], @selector(run));
Method m2 = class_getClassMethod([Person class], @selector(study));
method_exchangeImplementations(m1, m2);
[Person run];
[Person study];
}
輸出:
2018-06-01 17:39:00.497375+0800 ARRuntimeDemo[61448:5566239] 跑
2018-06-01 17:39:00.497841+0800 ARRuntimeDemo[61448:5566239] 學(xué)習(xí)
2018-06-01 17:39:00.499255+0800 ARRuntimeDemo[61448:5566239] 學(xué)習(xí)
2018-06-01 17:39:00.499449+0800 ARRuntimeDemo[61448:5566239] 跑
這篇文章我只是做了runtime一些簡單使用,并沒有相關(guān)的使用場景侍匙,算是入門了氮惯,文末參考提到的文章都是不錯,值得以后深入。
參考:
iOS開發(fā) -- Runtime 的幾個小例子
OC最實(shí)用的runtime總結(jié)妇汗,面試帘不、工作你看我就足夠了!
iOS-RunTime杨箭,不再只是聽說
Runtime全方位裝逼指南
Objective-C Runtime