iOS開發之 - Runtime

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~! 另祝大家周末愉快哈!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容