Is it possible to have an array of instances that accept a common parameter without knowing (or not caring) what the parameter is?

Consider the following test case that contains the class 'factory', which can cause the closure it contains, providing a new instance of some type of 'defaultable':

protocol Defaultable { init() } extension Int: Defaultable { } extension Double: Defaultable { } extension String: Defaultable { } class Factory<T : Defaultable> { let resultHandler: (T) -> () init(resultHandler: (T) -> ()) { self.resultHandler = resultHandler } func callResultHandler() { resultHandler(T.init()) } } 

Now this works well when I use it myself, where I can track the generic type:

 // Create Int factory variant... let integerFactory = Factory(resultHandler: { (i: Int) in print("The default integer is \(i)") }) // Call factory variant... integerFactory.callResultHandler() 

Unfortunately, this is not so good if I want to use factories in such a way that I cannot track the generic type:

 // Create a queue of factories of some unknown generic type... var factoryQueue = [Factory]() // Add factories to the queue... factoryQueue.append(integerFactory) factoryQueue.append(doubleFactory) factoryQueue.append(stringFactory) // Call the handler for each factory... for factory in factoryQueue { factory.callResultHandler() } 

I understand the error I am getting (the general parameter T cannot be deduced), but I do not understand why I cannot do this, because when I interact with the array, I do not need to know what the general parameter is (I do not interact with one of the common things in a Factory instance). Is there any way to achieve the above?

Note that the above is a simplified example of what I'm trying to do; I’m actually developing a download manager where it can determine what type of file I want (JSON, image, etc.) using generics; instead, the protocol actually contains the init(data:) throws initializer. I want to add load objects to the queue, but I cannot figure out how to add them to the queue due to the general nature of the load objects.

+5
source share
3 answers

I recently returned to the need for a better answer to this question - as I was refactoring - and thought it would be really useful to have common properties of the class, which, of course, would mean that the class itself would also have in common.

I'm not sure why this has not happened before, but I can simply create a protocol that reflects not the general methods of the class. Using the example that I originally had in my question, I could create a FactoryProtocol as follows:

 protocol FactoryProtocol { func callResultHandler() } 

Make the class appropriate to it:

 class Factory<T : Defaultable>: FactoryProtocol 

And then use the protocol, not the class, when I define my array:

 var factoryQueue = [FactoryProtocol]() 

This allows me to add any type of specialized Factory to the array and interact with non-core methods as I like.

+2
source

The problem is that security of the strict Swift type means that you cannot mix two instances of the same class with different common parameters. They are effectively regarded as completely different types.

However, in your case, all you do is pass the closure to the Factory instance, which takes the input T and then calls it at any given time using T.init() . Therefore, you can create a closed system to contain type T , which means that you do not need your general parameter to cover your class. Instead, you can limit it only to the scope of the initializer.

You can do this by specifying resultHandler as a closure of Void->Void and creating it, wrapping the passed closure in the initializer with another closure - and then passing T.init() to the provided closure (providing a new instance is created with every call).

Now, when you call your resultHandler , it will create a new instance of the type that you define in the closure you pass, and pass that instance to the closure.

This does not violate security rules such as Swift, since the result of T.init() is still known due to the explicit input of the closure that you pass. This new instance is then passed to your closure, which has an appropriate input type. Also, since you never pass the result of T.init() to the outside world, you never have to set the type in the definition of the Factory class.

Since your Factory class no longer has a common parameter, you can freely mix different instances.

For instance:

 class Factory { let resultHandler: () -> () init<T:Defaultable>(resultHandler: (T) -> ()) { self.resultHandler = { resultHandler(T.init()) } } func callResultHandler() { resultHandler() } } // Create Int factory variant... let integerFactory = Factory(resultHandler: { (i: Int) in debugPrint(i) }) // Create String factory variant... let stringFactory = Factory(resultHandler: { (i: String) in debugPrint(i) }) // Create a queue of factories of some unknown generic type... var factoryQueue = [Factory]() // Add factories to the queue... factoryQueue.append(integerFactory) factoryQueue.append(stringFactory) // Call the handler for each factory... for factory in factoryQueue { factory.callResultHandler() } // prints: // 0 // "" 

To adapt this to input NSData , you can simply change the closing function resultHandler and callResultHandler() to input NSData . Then you just need to change the wrapped closure in your initializer to use the init(data:) throws initializer, and convert the result to optional or do your own error handling to handle what it might throw.

For instance:

 class Factory { let resultHandler: (NSData) -> () init<T:Defaultable>(resultHandler: (T?) -> ()) { self.resultHandler = {data in resultHandler(try? T.init(data:data)) // do custom error handling here if you wish } } func callResultHandler(data:NSData) { resultHandler(data) } } 
+2
source

I am afraid that this is impossible. The reason for this is because Swift does not have first class metatypes. I can imagine that all kinds of Monads and Functors are being built, if possible. Unfortunately, this is a limitation. Welcome to Swift.

The golden rule is that in Swift you cannot nail a type to a protocol. Swift needs a specific type.

Check this article for more details on this.

0
source

All Articles