Table of Content

简介

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

老样子,把 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