Override Multiple Overloaded Init() Methods in Swift

Override multiple overloaded init() methods in Swift

It would seem that your declarations (with override) should be sufficient, however, they seem to need the @objc declarations as well. This works:

class CustomHighlightTextFieldCell : NSTextFieldCell {

required init(coder aCoder: NSCoder!) {
super.init(coder: aCoder)
}

@objc(initImageCell:)
override init(imageCell anImage: NSImage!) {
super.init(imageCell: anImage)
}

@objc(initTextCell:)
override init(textCell aString: String!) {
super.init(textCell: aString)
}
}

Swift Overload init()

Updated Answer

class Y { }
class X : Y {
var title: String?
var imageName: String?

convenience override init() {
self.init(title: nil, imageName: nil)
}

convenience init(title:String?) {
self.init(title: title, imageName: nil)
}

init(title: String?, imageName: String?) {
self.title = title
self.imageName = imageName
super.init()
}
}
  • Use the most complete initializer as the designated initializer.

    In this case init(title: String?, imageName: String?) is the only initializer that sets all its properties, so it should be the designated initializer.

  • Initialize your properties before calling super.init().

    My old answer only worked because title and imageName were both var and optional.

    In Two-Phase Initialization section of The Swift Programming Language: Initialization

    Safety check 1

    A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.


Old Answer

I plugged the sample into a playground, here is how I got it to work:

class Y { }
class X : Y {
var title: String?
var imageName: String?

override init() {

}

init(aTitle:String?) {
super.init()
self.title = aTitle
}

convenience init(aTitle:String?, aImageName:String?) {
self.init(aTitle: aTitle)
self.imageName = aImageName
}
}
  • init(aTitle:String?, aImageName:String?) cannot call init(aTitle:) and still be a designated initializer, it must be a convenience initializer.
  • self.init must be before anything else in init(aTitle:String?, aImageName:String?).
  • initializer must be passed the parameter name self.init(aTitle) must be self.init(aTitle: aTitle).

As a side note, I removed the unneeded semicolons and put super.init() first for style reasons.

Hope that helps.


UPDATE

To follow Apple's advice, there should only be one designated, the rest should be convenience initializers. For example, if you decide init(aTitle:String?) should be the designated initializer, then the code should look like:

convenience override init() {
self.init(aTitle: nil) // A convenience initializer calls the designated initializer.
}

init(aTitle:String?) {
super.init() // Only the designated initializer calls super.init.
self.title = aTitle
}

convenience init(aTitle:String?, aImageName:String?) {
self.init(aTitle: aTitle) // A convenience initializer calls the designated initializer.
self.imageName = aImageName
}

There are times when you might want more than once designated initializer, for example, UIView, but that should be an exception, not the rule.


UPDATE 2

Classes should have one designated initializer. Convenience initializer will (eventually) call the the designated initializer.

Initialization

A class may have multiple initializers. This occurs when the initialization data can take varied forms or where certain initializers, as a matter of convenience, supply default values. In this case, one of the initialization methods is called the designated initializer, which takes the full complement of initialization parameters.

Multiple initializers

The Designated Initializer

The initializer of a class that takes the full complement of initialization parameters is usually the designated initializer. The designated initializer of a subclass must invoke the designated initializer of its superclass by sending a message to super. The convenience (or secondary) initializers—which can include init—do not call super. Instead they call (through a message to self) the initializer in the series with the next most parameters, supplying a default value for the parameter not passed into it. The final initializer in this series is the designated initializer.

How a class has several methods with the same name?

Others are calling this "overloading," but it isn't. Overloading is when functions are distinguished only by their types (and Swift supports this). That's not what is happening here. The difference between these functions is their names, just like in "normal" functions. Swift is similar to Objective-C (and Smalltalk, but unlike most languages) in how it names functions. The names of these functions are (following the ObjC tradition):

centralManager(_:didDiscover:advertisementData:rssi:)
centralManager(_:didConnect:)
centralManager(_:didDisconnectPeripheral:error:)

As you can see, these are three completely different names, and so are three different functions without anything complex like overloads getting involved.

In most languages, the names of the parameters are not part of the functions name. But in Smalltalk, ObjC, and Swift, they are. This is not like "named parameters" in Python. The name of the function includes these parameter names, in order, and easily distinguish one from another. If Swift hadn't carried this over from ObjC, it would have been extremely difficult to bridge the two languages as well as they are.

Note that this kind of function naming, where the "base" part of the function name is the name of the caller ("centralManager"), is from the ObjC/Cocoa tradition. While it's common in Swift because of Cocoa (i.e. iOS), it is not actually common in "pure Swift," which generally doesn't use the delegate pattern like this (you never see this in SwiftUI, for example). So what you're really seeing here is ObjC automatically transliterated into Swift.

But distinguishing Swift functions by their full (including parameters) name is extremely common and "Swifty." You see it most commonly in init methods, but it happens all over.

Function overload with subclass in swift

Polymorphism is exactly how you get rid of the if and is that you so dread. That, in fact, is what polymorphism is. The problem is that you use the word "polymorphic" but you forgot to use polymorphism. In effect, there is no polymorphism in your code. Polymorphism means that each class or subclass acts for itself. Thus:

class A {
func who() -> String {return "A"}
}

class B : A {
override func who() -> String {return "B"}
}

class Example {
func action(_ param : A) -> String {
return param.who()
}
func test() -> String {
let t : A = B()
return self.action(t) // returns B
}
}

The alternative would be to say for example

return String(describing:type(of:param))

but I assume that you know that and that you count that as the same as saying is and as.

Overloading generic functions in iOS Swift

You haven't quite focussed on the actual issue. Let's eliminate everything irrelevant from the example. This compiles and works as expected:

struct TestFinder {

func doSomething<T,U>(_ function: (T,U) -> Void) -> Void {
print("two")
}

func doSomething<T,U,V>(_ function: (T,U,V) -> Void) -> Void {
print("three")
}

func doSomething<T,U,V,W>(_ function: (T,U,V,W) -> Void) -> Void {
print("four")
}

}

And here we'll test it:

    func f(_ s1: String, _ s2: String, _ s3: String, _ s4: String) -> Void {}
TestFinder().doSomething(f) // "four"

But if you add the version with one passed function parameter, everything breaks down:

struct TestFinder {

func doSomething<T>(_ function: (T) -> Void) -> Void {
print("one")
}

func doSomething<T,U>(_ function: (T,U) -> Void) -> Void {
print("two")
}

func doSomething<T,U,V>(_ function: (T,U,V) -> Void) -> Void {
print("three")
}

func doSomething<T,U,V,W>(_ function: (T,U,V,W) -> Void) -> Void {
print("four")
}
}

Now we can't compile, because the first version is seen as a candidate. And indeed, if we remove the other versions, we still compile!

struct TestFinder {

func doSomething<T>(_ function: (T) -> Void) -> Void {
print("one")
}

}

That's the weird part. We still compile, even though we are saying:

    func f(_ s1: String, _ s2: String, _ s3: String, _ s4: String) -> Void {}
TestFinder().doSomething(f)

Evidently, this function with four parameters is seen by the compiler as "fitting" the declaration with just one generic parameter.

I regard this as a bug. I think I can guess what might cause it; it could have to do with the legacy of function parameter list as tuples. This function f is "equivalent" to a function taking a single parameter consisting of a four-string tuple. Nevertheless, you cannot actually call the function inside doSomething with a four-string tuple; I cannot find a way to call it at all.

So, I would say, regard this as a bug, and work around it for now by removing the first version of your generic.


UPDATE: On the advice of the Swift team, I tested with the May 4, 2020 Swift 5.3 Development toolchain. With it, your code compiles and behaves as expected. This was indeed a bug, and it was fixed as part of

https://bugs.swift.org/browse/SR-8563

Returning for a moment to my version, my code, too, compiles and behaves as expected, with all four versions of doSomething present. However, note that if you delete all but the first version of doSomething, it still compiles and runs. Moreover, you can call function with four parameters by bundling them into a tuple and force casting, like this:

struct TestFinder2 {

func doSomething<T>(_ function: (T) -> Void) -> Void {
print("one")
function(("manny", "moe", "jack", "henry") as! T)
}

}

That seems to confirm my guess that what you're seeing is a consequence of the hidden tuple-nature of a function's parameter list. One can draw the same conclusion from the discussion of the bug, which refers to "tuple-splatting".

Multiple Initializers in a single class (swift)

Here's how you can add another initializer to the same class that will use two parameters to create an Isosceles triangle:

class Triangle {

var sideA: Int
var sideB: Int
var sideC: Int

init(sideA: Int, sideB: Int, sideC: Int) {

self.sideA = sideA
self.sideB = sideB
self.sideC = sideA
}

init(sideA: Int, sideB: Int) {

self.sideA = sideA
self.sideB = sideB
self.sideC = sideB
}

convenience init(equilateralWithEdge edge:Int) {
self.init(sideA: edge, sideB: edge, sideC:edge)
}

}

Swift Extension fails adding overloaded methods to Objective-C class

ObjC doesn't support method overloading. When you extend an ObjC class, the interface to your code is the ObjC runtime—even if you're calling your Swift code in that class from other Swift code—so your class' interface can't use features of Swift that aren't also present in ObjC.

As you've noted, this should probably be a compile error. I'd recommend filing a bug about that, especially since you've already put together a handy test case.

Overload Single Equals in swift

No dice, I’m afraid. From the language reference

The tokens =, ->, //, /*, */, ., the prefix operators <, &, and ?, the infix operator ?, and the postfix operators >, !, and ? are reserved. These tokens can’t be overloaded, nor can they be used as custom operators.

Instead, if you want ConvertibleUnit to always be creatable from an Int, give the protocol an init(_ val: Int) method so people can write let unit = T(42). Or maybe even consider having it conform to IntegerLiteralConvertible.

Generally, the preferred style in Swift is not to have automatic/implicit conversions between different types. This is why, for example, you must cast an Int to a Double before you can add it to another Double.

Of course, you can write things like this:

func +(lhs: Int, rhs: Double) -> Double {
return Double(lhs) + rhs
}

let i = 1
let f = 1.2
i + f // = 2.2

but this is in general not considered good practice.



Related Topics



Leave a reply



Submit