Generic delegate in Swift - ios

I'm writing a small class which helps to fetch paged data automatically.
I have following delegate:
protocol DataAPIDelegate: class {
func fetchItemsForPage<T>(api: DataAPI<T>, page: Int, onFinish: ([T] -> ())
}
And DataAPI class, which stores delegate:
class DataAPI<T> {
weak var delegate: DataAPIDelegate? = nil
//...
//some other methods
}
Then, what I want to do is to write a view controller which uses DataAPI and communicates with this object by DataAPIDelegate:
class CarsViewController: DataAPIDelegate {
var dataAPI = DataAPI<Car>
// MARK :- DataAPIDelegate
func fetchItemsForPage<T>(api: DataAPI<T>, page: Int, onFinish: ([T] -> ()) {
let cars: [Car] = //some method for getting cars
onFinish(cars)
}
}
And I get the error: Cannot invoke 'onFinish' with an argument list of type '([Car])' in the line: onFinish(cars).
I think about it for a few hours and I don't have any idea why it doesn't work. Anyone meets that problem?

Generic functions provide single template implementation for multiple types referred by T, while what it seems you are trying to do is to provide an implementation of fetchItemsForPage method specifically for Car type.
The way it would work is:
protocol DataAPIDelegate: class {
func fetchItemsForPage<T>(api: DataAPI<T>, page: Int, onFinish: ([T] -> ()))
}
class DataAPI<T> {
weak var delegate: DataAPIDelegate? = nil
//...
//some other methods
}
struct Car { }
class CarsViewController: DataAPIDelegate {
var dataAPI = DataAPI<Car>()
// MARK :- DataAPIDelegate
func fetchItemsForPage<T>(api: DataAPI<T>, page: Int, onFinish: ([T] -> ())) {
let items: [T] = [T]()
onFinish(items)
}
}
... while what you are trying to do is:
struct Bike { }
class BikesViewController: DataAPIDelegate {
var dataAPI = DataAPI<Bike>()
// MARK :- DataAPIDelegate
func fetchItemsForPage<Bike>(api: DataAPI<Bike>, page: Int, onFinish: ([Bike] -> ())) {
let items: [Bike] = [Bike]()
onFinish(items)
}
}
... but in the latter snippet Bike is not a type but a placeholder, which works just the same as if you would use T.

Generic delegates aren't possible in Swift, if they aren't just functions. Because functions (closures) can be generic. See my gist where I tried to resolve the same problem: https://gist.github.com/artyom-razinov/fe8c2b7611a0ba3bd2a3

Related

Call a method with dynamic class name in swift

How can we call class functions with a dynamic class name?
Assume the following example where I have two class with methods with same signature
class Foo{
class func doSomething()
}
class Foobar {
class func doSomething()
}
class ActualWork{
//call following method with a variable type so that it accepts dynamic class name
func callDynamicClassMethod(x: dynamicClass)
x.doSomething()
}
How can this be implemented so that x accepts values at run time
Edit: Sorry, I missed to mention that I was looking for any other ways other than protocol oriented approach. This is more of an exploratory question to explore if there is a more direct approach/pods/libraries to achieve this.
I liked this question, because it made me to think a lit'bit outside of the box.
I'll answer it, by dividing it into a few parts.
First
call class functions
Class function is basically a Type methods, which can be achieved using the static word inside the class context.
Taking that into account, you can get a simple solution, using protocol and passing the class reference (conforming to that protocol) like this:
protocol Aaa{
static func doSomething();
}
class Foo : Aaa{
static func doSomething() {
print("Foo doing something");
}
}
class FooBar : Aaa{
static func doSomething() {
print("FooBar doing something");
}
}
class ActualWork{
//Using class (static) method
func callDynamicClassMethod <T: Aaa> (x: T.Type) {
x.doSomething();
}
}
//This is how you can use it
func usage(){
let aw = ActualWork();
aw.callDynamicClassMethod(x: Foo.self);
aw.callDynamicClassMethod(x: Foo.self);
}
Second
In case you don't really need the method on the class context, you may consider using instance methods. In that case the solution would be even simpler, like this:
protocol Bbb{
func doSomething();
}
class Bar : Bbb{
func doSomething() {
print("Bar instance doing something");
}
}
class BarBar : Bbb{
func doSomething() {
print("BarBar instance doing something");
}
}
class ActualWork{
//Using instance (non-static) method
func callDynamicInstanceMethod <T: Bbb> (x: T){
x.doSomething();
}
}
//This is how you can use it
func usage(){
let aw = ActualWork();
aw.callDynamicInstanceMethod(x: Bar());
aw.callDynamicInstanceMethod(x: BarBar());
}
Third
If you need to use the class func syntax, as OP originally did:
class func doSomething()
You CANNOT simply use a protocol. Because protocol is not a class...
So compiler won't allow it.
But it's still possible, you can achieve that by using
Selector with NSObject.perform method
like this:
class ActualWork : NSObject{
func callDynamicClassMethod<T: NSObject>(x: T.Type, methodName: String){
x.perform(Selector(methodName));
}
}
class Ccc : NSObject{
#objc class func doSomething(){
print("Ccc class Doing something ");
}
}
class Ddd : NSObject{
#objc class func doSomething(){
print("Ccc class Doing something ");
}
#objc class func doOther(){
print("Ccc class Doing something ");
}
}
//This is how you can use it
func usage() {
let aw = ActualWork();
aw.callDynamicClassMethod(x: Ccc.self, methodName: "doSomething");
aw.callDynamicClassMethod(x: Ddd.self, methodName: "doSomething");
aw.callDynamicClassMethod(x: Ddd.self, methodName: "doOther");
}
Generics and Protocol oriented programming will do the job:
protocol Doable {
static func doSomething()
}
class Foo: Doable {
static func doSomething() {
debugPrint("Foo")
}
}
class Foobar: Doable {
static func doSomething() {
debugPrint("Foobar")
}
}
class ActualWork {
func callDynamicClassMethod<T: Doable>(x: T.Type) {
x.doSomething()
}
}
let work = ActualWork()
work.callDynamicClassMethod(x: Foo.self)
work.callDynamicClassMethod(x: Foobar.self)
you can achieve this with help of Protocol
protocol common {
static func doSomething()
}
class Foo : common{
static func doSomething() {
print("Foo")
}
}
class Foobar : common {
static func doSomething() {
print("Foobar")
}
}
class ActualWork{
//call following method with a variable type so that it accepts dynamic class name
func callDynamicClassMethod(x: common.Type) {
x.doSomething()
}
}
let fooObj : common = Foo()
let Foobarobj : common = Foobar()
let workObk = ActualWork()
workObk.callDynamicClassMethod(x:Foo.self)
workObk.callDynamicClassMethod(x:Foobar.self)
I think, there are three solutions. I shared an sample below.
Use "protocol" that has "doSomething()" function requirements.
Create a function which gets function definition as a parameter.
Use reflection. you can use EVReflection that is good Api for reflection.
sample code:
protocol FooProtocol {
static func doSomething()
}
class Foo: FooProtocol {
class func doSomething() {
print("Foo:doSomething")
}
}
class Foobar: FooProtocol {
class func doSomething() {
print("Foobar:doSomething")
}
}
class ActualWork {
func callDynamicClassMethod<T: FooProtocol>(x: T.Type) {
x.doSomething()
}
func callDynamicClassMethod(x: #autoclosure () -> Void) {
x()
}
func callDynamicClassMethod(x: () -> Void) {
x()
}
}
ActualWork().callDynamicClassMethod(x: Foo.self)
ActualWork().callDynamicClassMethod(x: Foobar.self)
print("\n")
ActualWork().callDynamicClassMethod(x: Foo.doSomething())
ActualWork().callDynamicClassMethod(x: Foobar.doSomething())
print("\n")
ActualWork().callDynamicClassMethod(x: Foo.doSomething)
ActualWork().callDynamicClassMethod(x: Foobar.doSomething)
Looks like you are searching for duck typing, and this is harder to achieve in a statically typed language (with some exceptions, listed in the linked Wikipedia page).
This is because dynamically calling a method requires knowledge about the layout of the target object, thus either inheritance of the class declaring the method, or conformance to a protocol that requires that method.
Starting with Swift 4.2, and the introduction of dynamic member lookup, there is another approach to solve your problem, however it also involves some ceremony:
// This needs to be used as base of all classes that you want to pass
// as arguments
#dynamicMemberLookup
class BaseDynamicClass {
subscript(dynamicMember member: String) -> () -> Void {
return { /* empty closure do nothing */ }
}
}
// subclasses can choose to respond to member queries any way they like
class Foo: BaseDynamicClass {
override subscript(dynamicMember member: String) -> () -> Void {
if member == "doSomething" { return doSomething }
return super[dynamicMember: member]
}
func doSomething() {
print("Dynamic from Foo")
}
}
class Bar: BaseDynamicClass {
override subscript(dynamicMember member: String) -> () -> Void {
if member == "doSomething" { return doSomething }
return super[dynamicMember: member]
}
func doSomething() {
print("Dynamic from Bar")
}
}
func test(receiver: BaseDynamicClass) {
receiver.doSomething()
}
test(receiver: Bar()) // Dynamic from Bar
To conclude, in the current Swift version there is no way to have both the argument and the method dynamic, some common ground needs to be set.

Extension of a protocol where the associatedType is class?

I tried something like this:
protocol MyModelProtocol {
var name: String { get set }
}
protocol MyProtocol {
associatedtype Model: MyModelProtocol
func changeModel(_ model: Model)
}
extension MyProtocol where Model: AnyObject {
}
The compiler goes happy. However, inside this extension the compiler still isn't sure if Model is class or struct. Example:
extension MyProtocol where Model: AnyObject {
func changeModel(_ model: Model) {
model.name = "changed"
}
}
Thus, I get the error: "Cannot assign to property: 'model' is a 'let' constant"
How can I tell the compiler that in that protocol extension the associated type will always be a class?
Btw, this is just a short example. I know I could use an inout parameter in this case, but it doesn't work for me because I want to change the object inside of an async callback like so:
func changeModel(_ model: inout Model, completion: #escaping () -> Void) {
Api.shared.doRandomAsyncStuff() { (_) in
model.name = "changed"
completion()
}
}
And trying to do this leads me to the error: "Escaping closures can only capture inout parameters explicitly by value".
You could just an add intermediate assignment to a var. For a class/reference type, this would have the same effect as setting a property on the original reference. For a struct type it would make a copy, which wouldn't work, but should be avoided by the constraint on the extension.
func changeModel(_ model: Model, completion: #escaping () -> Void) {
var modelRef = model
Api.shared.doRandomAsyncStuff() { (_) in
modelRef.name = "changed"
completion()
}
}

Pass Swift protocol as argument of another protocol function

I am creating generic data structure for my API result handling, reading about it online I found out that it would be best to use protocols and associatedtypes. This is my implementation so far.
protocol CreateDataCallback {
associatedtype E
func onSuccess(e:E) -> Void
func onFail() -> Void
}
protocol DataSource: class {
associatedtype T
func getData<GDC:GetDataCallback>(id:ID, callback:GDC) -> Void
}
As you can see in the code snippet, my getData function is not written correctly. Problem is that I don't know how to pass 'associatedtype T' from DataSource protocol to 'associatedtype E' of the CreateDataCallback protocol. I could write an extension of protocol DataSource but then it wouldn't be generic. Is this even possible in swift (I now that it is possible in Java), and if it is can you please shown me how. Thx
I'd make GDC an associated type of DataSourceand I'd make GDC conform to CreateDataCallBack. Then you don't need associated type T, you can just refer to GDC.E
protocol DataSource: class{
associatedtype GDC: CreateDataCallback
func getData(id:GDC.E, callback:GDC) -> Void
}
Here's some implementations of the two protocols:
class Bar: CreateDataCallback
{
func onSuccess(e: Int) {
// code
}
func onFail() {
// code
}
typealias E = Int
}
class Foo: DataSource
{
typealias GDC = Bar // This makes GDC.E a typealias for Int
func getData(id: Int, callback: Bar) {
// code
}
}

How can I add an OR generic type constraint on a function that accepts Any

I have an initializer that accepts a variadic parameter, but I want the arguments to be only one of two types:
A custom class, say MyCustomNSOperation
A tuple of (MyCustomNSOperation, () -> Bool)
How can I achieve that in Swift 2.0? I currently have my initializer written like this but I think it's too permissive:
init(items: Any ...) {
}
And somewhere in the class, I iterate through all of the items, check their types, and if it's not one of the two types I want to restrict to, I throw a fatalError.
for i in 0..<self.items.count {
guard self.items[i] is MyCustomNSOperation || self.items[i] is (MyCustomNSOperation, () -> Bool) else {
fatalError("Found unrecognised type \(self.items[i]) in the operation chain")
}
}
If it is, I execute one of the two overloaded versions of another function.
I've also looked at protocol compositions too but the type constraint logic enforced there is an AND, not an OR (i.e., the item has to conform to both types, not just one of them).
I would just abstract these objects into a protocol and use that in your class, and use a struct instead of a tuple:
protocol MyItem {
func doSomething()
}
class MyCustomNSOperation: NSOperation, MyItem {
func doSomething() {
print( "MyCustomNSOperation is doing something..." )
}
}
struct OperationWithClosure: MyItem {
let operation: MyCustomNSOperation
let closure: () -> Bool
func doSomething() {
print( "OperationWithClosure is doing something..." )
}
}
class MyClass {
let items: [MyItem]
init(items: MyItem...) {
self.items = items
}
func doSomethingWithItems() {
for item in items {
item.doSomething()
}
}
}

Implement delegate using generic protocol in Swift

I'm trying to create a delegate protocol that implements a function which passes an array of a generic type. I've tried several combinations but none of them seem to do the trick.
This is the most approximate thing i've reached to. This is the protocol:
protocol APIControllerProtocol {
typealias T
func didReceiveAPIResults(results: [T])
}
And this is the the delegator object:
class APIController<U:APIControllerProtocol> {
typealias ElementType = U
var delegate: ElementType?
init(delegate: ElementType){
self.delegate = delegate
}
func getAPIResults(){
// Perform some action before delegation
// "results" is an Array of dictionaries got from NSJSONSerialization
self.delegate?.didReceiveAPIResults(results.map{dict in Album(json:dict)})
}
}
However, the last line get this error: "Album is not convertible to U.T"
"Album" is the model object used to return the results.
What am i doing wrong?
EDIT:
Following Mike S advice, i've made the protocol method didReceiveAPIResults a generic function, and specified what T is in the delegate. However, when receiving and assigning the argument of type T to a property in the delegate, i get the error: "T is not identical to T"
class TestDelegate: APIControllerProtocol {
typealias T = Album
var albums:[T] = [T]()
func didReceiveAPIResults<T>(results: [T]) {
// ...
self.albums = results //ERROR: "T is not identical to T"
}
}
Your didReceiveAPIResults declaration in APIControllerProtocol needs to be a generic function so that the generic type T is passed along to it correctly.
protocol APIControllerProtocol {
typealias T
func didReceiveAPIResults<T>(results: [T])
}
Note: This means your delegate definition will need to define what T is:
class TestDelegate: APIControllerProtocol {
typealias T = Album
func didReceiveAPIResults<T>(results: [T]) {
// ...
}
}
Update: While the code above does get rid of the original error, it turns out that it acts more like a workaround and doesn't really address the root of the problem.
The real issue seems to be that the compiler is having trouble reconciling what U.T is with no ambiguity. That's actually easy enough to fix though, we just need to give it a more precise definition (note the where clause in the APIController definition):
protocol APIControllerProtocol {
typealias T
func didReceiveAPIResults(results: [T])
}
class APIController<U:APIControllerProtocol where U.T == Album> {
typealias ElementType = U
// ...
}
Note: I removed the <T> that I added to the function in the protocol previously; that's not needed anymore and will end up causing problems later.
With that, the TestDelegate class works as expected (you don't even need the typealias anymore):
class TestDelegate: APIControllerProtocol {
var albums: [Album]? = nil
func didReceiveAPIResults(results: [Album]) {
albums = results
}
}

Resources