cocoaxibclass-cluster

macOS 12.0.1 (Monterey) XIB fails to load; throwing exception "This coder is expecting the replaced object ... to be returned from NSClassSwapper"


I have XIBs that contain custom objects, one of these is actually a class cluster whose -init method always returns the same singleton object.

Basically:

- (instancetype)init
{
    self = [super init];
    if (HelpLinkHelperSingleton==nil)
        {
        // This is the first instance of DDHelpLink: make it the immortal singleton
        HelpLinkHelperSingleton = self;
        }
    else
        {
        // Not the first DDHelpLink object to be created: discard this instance
        //  and return a reference to the shared singleton
        self = HelpLinkHelperSingleton;
        }
    return self;
}

Starting in macOS 12.0.1, loading the XIB throws this exception:

This coder is expecting the replaced object 0x600002a4f680 to be returned from NSClassSwapper.initWithCoder instead of <DDHelpLink: 0x600002a487a0>

I tried implementing <NSSecureCoding> and doing the same thing, but that doesn't work either.

Is there still a way to use class clusters in NIBs?


Solution

  • I worked around this problem by using a proxy object in the XIB that forwards the messages to the singleton.

    @interface HelpLinkHelperProxy : NSObject
    @end
    
    @implementation HelpLinkHelperProxy
    {
        HelpLinkHelper* _singleton;
    }
    
    - (void) forwardInvocation:(NSInvocation*)invocation
    {
        if (_singleton == nil)
        {
            _singleton = [HelpLinkHelper new];
        }
    
        if ([_singleton respondsToSelector:[invocation selector]])
        {
            [invocation invokeWithTarget:_singleton];
        }
        else
        {
            [super forwardInvocation:invocation];
        }
    }
    
    @end
    

    If we were to subclass from NSProxy instead of NSObject, the solution would look like this:

    @interface HelpLinkHelperProxy : NSProxy
    @end
    
    @implementation HelpLinkHelperProxy
    {
        HelpLinkHelper* _singleton;
    }
    
    - (instancetype) init
    {
        _singleton = [HelpLinkHelper new];
        return self;
    }
    
    - (NSMethodSignature*) methodSignatureForSelector:(SEL)sel
    {
        return [_singleton methodSignatureForSelector:sel];
    }
    
    - (void) forwardInvocation:(NSInvocation*)invocation
    {
        if ([_singleton respondsToSelector:[invocation selector]])
        {
            [invocation invokeWithTarget:_singleton];
        }
        else
        {
            [super forwardInvocation:invocation];
        }
    }
    
    + (BOOL) respondsToSelector:(SEL)aSelector
    {
        return [HelpLinkHelper respondsToSelector:aSelector];
    }
    
    @end