Objc运行时使用值设置ivar

时间:2018-06-25 15:58:23

标签: objective-c

我正在尝试使用从JSON加载的数据动态创建一个类。我已经成功创建了该类,但是仍然停留在如何使用从JSON加载的值添加几个常量的方法上。

例如,下面的JSON:

"Josh": {
    "age": 10
}

我想要一个类似的课程

class Josh {
    int age = 10;
}

但是,我只能实现

class Josh {
    int age;
}

具有以下代码:

Class Josh = objc_allocateClassPair([NSObject class], "Josh", 0);
class_addIvar(Josh, "age", sizeof(int), rint(log2(sizeof(int))), @encode(int));
objc_registerClassPair(Josh);
// Below is the desired result
Josh j = [[Josh alloc] init];
NSLog([j age]); // prints 10

所以有人可以回答我,如何将原始值10添加到我添加的这个Ivar中?谢谢

1 个答案:

答案 0 :(得分:2)

几乎可以肯定,您正在做的事情是疯狂的,您不应该对此感到困惑。

也就是说,您编写的代码甚至无法编译。

所以我们来解决它。

您不能声明Josh j,因为Josh是变量,而不是类型。相反,您可以使用id,表示“任何Objective-C对象”。

id j = [[Josh alloc] init];

除非您在某个地方声明了一个[j age]方法,否则您也不能说age,所以请把它放在某个地方:

@protocol Dummy <NSObject>
- (int)age;
@end

您也不能说NSLog([j age]),因为[j age]返回int,而不是NSString *。而是这样做:

NSLog(@"%d", [j age]);

现在,您会发现遇到“无法识别的选择器”错误,因为没有向age类添加Josh方法。为此,您首先必须导入Objective-C运行时:

#import <objc/runtime.h>

然后,您可以像这样添加age方法:

        Ivar ageIvar = class_getInstanceVariable(Josh, "age");
        ptrdiff_t ageIvarOffset = ivar_getOffset(ageIvar);

        IMP ageImp = imp_implementationWithBlock(^int(id self) {
            void *base = (__bridge void *)self;
            return *(int *)((unsigned char *)base + ageIvarOffset);
        });
        class_addMethod(Josh, @selector(age), ageImp, "v@:");

这是完整,可编译且不会崩溃的程序:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@protocol Dummy <NSObject>
- (int)age;
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Class Josh = objc_allocateClassPair([NSObject class], "Josh", 0);

        class_addIvar(Josh, "age", sizeof(int), rint(log2(sizeof(int))), @encode(int));

        Ivar ageIvar = class_getInstanceVariable(Josh, "age");
        ptrdiff_t ageIvarOffset = ivar_getOffset(ageIvar);

        IMP ageImp = imp_implementationWithBlock(^int(id self) {
            void *base = (__bridge void *)self;
            return *(int *)((unsigned char *)base + ageIvarOffset);
        });
        class_addMethod(Josh, @selector(age), ageImp, "v@:");

        objc_registerClassPair(Josh);
        // Below is the desired result
        id j = [[Josh alloc] init];
        NSLog(@"%d", [j age]); // prints 10
    }
    return 0;
}

但是它仍然只打印“ 0”:

2018-06-25 15:07:53.179809-0500 madness[35050:5762222] 0
Program ended with exit code: 0

如果要将age初始化为10,则需要在init类中覆盖Josh。如果您正常地编写init方法,它将看起来像这样:

- (instancetype)init {
    if (self = [super init]) {
        _age = 10;
    }
    return self;
}

但是我们只能发送到super的编译器可识别为方法主体的内容中,而一个块(例如我们将传递给imp_implementationWithBlock的)不算作方法主体。因此,我们必须通过调用[super init]来艰难地完成objc_msgSendSuper部分。为此,我们必须导入另一个标头:

#import <objc/message.h>

然后我们可以编写实现init的块:

        IMP initImp = imp_implementationWithBlock(^id(id self) {
            struct objc_super superInfo = {
                .receiver = self,
                .super_class = NSObject.self
            };
            id (*msgSendSuper)(struct objc_super *, SEL) = (id (*)(struct objc_super *, SEL))objc_msgSendSuper;
            self = msgSendSuper(&superInfo, @selector(init));
            if (self) {
                void *base = (__bridge void *)self;
                *(int *)((unsigned char *)base + ageIvarOffset) = 10;
            }
            return self;
        });
        class_addMethod(Josh, @selector(init), initImp, "@@:");

这是完整的程序:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>

@protocol Dummy <NSObject>
- (int)age;
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Class Josh = objc_allocateClassPair([NSObject class], "Josh", 0);

        class_addIvar(Josh, "age", sizeof(int), rint(log2(sizeof(int))), @encode(int));

        Ivar ageIvar = class_getInstanceVariable(Josh, "age");
        ptrdiff_t ageIvarOffset = ivar_getOffset(ageIvar);

        IMP ageImp = imp_implementationWithBlock(^int(id self) {
            void *base = (__bridge void *)self;
            return *(int *)((unsigned char *)base + ageIvarOffset);
        });
        class_addMethod(Josh, @selector(age), ageImp, "v@:");

        IMP initImp = imp_implementationWithBlock(^id(id self) {
            struct objc_super superInfo = {
                .receiver = self,
                .super_class = NSObject.self
            };
            id (*msgSendSuper)(struct objc_super *, SEL) = (id (*)(struct objc_super *, SEL))objc_msgSendSuper;
            self = msgSendSuper(&superInfo, @selector(init));
            if (self) {
                void *base = (__bridge void *)self;
                *(int *)((unsigned char *)base + ageIvarOffset) = 10;
            }
            return self;
        });
        class_addMethod(Josh, @selector(init), initImp, "@@:");

        objc_registerClassPair(Josh);
        // Below is the desired result
        id j = [[Josh alloc] init];
        NSLog(@"%d", [j age]); // prints 10
    }
    return 0;
}

这是输出:

2018-06-25 15:31:41.837748-0500 madness[35369:5786038] 10
Program ended with exit code: 0