Home > Blockchain >  How can you get the shared instance from AnyClass using a protocol in Swift?
How can you get the shared instance from AnyClass using a protocol in Swift?

Time:02-24

In the past we've used Objective-C to anonymously get the sharedInstance of a class this way:

  (nullable NSObject *)sharedInstanceForClass:(nonnull Class)aClass
{
    // sharedPropertyProvider
    NSObject<KVASharedPropertyProvider> *sharedPropertyProvider = [aClass conformsToProtocol:@protocol(KVASharedPropertyProvider)]
        ? (NSObject<KVASharedPropertyProvider> *)aClass
        : nil;
    if (sharedPropertyProvider == nil)
    {
        return nil;
    }
    
    // return
    return [sharedPropertyProvider.class sharedInstance];
}

It's protocol based. We put this protocol on every class we have with a shared instance where we need to do this.

@objc (KVASharedPropertyProvider)
public protocol KVASharedPropertyProvider: AnyObject
{
    @objc (sharedInstance)
    static var sharedInstance: AnyObject { get }
}

The above works fine in Objective-C (and when called from Swift). When attempting to write the same equivalent code in Swift, however, there appears to be no way to do it. If you take this specific line(s) of Objective-C code:

NSObject<KVASharedPropertyProvider> *sharedPropertyProvider = [aClass conformsToProtocol:@protocol(KVASharedPropertyProvider)]
    ? (NSObject<KVASharedPropertyProvider> *)aClass
    : nil;

And attempt to convert it to what should be this line of Swift:

let sharedPropertyProvider = aClass as? KVASharedPropertyProvider

... initially it appears to succeed. The compiler just warns you that sharedPropertyProvider isn't be used. But as soon as you attempt to use it like so:

let sharedInstance = sharedPropertyProvider?.sharedInstance

It gives you the compiler warning back on the previous line where you did the cast:

Cast from 'AnyClass' (aka 'AnyObject.Type') to unrelated type 'KVASharedPropertyProvider' always fails

Any ideas? Is Swift simply not capable of casting AnyClass to a protocol in the same way that it could be in Objective-C?

In case you're wondering why we need to do this, it's because we have multiple xcframeworks that need to operate independently, and one xcframework (a core module) needs to optionally get the shared instance of a higher level framework to provide special processing if present (i.e. if installed) but that processing must be initiated from the lower level.

Edit:

It was asked what this code looked like in Swift (which does not work). It looks like this:

static func shared(forClass aClass: AnyClass) -> AnyObject?
{
    guard let sharedPropertyProvider = aClass as? KVASharedPropertyProvider else
    {
        return nil
    }
    
    return type(of: sharedPropertyProvider).sharedInstance
}

The above generates the warning:

Cast from 'AnyClass' (aka 'AnyObject.Type') to unrelated type 'KVASharedPropertyProvider' always fails

It was suggested I may need to use KVASharedPropertyProvider.Protocol. That looks like this:

static func shared(forClass aClass: AnyClass) -> AnyObject?
{
    guard let sharedPropertyProvider = aClass as? KVASharedPropertyProvider.Protocol else
    {
        return nil
    }
    
    return type(of: sharedPropertyProvider).sharedInstance
}

And that generates the warning:

Cast from 'AnyClass' (aka 'AnyObject.Type') to unrelated type 'KVASharedPropertyProvider.Protocol' always fails

CodePudding user response:

So, I assume you have something like this

protocol SharedProvider {
    static var shared: AnyObject { get }
}

class MySharedProvider: SharedProvider {
    static var shared: AnyObject = MySharedProvider()
}

If you want to use AnyObject/AnyClass

func sharedInstanceForClass(_ aClass: AnyClass) -> AnyObject? {
    return (aClass as? SharedProvider.Type)?.shared
}

Better approach

func sharedInstanceForClass<T: SharedProvider>(_ aClass: T.Type) -> AnyObject {
    return T.shared
}
  • Related