Reputation: 771
I'm trying to dynamically create a class with data loaded from JSON. I have successfully created the class, but still stuck at how to add several constants with the values loaded from the JSON.
For example, the JSON below:
"Josh": {
"age": 10
}
I want a class like
class Josh {
int age = 10;
}
However, I can only achieve
class Josh {
int age;
}
with the following code:
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
So can anybody answer me, how to add the primitive value 10 to this Ivar I've added? Thank you
Upvotes: 1
Views: 940
Reputation: 385500
What you are doing is almost certainly madness and you shouldn't be messing with it.
That said, the code you wrote doesn't even compile.
So let's fix it.
You can't declare Josh j
, because Josh
is a variable, not a type. Instead you can use id
, which means “any Objective-C object”.
id j = [[Josh alloc] init];
You also can't say [j age]
unless you declare an age
method somewhere, so stick this somewhere:
@protocol Dummy <NSObject>
- (int)age;
@end
You also can't say NSLog([j age])
because [j age]
returns an int
, not an NSString *
. So do this instead:
NSLog(@"%d", [j age]);
Now you'll find that you get an “unrecognized selector” error, because you didn't add an age
method to your Josh
class. To do that, you first have to import the Objective-C runtime:
#import <objc/runtime.h>
Then you can add an age
method like this:
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@:");
Here's the complete, compilable, non-crashing program:
#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;
}
But it still just prints “0“:
2018-06-25 15:07:53.179809-0500 madness[35050:5762222] 0
Program ended with exit code: 0
If you want to initialize age
to 10, you need to override init
in your Josh
class. If you were writing the init
method normally, it would look like this:
- (instancetype)init {
if (self = [super init]) {
_age = 10;
}
return self;
}
But we can only send to super
in something the compiler recognizes as a method body, and a block (like we'll pass to imp_implementationWithBlock
) doesn't count as a method body. So we have to do the [super init]
part the hard way, by calling objc_msgSendSuper
. To do that, we have to import another header:
#import <objc/message.h>
Then we can write the block that implements 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, "@@:");
Here's the complete program:
#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;
}
And here's the output:
2018-06-25 15:31:41.837748-0500 madness[35369:5786038] 10
Program ended with exit code: 0
Upvotes: 3