Objective-C的Runtime 运行时(一)
简介
运行时的话,首先先了解下消息处理机制
。
老样子,把 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_msgSend
和 sel_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