Search code examples
runtimeivarstrong-references

add __strong ivar at runtime under ARC


A normal ivar declared in @interface is __strong default.

@interface XLPerson : NSObject {
    NSString *name; // __strong default
}
@end

Now, I create above class at runtime:

Class XLPerson = objc_allocateClassPair([NSObject class], "XLPerson", 0);
size_t size = sizeof(NSString*);
class_addIvar(XLPerson, "name", size, log2(align), @encode(NSString*)));
objc_registerClass(XLPerson);

However, the ivar named "name" isn't a __strong ivar.
While I using object_setIvar(), the Ivar can't hold the newValue (it will be deallocated at the end of Autorelease Pool).

id person = [[XLPerson alloc] init];
Ivar ivar = class_getInstanceVariable(XLPerson, "name");

@autoreleasepool {
   object_setIvar(person, ivar, [NSString stringWithFormat@"Stack%@", @"Overflow"]);
   // @"StackOverflow" will be deallocated. 
}
NSLog(@"%@", object_getIvar(person, ivar));
// BAD_ACCESS  *** -[CFString retain]: message sent to deallocated instance 0x1004002f0

Then I find two functions class_setIvarLayout and class_setWeakIvarLayout, but there is not any useful information in Objective-C Runtime Reference.

So, how can I add a __strong Ivar into my class created at runtime?


Solution

  • You need to set ivar layouts into class, in your case (a single strong ivar):

    class_setIvarLayout(class, (const uint8_t *)"\x01");
    

    then tell runtime your class is managed under ARC:

    static void fixup_class_arc(Class class) {
        struct {
            Class isa;
            Class superclass;
            struct {
                void *_buckets;
                uint32_t _mask;
                uint32_t _occupied;
            } cache;
            uintptr_t bits;
        } *objcClass = (__bridge typeof(objcClass))class;
    
    #if !__LP64__
    #define FAST_DATA_MASK 0xfffffffcUL
    #else
    #define FAST_DATA_MASK 0x00007ffffffffff8UL
    #endif
    
        struct {
            uint32_t flags;
            uint32_t version;
            struct {
                uint32_t flags;
            } *ro;
        } *objcRWClass = (typeof(objcRWClass))(objcClass->bits & FAST_DATA_MASK);
    
    #define RO_IS_ARR 1<<7
    
        objcRWClass->ro->flags |= RO_IS_ARR;
    }
    

    Call it after your class's registering, this is very tricky and maybe unstable, I would prefer properties rather than ivars.

    My blog about this in Chinese, link here