【问题标题】:When are argument labels required in Swift?Swift 什么时候需要参数标签?
【发布时间】:2014-07-18 00:59:52
【问题描述】:

在回答 this question 时,需要使用参数标签来调用 init。这在 Swift 中很正常。

class Foo {
    init(one: Int, two: String) { }
}

let foo = Foo(42, "Hello world") // Missing argument labels 'one:two:' in call

然而,奇怪的力量在起作用:

extension Foo {
    func run(one: String, two: [Int]) { }
}

foo.run(one: "Goodbye", two: []) // Extraneous argument label 'one:' in call

要在此处使用参数标签,必须明确声明。

我还没有在文档中看到非常详尽的解释所有这些的内容。哪些类/实例/全局函数需要参数标签? Obj-C 方法是否总是使用参数标签导出和导入?

【问题讨论】:

    标签: swift syntax


    【解决方案1】:

    所有初始化方法都需要参数名称:

    var view = NSView(frame: NSRect(x: 10, y: 10, width: 50, height: 50))
    class Foo {
        init(one: Int, two: String) { }
    }
    
    let foo = Foo(one: 42, two: "Hello world")
    

    对象上调用的所有方法都使用参数名称,但第一个参数除外:

    extension Foo {
        func run(one: String, two: [Int]) { }
    }
    foo.run("Goodbye", two: [])
    

    Swift 和 Objective-c 中的所有类函数都遵循相同的模式。您还可以显式添加外部名称。

    extension Foo{
    class func baz(one: Int, two: String){}
    class func other(exOne one: Int,  exTwo two: String){}
    }
    Foo.baz(10, two:"str")
    Foo.other(exOne: 20, exTwo:"str")
    

    非类函数的 Swift 函数不需要参数名称,但您仍然可以显式添加它们:

    func bar(one: Int, two: String){}
    bar(1, "hello")
    

    正如 Bryan 所说,当在方法签名中具有参数名称的 Objective-C 方法上调用 Swift 方法时,它是有意义的。 init 方法包含第一个参数,因为 Swift 将 Objective-c 的 init 方法从 initWith:... 更改为 Class(),因此第一个参数名称不再包含在方法名称中。

    【讨论】:

    • 您的回答似乎是这里最完整的。你能添加一些对外部文档的引用吗?
    • 我想强制包含我的参数名称ascending,以便调用站点更加明确。所以我做了这个答案所示的方法签名sortByDate(ascending ascending: Bool)。 Xcode 建议我可以通过将 ascending ascending 更改为 #ascending 来简化这一点。我认为我们可以说 Swift 显然是一种“现代编程语言”,因为它包含标签(对我来说一直是“井号”)。现在我想在我的代码中的某处添加一个#stfu 参数。 ;-)
    • 来自 The Swift Programming Language 的一个有用的引用:“但是,初始化器在括号前没有函数和方法那样的识别函数名称。因此,名称和类型初始化器的参数在确定应该调用哪个初始化器时起着特别重要的作用。因此,如果您自己不提供外部名称,Swift 会为初始化器中的每个参数提供一个自动外部名称。"跨度>
    • 可能还想补充一点,您可以像这样强制使用带有# 的第一个参数标签:func myFunc( #parameter: String) {},并且必须像这样调用它:myFunc(parameter:""), @987654333 @ 会显示错误。就像@Robert 在他的回答中显示的那样
    • 为了更新之前的评论,“'#' 已经从 Swift 中移除。加倍 'parameter' 使参数标签与参数名称相同”func myFunc(parameter parameter: String) {}
    【解决方案2】:

    从 Swift 3.0 开始,这又发生了变化:所有方法、函数和初始化程序都需要 所有 参数的参数标签,除非您已明确选择不使用外部名称 _。这意味着像 addChildViewController(_:) 这样的方法现在写成这样:

    func addChildViewController(_ childController: UIViewController)
    

    这是proposed and approved 作为Swift Evolution process 的一部分,并在SR-961 中实现。

    【讨论】:

    • 我打算就此发表一个新问题,但也许您可以回答:为什么要省略参数标签?在我看来,何时以及何时不省略参数标签似乎是任意的。
    • add(childController: UIViewController) 会坏吗?我们应该如何决定参数描述应该是函数名还是参数标签的一部分?
    • @tfantina 就我而言,我有一个写入日志文件的类。一遍又一遍地做 Log.write("an error message") 比 Log.write(text:"an error message") 容易得多。将其与 print() 函数进行比较。在这种情况下,参数标签使语法(IMO)混乱,似乎是多余的
    【解决方案3】:

    斯威夫特 3.0

    在 Swift 3.0 中,slated to be released in late 2016,默认行为很简单:

    • 默认情况下,所有方法的所有参数都有外部标签。

    您可以在Swift API Design Guidelines 中最简洁地找到这些规则。这个最新的行为在SE-0056, "establish consistent label behavior across all parameters including first labels," 中提出并在SR-961 中实现。默认行为可以按照下面“覆盖默认行为”中的描述进行更改。

    斯威夫特 2.2

    在 Swift 2.2 中,语言对外部参数标签的默认设置已经改变,现在更简单了。默认行为可以总结如下:

    • 方法和函数的第一个参数不应有外部参数标签。
    • 方法和函数的其他参数应该有外部参数标签。
    • 初始化器的所有参数都应该有外部参数标签。

    默认行为可能会按照下面“覆盖默认行为”中的说明进行更改。

    一个例子

    这些规则最好用一个例子来说明:

    func printAnimal(animal: String, legCount: Int) {
        let legNoun = legCount == 1 ? "leg" : "legs"
        print("\(animal) has \(legCount) \(legNoun)")
    }
    
    struct Player {
        let name: String
        let lives: Int
    
        init(name: String, lives: Int) {
            self.name = name
            self.lives = lives
        }
    
        func printCurrentScore(currentScore: Int, highScore: Int) {
            print("\(name)'s score is \(currentScore). Their high score is \(highScore)")
        }
    }
    
    
    // SWIFT 3.0
    // In Swift 3.0, all argument labels must be included
    printAnimal(animal: "Dog", legCount: 4)
    let p = Player(name: "Riley", lives: 3)
    p.printCurrentScore(currentScore: 50, highScore: 110)
    
    // SWIFT 2.2
    // In Swift 2.2, argument labels must be included or omitted in exactly the following way
    // given the definition of the various objects.
    printAnimal("Dog", legCount: 4)
    let p = Player(name: "Riley", lives: 3)
    p.printCurrentScore(50, highScore: 110)
    
    // In Swift 2.2, none of the following will work
    printAnimal(animal: "Dog", legCount: 4)  // Extraneous argument label 'animal:' in call
    let q = Player("Riley", lives: 3)  // Missing argument label 'name:' in call
    p.printCurrentScore(50, 110)  // Missing argument label 'highScore:' in call
    

    覆盖默认行为

    对于任何方法或函数的任何参数,您都可能偏离语言的默认值,尽管样式指南正确地警告您不要这样做,除非有充分的理由。

    要在通常没有的地方添加一个外部参数标签——仅适用于 Swift 2.2,因为 Swift 3.0 默认为每个参数分配外部标签——或者更改一个外部参数标签——适用于两个版本——编写本地参数标签之前的所需外部参数标签:

    func printAnimal(theAnimal animal: String, legCount: Int) {
        let legNoun = legCount == 1 ? "leg" : "legs"
        print("\(animal) has \(legCount) \(legNoun)")
    }
    
    printAnimal(theAnimal: "Dog", legCount: 4)
    

    要删除通常会有的外部参数标签,请使用特殊的外部参数标签_

    func printAnimal(animal: String, _ legCount: Int) {
        let legNoun = legCount == 1 ? "leg" : "legs"
        print("\(animal) has \(legCount) \(legNoun)")
    }
    
    // SWIFT 3.0
    printAnimal(theAnimal: "Dog", 4)
    
    // SWIFT 2.2
    printAnimal("Dog", 4)
    

    这些“默认覆盖”适用于任何方法或函数,包括初始化程序。

    【讨论】:

    • 非常感谢!既然您已经描述了规则,我们是否有机会得到“为什么”?只是,为什么?只有我,还是这些语法规则完全没有任何意义?
    • 好吧,真正的原因只能来自实施这些准则的核心 Swift 团队。一些想法:这些规则使如何将方法名称从 Objective-C 转换为 Swift 变得相当明显。此外,如果重要的话,它们比 Swift 2.0 及以下版本更复杂(以前,方法和函数的规则略有不同)。
    • 这太愚蠢了。在 Swift 2.2 中调用 printAnimal(animal:"Dog", legCount: 4) 会导致错误。为什么不做这个一致的苹果呢?
    • @gondo:你会很高兴看到在 Swift 3.0 中规则正在发生变化。请参阅编辑后答案末尾的注释。
    • 在 Swift 3 中,第一个参数有外部参数标签。宣布于WWDC16 API design guidelines
    【解决方案4】:

    这是我通过阅读(相当稀疏的)文档和通过简单的实验收集到的信息:

    • Init 方法总是需要它们的标签。初始化方法就像标签一样,因为它们清楚地表明了您想要调用的初始化方法。否则,这个:

      FooBar(foos: 5)
      

      还有这个:

      FooBar(bars: 5)
      

      看起来完全一样:

      FooBar(5)
      

      没有其他地方是这种情况 - init 方法是 Swift 中唯一一个它们都具有相同名称但可能具有不同参数的地方。这就是为什么...

    • 函数、方法等(任何不是 init 方法的东西)都省略了第一个标签 - 这是为了样式和减少无聊的重复性。而不是

      aDictionary.removeValueForKey(key: "four")
      

      我们有这个:

      aDictionary.removeValueForKey("four")
      

      对于带有 两个 参数的函数,仍然有相当明确且易于阅读的参数。所以不是

      anArray.insert("zebras", 9)
      

      我们有一个更易于阅读的表格:

      anArray.insert("zebras", atIndex: 9)
      

    看起来好多了。当我在 WWDC 时,这被吹捧为 Swift 的一个特性:Java 风格的现代、简短的参数,而不牺牲可读性。正如Bryan Chen's answer 所示,这也简化了从 Objective-C 的过渡。

    【讨论】:

    • 实际上,函数根本没有任何外部标签(默认情况下)。
    • +1 好答案 - “第一个标签省略功能”的唯一问题是这样的方法:func insertLocation(lat : Double, lon : Double) { ... 然后像这样调用:insertLocation(2.5, lon: 168.3)。解决方法是称它为insertLocationWithLat,但它比它需要的更长。有什么方法可以禁用方法上的第一个标签省略功能?
    • > 有什么方法可以禁用方法上的第一个标签省略功能? -> 是的,使用# 看我的回答
    【解决方案5】:

    您可以在标签前使用#来制作调用方法所需的参数标签。

    例如:

    func addLocation(latitude : Double, longitude : Double) { /*...*/ }
    addLocation(125.0, -34.1) // Not clear
    

    可以这样改进:

    func addLocation(#latitude : Double, #longitude : Double) { /*...*/ }
    addLocation(latitude: 125.0, longitude: -34.1) // Better
    

    (来自WWDC 2014 - 416 - Building Modern Frameworks,15 分钟后)

    【讨论】:

    • Stack Overflow 的语法高亮似乎有 bug。
    • 谢谢,“#”正是我要找的!
    【解决方案6】:

    它只是让 ObjC 方法在 Swift 中看起来不错。

    Documentation

    实例方法

    方法的本地和外部参数名称

    具体来说,Swift 为方法中的第一个参数名称赋予了一个local 默认参数名称,并给出第二个和后续 默认情况下,参数名称包括本地参数名称和外部参数名称。 此约定与您的典型命名和调用约定相匹配 将熟悉编写 Objective-C 方法,并为 无需限定参数即可表达的方法调用 名字。

    ...

    上述默认行为意味着 Swift 中的方法定义使用与 Objective-C 相同的语法风格编写,并以自然、富有表现力的方式调用。

    自定义初始化

    本地和外部参数名称

    但是,初始化器在括号前没有函数和方法那样的标识函数名称。因此,初始化器参数的名称和类型在确定应该调用哪个初始化器时起着特别重要的作用。因此,如果您自己不提供外部名称,Swift 会为初始化器中的每个参数提供自动外部名称

    例如这个 ObjC 类

    @interface Counter : NSObject
    
    @property int count;
    
    - (void)incrementBy:(int)amount numberOfTimes:(int)numberOfTimes;
    
    @end
    

    它是用 Swift 编写的

    class Counter {
        var count: Int = 0
        func incrementBy(amount: Int, numberOfTimes: Int) {
            count += amount * numberOfTimes
        }
    }
    

    调用 ObjC 版本

    [counter incrementBy:10 numberOfTimes:2];
    

    和 Swift 版本

    counter.incrementBy(10, numberOfTimes:2)
    

    你可以看到它们几乎一样

    【讨论】:

    • 这似乎不是一个准确的比较。通常的objective-C实现不会在名称中包含第一个参数-incrementByAmount:(int)amount numberOfTimes:(int)numberOfTimes吗?我没有看到很多 swift 写成incrementByAmount(amount: Int, numberOfTimes: Int)
    猜你喜欢
    • 2010-10-23
    • 1970-01-01
    • 1970-01-01
    • 2012-09-16
    • 2015-05-24
    • 1970-01-01
    • 2014-12-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多