I've been trying to implement a protocol and protocol extension that provides a default == method in swift. Something like this:
protocol NameProtocol: Equatable {
func getName() -> String
}
extension NameProtocol{}
func ==<T: NameProtocol>(lhs: T, rhs: T) -> Bool{
return lhs.getName() == rhs.getName()
}
Then a class like this:
class NamedObject: NSObject, NameProtocol {
let name: String
init(name: String) {
self.name = name
super.init()
}
func getName() -> String {
return self.name
}
}
However, the custom == method is never called:
let one = NamedObject(name: "Name")
let two = NamedObject(name: "Name")
if one == two {
NSLog("EQUAL")
}
else{
NSLog("NOT EQUAL")
}
Am I doing something wrong here?
UPDATE:
From the answers I got it looks like what i'm trying to accomplish isn't really possible. The only way to come close is to subclass (which has its obvious drawbacks). I'm going to keep a lookout for a proper solution.
Because the == operator from the superclass takes precedence over that of the protocol. And for NSObject, == means pointer equal.
If you remove the inheritance from NSObject, it works as expected:
class NamedObject: NameProtocol {
let name: String
init(name: String) {
self.name = name
super.init()
}
func getName() -> String {
return self.name
}
}
I can't find any documentation on the order of precedence when there are multiple implementations for ==. This is just my experience.
Edit: instead of defining == for the protocol, define your own base class to override NSObject's default behavior:
class MyBaseClass: NSObject {
func getName() -> String {
fatalError("You must override this method")
}
}
func == (lhs: MyBaseClass, rhs: MyBaseClass) -> Bool {
return lhs.getName() == rhs.getName()
}
class NamedObject: MyBaseClass {
let name: String
init(name: String) {
self.name = name
}
override func getName() -> String {
return self.name
}
}
Your class derives from NSObject. You override isEqual: (not Swift ==) to change an NSObject-derived class's idea of what instance equality means.
Your strategy to move this off to a protocol extension, however, can never work because Objective-C knows nothing of Swift protocol extensions. Your code sitting in the protocol extension is completely invisible to Objective-C.
So, basically, by making this class derive from NSObject, you've shot your protocol extension strategy in the foot.
Related
How to make a struct/class confirm to H.ashable/Equatable that has an instance variable of a protocol type. Let me provide an example of this:
protocol Filter {
associatedtype Input
func satisfies(input: Input) -> Bool
}
protocol FilterProcessor {
func satisfies(input: String) -> Bool
}
struct SelectionFilter: Filter {
let identifier: String
let processor: FilterProcessor
func satisfies(input: String) -> Bool {
return processor.satisfies(input: input)
}
}
struct ProcessorOne: FilterProcessor {
let uuid: String
let type: String
func satisfies(input: String) -> Bool {
// Some logic based on uuid and type
return false
}
}
struct ProcessorTwo: FilterProcessor {
let data: Int
func satisfies(input: String) -> Bool {
// Some logic based on data
return false
}
}
This works fine as long as you don't need Hashable/Equatable confirmance on SelectionFilter but things quickly get very complicated as soon as you do, like try to make a Set of SelectionFilter:
Set([
SelectionFilter(identifier: "abc", processor: ProcessorOne(uuid: "1234", type: "size")),
SelectionFilter(identifier: "def", processor: ProcessorTwo(data: 1234))
])
For the above to work SelectionFilter needs to be Hashable. We can do that taking only identifier into account but that would be wrong as actual hashing/equality depends upon the processor's data also. For that FilterProcessor needs to be Hashable and as soon as we do that the SelectionFilter can't use the FilterProcessor directly as it is now a generic protocol.
One solution is to use a type eraser for the FilterProcessor as AnyFilterProcessor, but then this AnyFilterProcessor needs to confirm Hashable. How do you do that since all this AnyFilterProcessor will have is a closure that points back to the actual satisfies method.
So there are two alternatives I could make one is to make SelectionFilter an enum and the other is to eliminate the FilterProcessor and make SelectionFilter a class and other subclasses:
Option1:
enumm SelectionFilter: Filter, Hashable {
case filterOne(identifier: String, processor: ProcessorOne)
case filterTwo(identifier: String, processor: ProcessorTwo)
}
extension ProcessorOne: Hashable {}
extension ProcessorTwo: Hashable {}
This works neatly but the problem is that enums are not scalable. We have some 20 odd processors, which means 20 cases and for doing anything we need to switch all 20 cases.
Option2:
class SelectionFilter: Filter, Hashable {
let identifier: String
init(identifier: String) {
self.identifier = identifier
}
func satisfies(input: String) -> Bool {
return true
}
static func == (lhs: SelFilter, rhs: SelFilter) -> Bool {
lhs.identifier == rhs.identifier
}
func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
}
class FilterOne: SelectionFilter {
let uuid: String
let type: String
init(identifier: String, uuid: String, type: String) {
self.uuid = uuid
self.type = type
super.init(identifier: identifier)
}
override func satisfies(input: String) -> Bool {
return true
}
}
But the issue here is a lot of duplicate code as each SelectionFilter subclass will need to implement == and hash functions. Also, it is error-prone as any developer might forget to implement these functions in a subclass.
Both of these options work but I am really not liking them. Is there is another way to do it?
I have a protocol that has a function that can return a String or a [String: String]. This is my declaration:
protocol Test {
associatedtype T: Hashable
func returnSomething() -> T
}
Then I want a default implementation for returnSomething, so I made a protocol extension:
extension Test {
func returnSomething() -> T {
let valueToReturn = readValueFromPLISTthatCanReturnAStringOrDictionary() as T
return valueToReturn
}
}
So finally I have 2 clases, TestString and TestDictionary that both implements Test protocol and I want to indicate the T parameter and I want to use the default implementation. How I do this?
class TestString: Test {}
class TestDictionary: Test { }
class TestString: Test where Test.T = String or similar?
I have a protocol that has a function that can return a String or a [String: String]. This is my declaration:
No problem. Let's write that down.
enum StringOrDictionary {
case string(String)
case dictionary([String: String])
}
protocol Test {
func returnSomething() -> StringOrDictionary
}
Then I want a default implementation for returnSomething, so I made a protocol extension:
Sounds good. I'll assume that readValueFromPLISTthatCanReturnAStringOrDictionary() actually returns Any, since that's what is returned by propertyList(from:).
extension Test {
func returnSomething() -> StringOrDictionary {
let value = readValueFromPLISTthatCanReturnAStringOrDictionary()
switch value {
case let string as String: return .string(string)
case let dictionary as [String: String]: return .dictionary(dictionary)
default: fatalError() // Or perhaps you'd like to do something else
}
}
}
It'd probably be nice to name your type something more meaningful than StringOrDictionary, but other than that, it should be pretty straightforward. Just make a type that means what you say. You want a type that means "OR" and that is an enum. (If you want a type that means "AND" that's a struct BTW.)
Regarding your answer, this isn't legal:
class RandomClass: Test where Test.T == String {
func getValue() {
let bah = doSomething() // I don't need here to specify bah's type.
}
}
The way to define your T is to implement the required method.
class RandomClass: Test {
func returnSomething() -> String {
return ""
}
}
If you wanted to share some common code, then you can attach that as an extension rather than a default implementation. You could write a returnString() method and call it from the RandomClass.returnSomething(). This is all very useful in some cases, but I definitely wouldn't use it in this case. You don't mean "returns any possible type (T)." You mean "returns one of two possible types" and that's an enum, not a generic.
Update: Apparently they've added a new feature that they've talked about but I thought wasn't in yet. You could now implement RandomClass this way:
class RandomClass: Test {
typealias T = String
}
(Which is a very nice new feature, even if it's not a good answer for this problem.)
Here's a solution to your immediate problem:
Create 2 subtypes of your protocol, each with a different definition of the associated type, and a different default implementation. You select which default implementation you'd like your classes to use by picking between the 2 sub types.
The next issue here is that [String: String] isn't Hashable. This is due to a lack of support for conditional conformances (e.g. the ability to express that a Dictionary is Hashable iff the keys and values are both Hashable), one of Swift's largest downfalls, IMO. You'll probably want to use the type erasing wrapper AnyHashable.
protocol ResultProvider {
associatedtype Result: Hashable
func getResult() -> Result
}
protocol StringResultProvider: ResultProvider {
typealias Result = String
}
extension StringResultProvider {
func getResult() -> String {
return "A string result"
}
}
protocol IntResultProvider: ResultProvider {
typealias Result = Int
}
extension IntResultProvider {
func getResult() -> Int {
return 123
}
}
class TestIntResult: IntResultProvider {}
class TestString: StringResultProvider {}
print(TestString().getResult())
print(TestIntResult().getResult())
// protocol DictionaryResultProvider: ResultProvider {
// typealias Result = [String: String]
// }
// extension DictionaryResultProvider {
// func getResult() -> [String: String] {
// return ["A dictionary": "result"]
// }
// }
// class TestDictionaryProvider: DictionaryResultProvider {}
You need to specify the typealias when you extend the class, like so:
protocol Test {
associatedtype T: Hashable
func returnSomething() -> T
}
extension String: Test {
typealias T = Int
}
func def() -> Int {
return 6
}
extension Test {
func returnSomething() -> T {
return def() as! Self.T
}
}
"".returnSomething()
6
However, I couldn't find a way to do it without force casting.
The only working solution is made the generic in the function and specify the variable type when calling the function. I was wondering if i could specify the T type when i implement the protocol in the class, similar like this:
class RandomClass: Test where Test.T == String {
func getValue() {
let bah = doSomething() // I don't need here to specify bah's type.
}
}
But previous example just don't work, so an alternative could be this:
protocol Test {
func doSomething<T>() -> T
}
extension Test {
func doSomething<T>(key: String) -> T {
return returnDictOrStringFromPLIST(key: key) as! T
}
}
class TestString: Test {
func getValue() {
let bah: String = doSomething()
}
}
class TestDict: Test {
func getValue() {
let bah: [String: String] = doSomething()
}
}
I am trying to extend my protocol Option with Comparable to use simple .sort() method.
Below short example only with Equatable to show errors.
#objc protocol Option: Equatable {
var title: String { get }
var enabled: Bool { get }
var position: Int { get }
}
func ==(lhs: Option, rhs: Option) -> Bool {
return lhs.position == rhs.position
}
The Option protocol must be marked as #objc or inherit from NSObjectProtocol because it will be used with UIKit.
Errors:
#objc protocol 'Option' cannot refine non-#objc protocol
'Equatable'
Protocol 'Option' can only be used as a generic constraint
because it has Self or associated type requirements
Do you have any suggestion how to solve this problem?
Equatable lives in the Swift world only, thus you cannot extend it to a protocol that will be used by Objective-C. Trying to do this results in error #1
Protocols that have a Self requirement (i.e. at least one method from the protocol declaration contains Self) cannot be used as arguments to functions, or to variable declarations, only as arguments to a generic clause, e.g. func doSomething<T: Option>(argument: T).
Removing Equatable from the Option protocol declaration, and declaring == as generic on Option will solve the compile errors. As for sorting, you can also overload the < operator, and sort via that operator (without needing to implement Comparable):
#objc protocol Option {
var title: String { get }
var enabled: Bool { get }
var position: Int { get }
}
func ==<T: Option>(lhs: T, rhs: T) -> Bool {
return lhs.position == rhs.position
}
func <<T: Option>(lhs: T, rhs: T) -> Bool {
return lhs.position < rhs.position
}
This allows you to pass objects that conform to the protocol to UIKit, and to also compare them within your swift code.
class A: NSObject, Option { .. }
class B: NSObject, Option { ... }
let a = A()
let b = B()
a == b // compiles, and returns true if a and b have the same position
let c: [Option] = [a, b]
c.sort(<) // returns a sorted array by the `position` field
One important note regarding the sorting code above: if you don't specify the type for c, then the compiler infers its type as [NSObject], and the sort call will not compile due to ambiguity of the < operator. You need to explicitly declare c as [Option] to take advantage of the overloaded operator.
The issue can be fixed by the new protocol oriented programming features introduced in swift 2.0
#objc protocol 'Option' cannot refine non-#objc protocol 'Equatable'
As the error states, the Equatable protocol is a swift protocol that you can't to Obj C context
Protocol 'Option' can only be used as a generic constraint because it
has Self or associated type requirements
You can achieve this in the following way:
#objc protocol Option {
var title: String { get }
var enabled: Bool { get }
var position: Int { get }
}
extension Equatable where Self : Option
{
}
extension Comparable where Self : Option
{
}
func ==(lhs: Option, rhs: Option) -> Bool
{
return lhs.position == rhs.position
}
func <(lhs: Option, rhs: Option) -> Bool
{
return lhs.position < rhs.position
}
func >(lhs: Option, rhs: Option) -> Bool
{
return lhs.position > rhs.position
}
And your class and implementation looks like:
class MyClass: Option
{
#objc var title: String = ""
#objc var enabled: Bool = true
#objc var position: Int = 0
init()
{
}
convenience init(title : String, enabled : Bool, position: Int)
{
self.init()
self.title = title
self.enabled = enabled
self.position = position
}
}
let firstObj = MyClass()
let secondObj = MyClass()
let optionArray : [Option] = [firstObj, secondObj]
// Sort array of options
optionArray.sort(<)
I have an issue with a protocol I've defined below. I've got two requirements:
I'd like to be able to use the protocol Peer as a type in other classes while keeping the concrete class private.
I'd like to store the protocol in an array and be able to determine the index of an instance.
In order to satisfy the second point, I need to make the protocol conform to the Equatable protocol. But when I do that, I can no longer use Peer as a type, since it needs to be treated as a generic. This means I cannot have the concrete implementation private anymore, and requirement 1 is broken.
Wondering if anyone else has encountered this problem and gotten around it somehow. Maybe I'm misinterpreting the error I'm getting at indexOf...
Group.swift
import Foundation
class Group {
var peers = [Peer]()
init() {
peers.append(PeerFactory.buildPeer("Buddy"))
}
func findPeer(peer: Peer) -> Bool {
if let index = peers.indexOf(peer) {
return true
}
return false
}
}
Peer.swift
import Foundation
protocol Peer {
var name: String { get }
}
class PeerFactory {
static func buildPeer(name: String) -> Peer {
return SimplePeer(name: name)
}
}
private class SimplePeer: Peer {
let name: String
init(name: String) {
self.name = name
}
}
Error at indexOf if Peer is not Equatable:
cannot convert value of type 'Peer' to expected argument type '#noescape (Peer) throws -> Bool'
So I found a solution to get around the Equatable requirement by extending CollectionType to define a new indexOf for elements are of Peer type, which takes advantage of the other closure-based indexOf. This is essentially a convenience function which saves me from using the closure indexOf directly. Code below:
extension CollectionType where Generator.Element == Peer {
func indexOf(element: Generator.Element) -> Index? {
return indexOf({ $0.name == element.name })
}
}
This of course assumes everything I need to test equality can be obtained from the Peer protocol (which is true for my specific use case).
EDIT: Update for Swift 3:
extension Collection where Iterator.Element == Peer {
func indexOf(element: Iterator.Element) -> Index? {
return index(where: { $0.name == element.name })
}
}
I would suggest you use public super class, so the class can conform to Equatable
class Peer: Equatable {
// Read-only computed property so you can override.
// If no need to override, you can simply declare a stored property
var name: String {
get {
fatalError("Should not call Base")
}
}
// should only be called from subclass
private init() {}
}
private class SimplePeer: Peer {
override var name: String {
get {
return _name
}
}
let _name: String
init(name: String) {
_name = name
super.init()
}
}
func == (lhs: Peer, rhs: Peer) -> Bool {
return lhs.name == rhs.name
}
class PeerFactory {
static func buildPeer(name: String) -> Peer {
return SimplePeer(name: name)
}
}
This is a relatively common design pattern:
https://stackoverflow.com/a/17015041/743957
It allows you to return a subclass from your init calls.
I'm trying to figure out the best method of achieving the same thing using Swift.
I do know that it is very likely that there is a better method of achieving the same thing with Swift. However, my class is going to be initialized by an existing Obj-C library which I don't have control over. So it does need to work this way and be callable from Obj-C.
Any pointers would be very much appreciated.
I don't believe that this pattern can be directly supported in Swift, because initialisers do not return a value as they do in Objective C - so you do not get an opportunity to return an alternate object instance.
You can use a type method as an object factory - a fairly contrived example is -
class Vehicle
{
var wheels: Int? {
get {
return nil
}
}
class func vehicleFactory(wheels:Int) -> Vehicle
{
var retVal:Vehicle
if (wheels == 4) {
retVal=Car()
}
else if (wheels == 18) {
retVal=Truck()
}
else {
retVal=Vehicle()
}
return retVal
}
}
class Car:Vehicle
{
override var wheels: Int {
get {
return 4
}
}
}
class Truck:Vehicle
{
override var wheels: Int {
get {
return 18
}
}
}
main.swift
let c=Vehicle.vehicleFactory(4) // c is a Car
println(c.wheels) // outputs 4
let t=Vehicle.vehicleFactory(18) // t is a truck
println(t.wheels) // outputs 18
The "swifty" way of creating class clusters would actually be to expose a protocol instead of a base class.
Apparently the compiler forbids static functions on protocols or protocol extensions.
Until e.g. https://github.com/apple/swift-evolution/pull/247 (factory initializers) is accepted and implemented, the only way I could find to do this is the following:
import Foundation
protocol Building {
func numberOfFloors() -> Int
}
func createBuilding(numberOfFloors numFloors: Int) -> Building? {
switch numFloors {
case 1...4:
return SmallBuilding(numberOfFloors: numFloors)
case 5...20:
return BigBuilding(numberOfFloors: numFloors)
case 21...200:
return SkyScraper(numberOfFloors: numFloors)
default:
return nil
}
}
private class BaseBuilding: Building {
let numFloors: Int
init(numberOfFloors:Int) {
self.numFloors = numberOfFloors
}
func numberOfFloors() -> Int {
return self.numFloors
}
}
private class SmallBuilding: BaseBuilding {
}
private class BigBuilding: BaseBuilding {
}
private class SkyScraper: BaseBuilding {
}
.
// this sadly does not work as static functions are not allowed on protocols.
//let skyscraper = Building.create(numberOfFloors: 200)
//let bigBuilding = Building.create(numberOfFloors: 15)
//let smallBuilding = Building.create(numberOfFloors: 2)
// Workaround:
let skyscraper = createBuilding(numberOfFloors: 200)
let bigBuilding = createBuilding(numberOfFloors: 15)
let smallBuilding = createBuilding(numberOfFloors: 2)
Since init() doesn't return values like -init does in Objective C, using a factory method seems like the easiest option.
One trick is to mark your initializers as private, like this:
class Person : CustomStringConvertible {
static func person(age: UInt) -> Person {
if age < 18 {
return ChildPerson(age)
}
else {
return AdultPerson(age)
}
}
let age: UInt
var description: String { return "" }
private init(_ age: UInt) {
self.age = age
}
}
extension Person {
class ChildPerson : Person {
let toyCount: UInt
private override init(_ age: UInt) {
self.toyCount = 5
super.init(age)
}
override var description: String {
return "\(self.dynamicType): I'm \(age). I have \(toyCount) toys!"
}
}
class AdultPerson : Person {
let beerCount: UInt
private override init(_ age: UInt) {
self.beerCount = 99
super.init(age)
}
override var description: String {
return "\(self.dynamicType): I'm \(age). I have \(beerCount) beers!"
}
}
}
This results in the following behavior:
Person.person(10) // "ChildPerson: I'm 10. I have 5 toys!"
Person.person(35) // "AdultPerson: I'm 35. I have 99 beers!"
Person(35) // 'Person' cannot be constructed because it has no accessible initializers
Person.ChildPerson(35) // 'Person.ChildPerson' cannot be constructed because it has no accessible initializers
It's not quite as nice as Objective C, since private means all the subclasses need to be implemented in the same source file, and there's that the minor syntax difference Person.person(x) (or Person.create(x) or whatever) instead of simply Person(x), but practically speaking, it works the same.
To be able to instantiate literally as Person(x), you could turn Person into a proxy class which contains a private instance of the actual base class and forwards everything to it. Without message forwarding, this works for simple interfaces with few properties/methods but it gets unwieldy for anything more complex :P
I think actually the Cluster pattern can be implemented in Swift using runtime functions. The main point is to replace the class of your new object with a subclass when initializing. The code below works fine though I think more attention should be paid to subclass' initialization.
class MyClass
{
var name: String?
convenience init(type: Int)
{
self.init()
var subclass: AnyClass?
if type == 1
{
subclass = MySubclass1.self
}
else if type == 2
{
subclass = MySubclass2.self
}
object_setClass(self, subclass)
self.customInit()
}
func customInit()
{
// to be overridden
}
}
class MySubclass1 : MyClass
{
override func customInit()
{
self.name = "instance of MySubclass1"
}
}
class MySubclass2 : MyClass
{
override func customInit()
{
self.name = "instance of MySubclass2"
}
}
let myObject1 = MyClass(type: 1)
let myObject2 = MyClass(type: 2)
println(myObject1.name)
println(myObject2.name)
protocol SomeProtocol {
init(someData: Int)
func doSomething()
}
class SomeClass: SomeProtocol {
var instance: SomeProtocol
init(someData: Int) {
if someData == 0 {
instance = SomeOtherClass()
} else {
instance = SomethingElseClass()
}
}
func doSomething() {
instance.doSomething()
}
}
class SomeOtherClass: SomeProtocol {
func doSomething() {
print("something")
}
}
class SomethingElseClass: SomeProtocol {
func doSomething() {
print("something else")
}
}
Basically you create a protocol that your class cluster inherits from. You then wrap around an instance variable of the same type and choose which implementation to use.
For example, if you were writing an array class that switched between a LinkedList or a raw array then SomeOtherClass and SomethingElseClass might be named LinkedListImplementation or PlainArrayImplementation and you could decide which one to instantiate or switch to based on whatever is more efficient.
There is a way to achieve this. Whether it is good or bad practice is for another discussion.
I have personally used it to allow for extension of a component in plugins without exposing the rest of the code to knowledge of the extensions. This follows the aims of the Factory and AbstractFactory patterns in decoupling code from the details of instantiation and concrete implementation classes.
In the example case the switching is done on a typed constant to which you would add in extensions. This kinda contradicts the above aims a little technically - although not in terms of foreknowledge. But in your case the switch might be anything - the number of wheels for example.
I don’t remember if this approach was available in 2014 - but it is now.
import Foundation
struct InterfaceType {
let impl: Interface.Type
}
class Interface {
let someAttribute: String
convenience init(_ attribute: String, type: InterfaceType = .concrete) {
self.init(impl: type.impl, attribute: attribute)
}
// need to disambiguate here so you aren't calling the above in a loop
init(attribute: String) {
someAttribute = attribute
}
func someMethod() {}
}
protocol _Factory {}
extension Interface: _Factory {}
fileprivate extension _Factory {
// Protocol extension initializer - has the ability to assign to self, unlike class initializers.
init(impl: Interface.Type, attribute: String) {
self = impl.init(attribute: attribute) as! Self;
}
}
Then in a concrete implementation file ...
import Foundation
class Concrete: Interface {
override func someMethod() {
// concrete version of some method
}
}
extension InterfaceType {
static let concrete = InterfaceType(impl: Concrete.self)
}
For this example Concrete is the "factory" supplied default implementation.
I have used this, for example, to abstract the details of how modal dialogs were presented in an app where initially UIAlertController was being used and migrated to a custom presentation. None of the call sites needed changing.
Here is a simplified version that does not determine the implementation class at runtime. You can paste the following into a Playground to verify its operation ...
import Foundation
class Interface {
required init() {}
convenience init(_ discriminator: Int) {
let impl: Interface.Type
switch discriminator {
case 3:
impl = Concrete3.self
case 2:
impl = Concrete2.self
default:
impl = Concrete1.self
}
self.init(impl: impl)
}
func someMethod() {
print(NSStringFromClass(Self.self))
}
}
protocol _Factory {}
extension Interface: _Factory {}
fileprivate extension _Factory {
// Protocol extension initializer - has the ability to assign to self, unlike class initializers.
init(impl: Interface.Type) {
self = impl.init() as! Self;
}
}
class Concrete1: Interface {}
class Concrete2: Interface {}
class Concrete3: Interface {
override func someMethod() {
print("I do what I want")
}
}
Interface(2).someMethod()
Interface(1).someMethod()
Interface(3).someMethod()
Interface(0).someMethod()
Note that Interface must actually be a class - you can't collapse this down to a protocol avoiding the abstract class even if it had no need for member storage. This is because you cant invoke init on a protocol metatype and static member functions cannot be invoked on protocol metatypes. This is too bad as that solution would look a lot cleaner.
We can take advantage of a compiler quirk - self is allowed to be assigned in protocol extensions - https://forums.swift.org/t/assigning-to-self-in-protocol-extensions/4942.
Thus, we can have in place something like this:
/// The sole purpose of this protocol is to allow reassigning `self`
fileprivate protocol ClusterClassProtocol { }
extension ClusterClassProtocol {
init(reassigningSelfTo other: Self) {
self = other
}
}
/// This is the base class, the one that gets circulated in the public space
class ClusterClass: ClusterClassProtocol {
convenience init(_ intVal: Int) {
self.init(reassigningSelfTo: IntChild(intVal))
}
convenience init(_ stringVal: String) {
self.init(reassigningSelfTo: StringChild(stringVal))
}
}
/// Some private subclass part of the same cluster
fileprivate class IntChild: ClusterClass {
init(_ intVal: Int) { }
}
/// Another private subclass, part of the same cluster
fileprivate class StringChild: ClusterClass {
init(_ stringVal: String) { }
}
Now, let's give this a try:
print(ClusterClass(10)) // IntChild
print(ClusterClass("abc")) // StringChild
This works the same as in Objective-C, where some classes (e.g. NSString, NSArray, NSDictionary) return different subclasses based on the values given at initialization time.