Objc runtime устанавливает значение ivar со значением - PullRequest
0 голосов
/ 25 июня 2018

Я пытаюсь динамически создать класс с данными, загруженными из 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 голосов
/ 25 июня 2018

То, что вы делаете, это почти наверняка безумие, и вы не должны с этим возиться.

Тем не менее, написанный вами код даже не компилируется.

Итак, давайте исправим это.

Вы не можете объявить 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...