YonF
YonF

Reputation: 651

swift how to define abstract class and why apple invent associated type but not use generic protocol

I am a swift beginner. Something puzzled me when learning. Now I want to define an abstract class or define some pure virtual method, but I cannot find a way to do it. I have a protocol with associated type(this also puzzled me, why not use generic protocol), and some methods need to be implemented in a base class, and other classes inherited from the base class, they should implement other methods in the protocol, how can I do? for instance:

Protocol P{
    typealias TypeParam
    func A()
    func B()
}

class BaseClass<TypeParam> : P {
    abstract func A()
    func B(){
        if someCondition {
            A()
        }
    }
}

class ChildClass : BaseClass<Int> {
    func A(){}
}

It seems very strange, and I still cannot find a method to resolve the abstract problem.

Upvotes: 1

Views: 1905

Answers (1)

Qbyte
Qbyte

Reputation: 13243

Swift has something similar: protocol extensions

They can define default implementations so you don't have to declare the method in your base class but it also doesn't force to do that in any class, struct or enum.

protocol P {
    associatedtype TypeParameter
    func A()
    func B()
}

extension P {
    func A(){}
}

class BaseClass<TypeParam> : P {
    typealias TypeParameter = TypeParam
    func B(){
        if someCondition {
            A()
        }
    }
}

class ChildClass : BaseClass<Int> {
    // implementation of A() is not forced since it has a default implementation
    func A(){}
}

Another approach would be to use a protocol instead of BaseClass which is more in line with protocol oriented programming:

protocol Base {
    associatedtype TypeParameter
    func A()
    func B()
}

extension Base {
    func B(){
        if someCondition {
            A()
        }
    }
}

class ChildClass : Base {
    typealias TypeParameter = Int

    // implementation of A() is forced but B() is not forced
    func A(){}
}

However one of the big disadvantages would be that a variable of protocol type can only be used in generic code (as generic constraint):

var base: Base = ChildClass() // DISALLOWED in every scope

As a workaround for this limitation you can make a wrapper type:

// wrapper type
struct AnyBase<T>: Base {
    typealias TypeParameter = T
    let a: () -> ()
    let b: () -> ()
    init<B: Base>(_ base: B) where B.TypeParameter == T {
        // methods are passed by reference and could lead to reference cycles
        // below is a more sophisticated way to solve also this problem
        a = base.A
        b = base.B
    }
    func A() { a() }
    func B() { b() }
}

// using the wrapper:
var base = AnyBase(ChildClass()) // is of type AnyBase<Int>

Regarding the use of "true" generic protocols, the Swift team has chosen to use associatedtype because you can use many generic types without having to write all out in brackets <>.

For example Collection where you have an associated Iterator and Index type. This allows you to have specific iterators (e.g. for Dictionary and Array).

In general, generic/associated types are good for code optimization during compilation but at the same time being sometimes too static where you would have to use a generic wrapper type.

A useful link to some patterns for working with associated types.


(See also above)

A more sophisticated way to solve the problem of passing the methods by reference.

// same as `Base` but without any associated types
protocol _Base {
    func A()
    func B()
}

// used to store the concrete type
// or if possible let `Base` inherit from `_Base`
// (Note: `extension Base: _Base {}` is currently not possible)
struct BaseBox<B: Base>: _Base {
    var base: B
    init(_ b: B) { base = b}
    func A() { base.A() }
    func B() { base.B() }
}

struct AnyBase2<T>: Base {
    typealias TypeParameter = T
    var base: _Base
    init<B: Base>(_ base: B) where B.TypeParameter == T {
        self.base = BaseBox(base)
    }
    func A() { base.A() }
    func B() { base.B() }
}

// using the wrapper:
var base2 = AnyBase2(ChildClass()) // is of type AnyBase2<Int>

Upvotes: 6

Related Questions