Reputation: 651
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
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