Swift:仅在 return 类型上不同的方法重载

Swift: method overloads that only differ in return type

我一直看到 Swift 类 其中定义了两种方法,仅 return 类型不同。我不习惯使用允许这样做的语言(Java、C# 等),所以我去寻找描述它在 Swift 中如何工作的文档。我在任何地方都找不到任何东西。我本来希望 Swift 书中有一个完整的章节。这记录在哪里?

这是我正在谈论的示例(我正在使用 Swift 2,FWIW):

class MyClass {
    subscript(key: Int) -> Int {
        return 1
    }

    subscript(key: Int) -> String {
        return "hi"
    }

    func getSomething() -> Int {
        return 2
    }

    func getSomething() -> String {
        return "hey"
    }
}

测试:

    let obj = MyClass()    

    //let x = obj[99]
    // Doesn't compile: "Multiple candidates fail to match based on result type"

    let result1: String = obj[123]
    print("result1 \(result1)")  // prints "result1 hi"

    let result2: Int = obj[123]
    print("result2 \(result2)") // prints "result2 1"

    //let x = obj.getSomething()
    // Doesn't compile: "Ambiguous use of 'getSomething'"

    let result3: String = obj.getSomething()
    print("result3 \(result3)")  // prints "result3 hey"

    let result4: Int = obj.getSomething()
    print("result4 \(result4)") // prints "result4 2"

一个函数的类型由它的参数类型和它的 return 值的类型决定,并且编译器可以通过它们的类型来消除相似命名的函数的歧义——从你的例子:

subscript(key: Int) -> Int {
    return 1
}

...具有类型 (Int) -> Int

subscript(key: Int) -> String {
    return "hi"
}

...具有类型 (Int) -> String

-- 因此尽管它们的名称相似,但编译器可以通过 return 值的分配方式(或者因为这是一个 subscript,通过什么值来推断正在调用哪个正在分配给该下标)

继续:

func getSomething() -> Int {
    return 2
}

...类型为 () -> Int

func getSomething() -> String {
    return "hey"
}

...具有类型 () -> String

注意:如果您没有为编译器提供足够的信息来推断您正在调用哪个函数,那么您可能会遇到麻烦,例如如果你简单地调用 getSomething() 而没有对其 return 值做任何事情,它会抱怨 ambiguous use of 'getSomething'

EDIT - 啊,我现在在你的示例代码中看到你确实提供了一个例子,在这种情况下 :) 通过分配 return 值到一个你没有指定类型的常量 (let x = getSomething()) 没有足够的信息让编译器找出你正在调用的函数

EDIT EDIT - 请注意,我开始时说的是“编译器可以通过它们来消除类似命名的函数的歧义type',函数名称由以下因素确定:(1) 函数的标识符,以及 (2) 函数外部参数名称的标识符 - 例如尽管下面两个函数的类型和函数标识符相同,但它们是不同的函数,并且具有不同的函数名称,因为它们用于外部参数名称的标识符不同:

func getSomething(thing: String, howMany: Int) -> String

...具有类型 (String, Int) -> String,并命名为 getSomething(_:howMany:)

func getSomething(thing: String, howManyTimes: Int) -> String

...具有类型 (String, Int) -> String,并命名为 getSomething(_:howManyTimes:)

Where is this documented?

至于subscript:

Language Reference / Declarations / Subscript Declaration

You can overload a subscript declaration in the type in which it is declared, as long as the parameters or the return type differ from the one you’re overloading.

Language Guide / Subscripts / Subscript Options

A class or structure can provide as many subscript implementations as it needs, and the appropriate subscript to be used will be inferred based on the types of the value or values that are contained within the subscript braces at the point that the subscript is used.

我找不到任何关于重载方法或函数的官方文档。但在 Swift 博客中:

Redefining Everything with the Swift REPL / Redefinition or Overload?

Keep in mind that Swift allows function overloading even when two signatures differ only in their return type.

这是 Swift 的一个相当酷的方面。我目前在通用 class 中使用它来获得多个下标。这是我创建的一个 playground,用于对其进行研究:

import Foundation

/*
 Return Type Differentiation

 This playground illustrates a rather useful capability of Swift: The ability to differentiate methods by return type; not just argument list.

 In this example, we will set up multiple subscript() methods for an aggregator/façade class that will access the contained instances in
 various ways, depending on the return type requested.
 */

// This class should win the igNoble prize for poitry.
struct A {
    let poem: [String] = ["I'm a little teapot",
                          "bloody and cut.",
                          "This is my handle.",
                          "This is my "]

    let multiplier: UInt32 = arc4random_uniform(100)    // Just a random integer from 0 to 100.
}

// This class has a few different data types that are set at instantiation time, and one static instance of A
class B {
    let stringProperty: String
    let intProperty: Int = Int(arc4random_uniform(10))
    let objectProperty: A = A()

    init(_ string: String) {
        self.stringProperty = string
    }

    // This will be used to demonstrate that we don't need to explicitly cast, if we only have one subscript method.
    subscript(_ ignoredIndex: Int) -> A {
        return self.objectProperty
    }
}

// This class acts as a façade class. It provides an interface to its contained classes as if they were direct subscripts.
class C : Sequence {
    let aArray: [B]

    init() {
        self.aArray = [B("hut"),B("butt")]
    }

    // You can have multiple subscript() methods, differentiated by return type.
    subscript(_ index: Int) -> B {
        return self.aArray[index]
    }

    subscript(_ index: Int) -> String {
        return self.aArray[index].stringProperty
    }

    subscript(_ index: Int) -> UInt32 {
        return (self[index] as A).multiplier
    }

    subscript(_ index: Int) -> Int {
        return self.aArray[index].intProperty
    }

    subscript(_ index: Int) -> A {
        return self.aArray[index].objectProperty
    }

    // These are not simple data return subscripts. In fact, there are no Float properties, so that one is made from whole cloth.
    subscript(_ index: Int) -> Float {
        return Float(self.aArray[index].intProperty) * Float((self[index] as A).multiplier)
    }

    subscript(_ index: Int) -> [String] {
        var ret: [String] = []

        let aInstance: B = self.aArray[index]

        ret = aInstance[0].poem // No need for explicit casting if we only have one subscript.

        ret[3] += self[index] + "." // This is allowed, as we know we're a String.

        return ret
    }

    // You can only have one makeIterator() method.
    func makeIterator() -> AnyIterator<[String]> {
        var nextIndex = 0

        // Return a "bottom-up" iterator for the list.
        return AnyIterator() {
            if nextIndex == self.aArray.count {
                return nil
            }
            let ret: [String]! = self.aArray[nextIndex - 1].objectProperty.poem
            nextIndex += 1
            return ret
        }
    }

    // You can have multiple methods with the same input signature, differentiated only by their output signature.
    func returnIndexedElement(_ atIndex: Int) -> Int {
        return self[atIndex]    // Note no explicit casting is necessary, here.
    }

    func returnIndexedElement(_ atIndex: Int) -> UInt32 {
        return self[atIndex]
    }

    func returnIndexedElement(_ atIndex: Int) -> A {
        return self[atIndex]
    }

    func returnIndexedElement(_ atIndex: Int) -> B {
        return self[atIndex]
    }

    func returnIndexedElement(_ atIndex: Int) -> Float {
        return self[atIndex]
    }

    func returnIndexedElement(_ atIndex: Int) -> String {
        return self[atIndex]
    }

    func returnIndexedElement(_ atIndex: Int) -> [String] {
        return self[atIndex]
    }
}

let mainObject = C()

// First, let's test the subscripts.
// We have 3 elements, so
let aObject1: A = mainObject[0]
let aObject2: B = mainObject[0]
let aString: String = mainObject[0]
let aPoem: [String] = mainObject[0]
let aInt: Int = mainObject[0]
let aUInt32 = mainObject[0] as UInt32
let aFloat = mainObject[0] as Float

// This will not work. You need to specify the type explicitly when using multiple subscripts, differentiated only by return type.
// let failObject = mainObject[0]

// However, this will work, because the class has only one subscript method defined.
let aObject2_Subscript = aObject2[0]
let aObject2_Poem = aObject2_Subscript.poem

// Next, test the accessor methods.
let bObject1: A = mainObject.returnIndexedElement(1)
let bObject2: B = mainObject.returnIndexedElement(1)
let bString: String = mainObject.returnIndexedElement(1)
let bPoem: [String] = mainObject.returnIndexedElement(1)
let bInt: Int = mainObject.returnIndexedElement(1)
let bUInt32 = mainObject.returnIndexedElement(1) as UInt32
let bFloat = mainObject.returnIndexedElement(1) as Float

// This will not work. You need to specify the type explicitly when using multiple methods, differentiated only by return type.
// let failObject = mainObject.returnIndexedElement(1)