Swift 4 Codable & generics - swift

I'm adding cache to my app with PINCache and I am in a situation where delegate methods to encode/decode are called by the cache system.
Those methods are generic but the generic value do not conform explicitly to Codable. Because they are delegates, I can't change the signature to make the generic type conform to Codable.
func modelForKey<T : SimpleModel>(_ cacheKey: String?, context: Any?, completion: #escaping (T?, NSError?) -> ()) {
guard let cacheKey = cacheKey, let data = cache.object(forKey: cacheKey) as? Data, T.self is Codable else {
completion(nil, nil)
return
}
let decoder = JSONDecoder()
do {
let model: T = try decoder.decode(T.self, from: data)
completion(model, nil)
} catch {
completion(nil, nil)
}
}
With this code, I'm having the following error:
In argument type T.Type, T does not conform to expected type Decodable
How can I force my decoder to accept the generic value?

Since Codable can't be implemented in extensions (yet?) and since SimpleModel is internal to PINCache you can't make it conform to Codable.
If possible I would suggest switching to a caching library with a protocol that supports Codable like Cache

Try func modelForKey<T : SimpleModel, Decodable> ... to require that type is constrained to Decodable.

Change this line to check that it conforms to Decodable:
guard let cacheKey = ... as? Data, T.self is Decodable else {

IMO the problem is not with PINCache.
T.self is Codable doesn't tell the compiler more about the type T, so decoder.decode(T.self, from: data) won't pass the type checking, even if T is a Decodable.
I think forking RocketData will be the easiest solution here (if you want to keep using RocketData + Decodable and all your models conform to Decodable). Make SimpleModel conform to Decodable.

Try to create CustomProtocol: Codable, SimpleModel
If point 1 doesn't work, try to create custom class CustomClass: SimpleModel, Codable and use modelForKey<T : CustomClass>

Related

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

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
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
}

Downcast enum with Error protocol from Any type in Swift 4 [duplicate]

FYI: Swift bug raised here: https://bugs.swift.org/browse/SR-3871
I'm having an odd problem where a cast isn't working, but the console shows it as the correct type.
I have a public protocol
public protocol MyProtocol { }
And I implement this in a module, with a public method which return an instance.
internal struct MyStruct: MyProtocol { }
public func make() -> MyProtocol { return MyStruct() }
Then, in my view controller, I trigger a segue with that object as the sender
let myStruct = make()
self.performSegue(withIdentifier: "Bob", sender: myStruct)
All good so far.
The problem is in my prepare(for:sender:) method.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Bob" {
if let instance = sender as? MyProtocol {
print("Yay")
}
}
}
However, the cast of instance to MyProtocol always returns nil.
When I run po sender as! MyProtocol in the console, it gives me the error Could not cast value of type '_SwiftValue' (0x1107c4c70) to 'MyProtocol' (0x1107c51c8). However, po sender will output a valid Module.MyStruct instance.
Why doesn't this cast work?
(I've managed to solve it by boxing my protocol in a struct, but I'd like to know why it's not working as is, and if there is a better way to fix it)
This is pretty weird bug – it looks like it happens when an instance has been bridged to Obj-C by being boxed in a _SwiftValue and is statically typed as Any(?). That instance then cannot be cast to a given protocol that it conforms to.
According to Joe Groff in the comments of the bug report you filed:
This is an instance of the general "runtime dynamic casting doesn't bridge if necessary to bridge to a protocol" bug. Since sender is seen as _SwiftValue object type, and we're trying to get to a protocol it doesn't conform to, we give up without also trying the bridged type.
A more minimal example would be:
protocol P {}
struct S : P {}
let s = S()
let val : Any = s as AnyObject // bridge to Obj-C as a _SwiftValue.
print(val as? P) // nil
Weirdly enough, first casting to AnyObject and then casting to the protocol appears to work:
print(val as AnyObject as! P) // S()
So it appears that statically typing it as AnyObject makes Swift also check the bridged type for protocol conformance, allowing the cast to succeed. The reasoning for this, as explained in another comment by Joe Groff, is:
The runtime has had a number of bugs where it only attempts certain conversions to one level of depth, but not after performing other conversions (so AnyObject -> bridge -> Protocol might work, but Any -> AnyObject -> bridge -> Protocol doesn't). It ought to work, at any rate.
The problem is that the sender must pass through the Objective-C world, but Objective-C is unaware of this protocol / struct relationship, since both Swift protocols and Swift structs are invisible to it. Instead of a struct, use a class:
protocol MyProtocol {}
class MyClass: MyProtocol { }
func make() -> MyProtocol { return MyClass() }
Now everything works as you expect, because the sender can live and breathe coherently in the Objective-C world.
Still not fixed. My favorite and easiest workaround is by far chain casting:
if let instance = sender as AnyObject as? MyProtocol {
}
I came across this issue on macOS 10.14.
I have an _NSXPCDistantObject coming from Objc for which
guard let obj = remoteObj as? MyProtocol else { return }
returns
My solution was to define a c function in a separate header like this:
static inline id<MyProtocol> castObject(id object) {
return object
}
And then use like this:
guard let obj: MyProtocol = castObject(remoteObject) else { return }
Here's my solution. I didn't want to just make it into a class (re: this answer) because my protocol is being implemented by multiple libraries and they would all have to remember to do that.
I went for boxing my protocol into a struct.
public struct BoxedMyProtocol: MyProtocol {
private let boxed: MyProtocol
// Just forward methods in MyProtocol onto the boxed value
public func myProtocolMethod(someInput: String) -> String {
return self.boxed.myProtocolMethod(someInput)
}
}
Now, I just pass around instances of BoxedMyProtocol.
I know this issue was resolved with swift 5.3 but sometimes you have to support older versions of iOS.
You can cast to the protocol if you first cast it to AnyObject.
if let value = (sender as? AnyObject) as? MyProtocol {
print("Yay")
}

Can I implement default initialization in protocol in Swift

I have a variety of classes that all conform to a single protocol and share the same initialization method. Is there a way to implement the initialization in the protocol? So I don't have to copy the code in every class. This is what I have so far
protocol someProtocol {
init(data: Data)
}
class ObjectA: someProtocol {
let data: Data
required init(data: Data) {
self.data = data
}
}
class ObjectB: someProtocol {
let data: Data
required init(data: Data) {
self.data = data
}
}
You can’t do this as the protocol and protocol extension have no knowledge about the properties in the objects that conform to them and so you cannot initialise all the story properties.
I’m sure there are other runtime reason about type inference too but this one is probably the most simple to explain.

How to call a static method of a generic type conforming to a protocol in Swift

Say I have:
#objc public protocol InteractivelyNameable: Nameable {
static func alertViewForNaming(_ existingObject: Nameable?,
context: NSManagedObjectContext,
completion:#escaping ((_ success: Bool, _ object: Nameable?, _ didCancel: Bool, _ error: Error?) -> Void)) -> UIAlertController?
}
And I have a generic view controller that manages various types (generic type is .fetchableObjectType... basically NSManagedObject.self.. well, a subclass of it). I need to check if a specific object type conforms to the protocol, and if so, invoke it.
something like:
// valid swift code
if self.dataSource.fetchableObjectType is InteractivelyNameable {
// not valid swift code
if let alert = (self.dataSource.fetchableObjectType as! InteractivelyNameable).alertViewForNaming(....) { // ... do stuff }
}
To cast a Type to a Protocol at a "Class Level", you can use the .Type property of the protocol itself.
if let type = self.dataSource.fetchableObjectType as? InteractivelyNameable.Type {
if let alert = type.alertViewForNaming(nil, context: self.dataSource.managedObjectContext, completion: completion) {
// this code finds itself inside a UIViewController subclass...
self.present(alert, animated: true, completion: nil)
return
}
}
Summary in Generic Form:
if let myConformingObject = someObject as? InteractivelyNameable {
// invoke instance methods...
myConformingObject.someInstanceMethodDefinedInProtocol()
// invoke class methods
type(of: myConformingObject).someClassMethodDefinedInProtocol()
}
// i.e. someTypeParameter = NSManagedObject.Type
if let conformingType = someTypeParameter as? InteractivelyNameable.Type {
conformingType.someClassMethodDefinedInProtocol()
}
The static method you wrote isn't generic but protocol as type parameter.
Basically, when you use as a protocol type parameter and not the generic form you force the compiler to use the dynamic dispatcher, ergo, Objective-C.
What you need to do in order to use the statically type dispatcher (Swift):
static func alertViewForNaming<T : Nameable>(_ existingObject: T,
context: NSManagedObjectContext,
completion:#escaping ((_ success: Bool, _ object: T, _ didCancel: Bool, _ error: Error?) -> Void)) -> UIAlertController?
This is, a generic type constraint method and in this case, it's protocol type constraint AKA Nameable.
You invoke the static method as follows:
let test : ObjectThatConformsToNameableProtocol = InteractivelyNameable.alertViewForNaming.....
This way, the compiler can infer the type for the generic type method which in this case, ObjectThatConformsToNameableProtocol.
I didn't test the code but it's important to understand the difference between generics and protocol type parameter.

Can swift generics be used inside class with generics

I have a protocol call Decodable:
public protocol Decodable {
typealias DecodedType = Self
static func decode(e: Extractor) -> DecodedType?
}
I have a function decode:
public func decode<T: Decodable where T.DecodedType == T>(object: AnyObject) -> T? {
Also, I have a class RestApiResponseParser
class RestApiResponseParser<T where T:Decodable> {
private func createServerModels(response: ServerResponse) -> Result<T> {
var parsingError:NSError?
let link: T? = decode(response)
...
}
...
}
and typealias ServerResponse = [String: AnyObject]
which I try to call as:
let responseParser = RestApiResponseParser<MyModel>(responseData: inputObject as! NSData)
let processError = responseParser.processResponseData()
Unfortunately, I get compile time error:
Does it mean, I can't use generic function in generic class?
Yes, you can use a generic function in a generic class. Your problem is that you are passing a [String: AnyObject] dictionary (which is a struct, and not an AnyObject) into a function that is expecting an AnyObject. It looks like you need to extract the AnyObject from the dictionary using its key:
if let object = response["<insert your key here>"] {
decode(object)
}
Another approach would be to cast your ServerResponse to an NSDictionary which is a class, and can therefore be passed as an AnyObject:
let link: T? = decode(response as NSDictionary)
And btw, the bridging between Dictionary and NSDictionary is not as "seemless" as Apple claims - I have run into instances like this where I can't figure out why Swift isn't able to automatically bridge them for me, and when I have to be explicit. There are inconsistencies like the one that you noted. Your function will take a Dictionary as an argument in once place, but not another, without a clear rationale for why. Perhaps it has something to do with the fact that you are using a typealias, but I'm not sure.