0

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?

James Bucanek
  • 3,299
  • 3
  • 14
  • 30

1 Answers1

1

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

Kai Oezer
  • 104
  • 1
  • 3
  • Cleaver solution! But I I'd recommend a generalized solution that's a subclass of `NSProxy`. In fact, you'd only need one general purposes implementation for all local proxy solutions: `@interace LocalObject : NSProxy` ... `-(instancetype)initWithObject:(id)localObject` ... Then, if you wanted to be really clever, you could implement convenience constructors, as categories, for specific objects: `@implementation LocalObject (HelpLinkProxy)` ... `+ (LocalObject*)helpLinkProxy { return [[LocalObject alloc] initWithObject:[HelpLinkHelper new]]; }` – James Bucanek Nov 20 '21 at 19:26
  • I gave it a try. It is possible to subclass from `NSProxy` and it will work in a XIB although `NSProxy` does not subclass `NSObject`. In that case, `_singleton` will be initialized in the `init` method of the `NSProxy` subclass, and the `methodSignatureForSelector:` method needs to be implemented as well. – Kai Oezer Nov 21 '21 at 19:14