iOS開發之 - Runtime
想總結 Runtime 不是一天兩天了,前幾天利用空閑時間已經打好文稿,但覺得還有需要改進的地方,現在已經改過來了,這里貼出來與大家分享下(如果還有什么小問題,還望各位朋友指出來哈)! 下面我們就開始進入運行時 Runtime 的世界吧!
一、Runtime簡介
Runtime簡稱運行時,是系統在運行的時候的一些機制,其中最主要的是消息機制。它是一套比較底層的純 C 語言 API, 屬于一個 C 語言庫,包含了很多底層的 C 語言 API。我們平時編寫的 OC 代碼,在程序運行過程時,其實最終都是轉成了 Runtime 的 C 語言代碼。如下所示:
// OC代碼:
[Person coding];
//運行時 runtime 會將它轉化成 C 語言的代碼:
objc_msgSend(Person, @selector(coding));
二、相關函數
// 遍歷某個類所有的成員變量
class_copyIvarList
// 遍歷某個類所有的方法
class_copyMethodList
// 獲取指定名稱的成員變量
class_getInstanceVariable
// 獲取成員變量名
ivar_getName
// 獲取成員變量類型編碼
ivar_getTypeEncoding
// 獲取某個對象成員變量的值
object_getIvar
// 設置某個對象成員變量的值
object_setIvar
// 給對象發送消息
objc_msgSend
三、相關應用
- 1.利用Runtime遍歷模型對象的所有屬性
- 2.歸檔與解檔
- 3.字典模型互轉(利用 Runtime 遍歷模型對象的所有屬性, 根據屬性名從字典中取出對的值,設置到模型的屬性上)
- 4.動態添加/修改屬性、動態添加/修改/替換方法(修改Person的身高體重等;替換 coding 方法為 eating 等)
四、演示代碼
演示代碼一:遍歷對象的屬性
新建一個Person類,繼承自NSObject,在.h文件中設置它的屬性如下
@interface NNPerson : NSObject {
NSString * _str1;
}
@property NSString * str2;
@property (nonatomic, copy) NSString *str3;
@property (nonatomic, copy) NSDictionary * dict;
@property (nonatomic, copy) NSArray *ary;
在 ViewController 中獲取它的屬性,代碼如下
#import "ViewController.h"
#import <objc/runtime.h>
#import "NNPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 獲取 NNModel 中所有的變量并且輸出變量名及類型
unsigned int count = 0;
Ivar * ivars = class_copyIvarList([NNPerson class], &count);
for (unsigned int i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
NSLog(@"類型: %s -名字: %s ", ivar_getTypeEncoding(ivar), ivar_getName(ivar));
}
free(ivars);
}
演示代碼二:歸檔解檔
新建一個類,繼承自 NSObject 。.m中的代碼如下:
#pragma mark -歸檔
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
//獲取類中所有屬性
Ivar *vars = class_copyIvarList([self class], &count);
for (unsigned int i = 0; i < count; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
//利用 KVC 進行取值,根據屬性名稱獲取對應的值
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
free(vars);
}
#pragma mark -解檔
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int count = 0;
//獲取類中所有屬性
Ivar *vars = class_copyIvarList([self class], &count);
for (unsigned int i = 0; i < count; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
//進行解檔取值
id value = [aDecoder decodeObjectForKey:key];
//利用 KVC 對屬性賦值
[self setValue:value forKey:key];
}
free(vars);
}
return self;
}
ViewController中的代碼:
- (void)viewDidLoad {
[super viewDidLoad];
LZNArchive *archive = [LZNArchive objectWithKeyValues:self.Mydictionary];
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
path = [path stringByAppendingPathComponent:@"hello"];
[NSKeyedArchiver archiveRootObject:archive toFile:path];
LZNArchive *m = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
}
示例代碼三:字典與模型互轉
首先應導入一個 plist 文件,將文件中的內容讀取到字典中;然后新建一個分類如: NSObject+KeyValues
#pragma mark -字典轉模型
+(instancetype)objectWithDict:(NSDictionary *)dict {
id objc = [[self alloc] init];
//遍歷字典中的屬性
for (NSString *key in dict.allKeys) {
id value = dict[key];
objc_property_t property = class_getProperty(self, key.UTF8String);
unsigned int count = 0;
objc_property_attribute_t *attributeList = property_copyAttributeList(property, &count);
objc_property_attribute_t attribute = attributeList[0];
NSString *string = [NSString stringWithUTF8String:attribute.value];
if ([string isEqualToString:@"@\"LZNArchive\""]) {
value = [self objectWithDict:value];
}
//生成 setter 方法,并用 objc_msgSend 調用
NSString *methodName = [NSString stringWithFormat:@"set%@%@:",[key substringToIndex:1].uppercaseString,[key substringFromIndex:1]];
SEL setter = sel_registerName(methodName.UTF8String);
if ([objc respondsToSelector:setter]) {
((void (*) (id,SEL,id)) objc_msgSend) (objc,setter,value);
}
free(attributeList);
}
return objc;
}
#pragma mark -模型轉字典
-(NSDictionary *)keyValuesWithObject {
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
//遍歷模型中屬性
for (int i = 0; i < count; i ++) {
objc_property_t property = propertyList[i];
//生成 getter 方法,并用 objc_msgSend 調用
const char *propertyName = property_getName(property);
SEL getter = sel_registerName(propertyName);
if ([self respondsToSelector:getter]) {
id value = ((id (*) (id,SEL)) objc_msgSend) (self,getter);
//判斷當前屬性
if ([value isKindOfClass:[self class]] && value) {
value = [value keyValuesWithObject];
}
if (value) {
NSString *key = [NSString stringWithUTF8String:propertyName];
[dict setObject:value forKey:key];
}
}
}
free(propertyList);
return dict;
}
演示代碼四:objc_msgSend 消息傳遞
新建一個類,繼承自NSObject,.m中的代碼如下:
- (void)sayHello {
NSLog(@"Hello!");
}
-(void)showName:(NSString *)name {
NSLog(@"My name is %@",name);
}
-(void)showAge:(NSInteger)age {
NSLog(@"My age is %ld", age);
}
-(float)showHeight{
return 180.0f;
}
-(NSString *)showInformation {
return @"Nice to meet you!";
}
ViewController中的代碼:
LZN_msgSend *msgSend = [[LZN_msgSend alloc] init];
((void (*) (id, SEL)) objc_msgSend) (msgSend, sel_registerName("sayHello"));
((void (*) (id, SEL, NSString *)) objc_msgSend) (msgSend, sel_registerName("showName:"), @"Liu Zhong Ning");
((void (*) (id, SEL, NSInteger)) objc_msgSend) (msgSend, sel_registerName("showAge:"), 23);
float f = ((float (*) (id, SEL)) objc_msgSend_fpret) (msgSend, sel_registerName("showHeight"));
NSLog(@"and height is %.2fcm",f);
NSString *information = ((NSString* (*) (id, SEL)) objc_msgSend) (msgSend, sel_registerName("showInformation"));
NSLog(@"%@",information);
the end
結束語:Runtime 相關的知識就整理了這么些,希望對看到的朋友們有用!另外如果 demo 有什么問題,還煩請大家告知,一起進步!謝謝O(∩_∩)O~! 另祝大家周末愉快哈!