Objective-C的Runtime 运行时(一)

iOS May 24, 2015

简介

运行时的话,首先先了解下消息处理机制

老样子,把 main.m 编译成 c++ 代码。

新建一个类,RuntimeTest

RuntimeTest.h

//
//  RuntimeTest.h
//  Block-Test
//
//  Created by JieLee on 15/5/24.
//  Copyright (c) 2015年 PUPBOSS. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface RuntimeTest : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) NSInteger age;

@end

main.m

#import <Foundation/Foundation.h>
#import "RuntimeTest.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        RuntimeTest *runtime = [[RuntimeTest alloc] init];
        
        runtime.name = @"Test";
        runtime.age = 19;
    }
    return 0;
}

然后用 clang 编译下 clang -rewrite-objc main.m

又得到一个 10 万多行的 main.cpp 文件。

直接看最后,有一个

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        RuntimeTest *runtime = ((RuntimeTest *(*)(id, SEL))(void *)objc_msgSend)((id)((RuntimeTest *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("RuntimeTest"), sel_registerName("alloc")), sel_registerName("init"));

        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)runtime, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_f__kvyhp79j5wbcb2d7t24v3v_c0000gn_T_main_6fd8e2_mi_0);
        ((void (*)(id, SEL, NSInteger))(void *)objc_msgSend)((id)runtime, sel_registerName("setAge:"), (NSInteger)19);
    }
    return 0;
}

强转删掉,变成这样

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        RuntimeTest *runtime = objc_msgSend(objc_msgSend(objc_getClass("RuntimeTest"), sel_registerName("alloc")), sel_registerName("init"));

        objc_msgSend((id)runtime, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_f__kvyhp79j5wbcb2d7t24v3v_c0000gn_T_main_6fd8e2_mi_0);
        objc_msgSend((id)runtime, sel_registerName("setAge:"), (NSInteger)19);
    }
    return 0;
}

注意看 objc_msgSendsel_registerName objc_getClass 这些方法

OC 的运行时代码已经比较明显了,就是这几个方法。

为了做一个验证,我们来 main.m 里面这么写

RuntimeTest *runtime = [[RuntimeTest alloc] init];

objc_msgSend(runtime, @selector(setName:), @"Test");
NSLog(@"%@", runtime.name);

2015-05-24 13:41:13.575 Block-Test[4395:91951] Test

当然前面是要先 #import <objc/message.h>

不过有可能还有部分人这么写报错,说Too many arguments to function call, expected 0, have 3

这个我猜是 Xcode 版本的问题,如果这么报错了,在 Build Setting 里面把 Enable Strict Checking of objc_msgSend Calls 改成 NO

动态增加一个属性

之前写过一篇介绍 Category,在这里 iOS 类的分类 Category,不熟悉分类的可以看看。

里面说到了,Category 不能用于向被扩展类添加实例变量,但是有了运行时机制,这一切都变得容易了。

我们来新建一个分类

RuntimeTest+LJ.h

//
//  RuntimeTest+LJ.h
//  Block-Test
//
//  Created by JieLee on 15/5/24.
//  Copyright (c) 2015年 PUPBOSS. All rights reserved.
//

#import "RuntimeTest.h"

@interface RuntimeTest (LJ)

@property (nonatomic, assign) double height;
@end

RuntimeTest+LJ.m

#import "RuntimeTest+LJ.h"
#import <objc/message.h>

static double HeightKey;

@implementation RuntimeTest (LJ)

- (void)setHeight:(double)height {
    
    objc_setAssociatedObject(self, &HeightKey, @(height), OBJC_ASSOCIATION_ASSIGN);
}

- (double)height {
    
    return [objc_getAssociatedObject(self, &HeightKey) doubleValue];
}

@end

然后就好了,在 main.m 中就可以设置这个属性~

还有一个问题,有人可能对这个 Key 有疑问,这个 Key 其实它什么都不是,打印一下,会出来一个 0,里面是不存任何东西的,他只是当做一个索引,所以说是动态增加一个属性。

高级用法

遍历类中的属性

前面一直是用 <objc/message.h>,这次终于需要用 <objc/runtime.h> 了。

unsigned int count = 0;
/ 获得Person类中的所有成员变量
Ivar *ivars = class_copyIvarList([RuntimeTest class], &count);

// 遍历所有的成员变量
for (int i = 0; i < count; i++) {
    Ivar ivar = ivars[i];

    const char *name = ivar_getName(ivar);
    const char *type = ivar_getTypeEncoding(ivar);

    NSLog(@"%s %s", name, type);
}

2015-05-24 14:36:55.488 Block-Test[4582:118690] _name @"NSString"
2015-05-24 14:36:55.489 Block-Test[4582:118690] _age q

Tags

Jie Li

🚘 On-road / 📉 US Stock / 💻 Full Stack Developer / 🎓 Grad Student / ®️ ENTJ

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.