Swift:下标 vs. callAsFunction

4

subscriptcallAsFunction之间有什么区别?在我看来,它们的行为几乎相同,它们都可以有参数标签、默认值等。

除了subscript使用方括号instance[index]callAsFunction使用圆括号instance(index),它们之间是否有任何实际区别呢?

顺便说一下,我们知道subscript可以有一个setter,那么callAsFunction呢?它也可以有setter吗?

3个回答

1

通过getter和setter方法,可以将下标设置为读写或只读。callAsFunction()是一种允许您具有参数执行某些步骤或函数的方法。

下标

查看Swift文档中的下标,我们可以看到如下内容。

Source: https://docs.swift.org/swift-book/LanguageGuide/Subscripts.html

subscript(index: Int) -> Int {
    get {
        // Return an appropriate subscript value here.
    }
    set(newValue) {
        // Perform a suitable setting action here.
    }
}

callAsFunction()

callAsFunction()的调用没有getter和setter。你可能会有像这样返回计算值的内容。

来源: https://www.hackingwithswift.com/articles/212/whats-new-in-swift-5-2

struct StepCounter {
    var steps = 0

    mutating func callAsFunction(count: Int) -> Bool {
        steps += count
        print(steps)
        return steps > 10_000
    }
}

var steps = StepCounter()
let targetReached = steps(count: 10)

1
我们知道下标可以有一个setter,那么callAsFunction呢?它也可以有一个setter吗?
不行,因为callAsFunction只是在你的类/结构体中声明的一个普通方法。能够在没有其名称的情况下调用它只是通过编译器魔法实现的语法糖。
这基本上是下标和callAsFunction之间的唯一实际区别。
但我认为它们之间更重要的区别不是功能上的区别,而是样式上的区别。下标和函数调用表达不同的语义。下标表示“访问/设置某些东西”的语义,而函数调用更像是“做某事”。
这个想法得到了苹果的Swift API Design Guidelines的支持:
描述函数或方法“做什么”以及它“返回”什么
描述下标“访问”什么
显然,函数应该“做”一些事情,而下标用于“访问”东西。

让我们使用这里的示例:

struct Dice {
    var lowerBound: Int
    var upperBound: Int

    func callAsFunction() -> Int {
        (lowerBound...upperBound).randomElement()!
    }
}

let d6 = Dice(lowerBound: 1, upperBound: 6)
let roll1 = d6()
print(roll1)

想象一下,如果这个用下标写会是什么样子:

subscript() -> Int {
    (lowerBound...upperBound).randomElement()!
}

let roll1 = d6[]

这段代码可以运行,但是d6[]并不能像d6()那样准确地表达“掷骰子”的意思。使用d6[]的感觉更像是在访问d6中的某个元素。

相反,想象一下如果字典访问也像函数调用一样:

let studentScores = ["Tom": 100, "Jack": 80, "Harry", 70]
let tomsScore = studentScores("Tom")

感觉我正在执行一个名为studentScores的操作。如果我将字典重命名为studentScoresFor会更好,但这对于一个字典来说是一个奇怪的名称...


-3

方法和下标目前具有不同的功能。但它们正在趋同。(例如,下标获得了泛型/静态,而callAsFunction使用与下标相同的语法。)

现在没有理由让它们同时存在。它们来自于Swift之前的时代。任何认为它们具有固有不同含义的人只是因为他们学习了它并且现在觉得它“自然”。要警惕教条主义。

目前,如果您想在等号后使用值,则需要使用下标。如果此功能最终被纳入Swift中,它可能会以“命名下标”的形式出现。一旦人们习惯了这种方式,您将看到更多像您一样的人开始提出问题。

然后,其中一个语法将被弃用,或者我们将等待Swift的继任者。

以下是命名下标的示例:

final class Class {
  var bools: ObjectSubscript<Class, String, Bool?> {
    .init(
      self,
      get: { object in
        { object._bools[$0]
          ?? Bool(binaryString: $0)
        }
      },
      set: { $0._bools[$1] = $2 }
    )
  }

  private var _bools: [String: Bool] = [:]
}

let object = Class()
let ghoul = ""
XCTAssertEqual(object.bools["1"], true)

XCTAssertNil(object.bools[ghoul])
object.bools[ghoul] = false
XCTAssertEqual(object.bools[ghoul], false)

/// An emulation of the missing Swift feature of named subscripts.
/// - Note: Argument labels are not supported.
public struct ObjectSubscript<Object: AnyObject, Index, Value> {
  public typealias Get = (Object) -> (Index) -> Value
  public typealias Set = (Object, Index, Value) -> Void

  public unowned var object: Object
  public var get: Get
  public var set: Set
}

public extension ObjectSubscript {
  init(
    _ object: Object,
    get: @escaping Get,
    set: @escaping Set
  ) {
    self.object = object
    self.get = get
    self.set = set
  }

  subscript(index: Index) -> Value {
    get { get(object)(index) }
    nonmutating set { set(object, index, newValue) }
  }
}

var set: Set = [1, 2, 3]

XCTAssert(set.contains[3])

set.contains[1] = false
XCTAssertEqual(set, [2, 3])

var contains = set.contains

contains[3].toggle()
XCTAssertEqual(set, [2])

contains.set = { _, _, _ in }
contains[2].toggle()
XCTAssertEqual(set, [2])

var four: Set = [4]
withUnsafeMutablePointer(to: &four) {
  contains.pointer = $0
  XCTAssert(contains[4])
}

public extension SetAlgebra {
  var contains: ValueSubscript<Self, Element, Bool> {
    mutating get {
      .init(
        &self,
        get: Self.contains,
        set: { set, element, newValue in
          if newValue {
            set.insert(element)
          } else {
            set.remove(element)
          }
        }
      )
    }
  }
}

/// An emulation of the missing Swift feature of named subscripts.
/// - Note: Argument labels are not supported.
public struct ValueSubscript<Root, Index, Value> {
  public typealias Pointer = UnsafeMutablePointer<Root>
  public typealias Get = (Root) -> (Index) -> Value
  public typealias Set = (inout Root, Index, Value) -> Void

  public var pointer: Pointer
  public var get: Get
  public var set: Set
}

public extension ValueSubscript {
  init(
    _ pointer: Pointer,
    get: @escaping Get,
    set: @escaping Set
  ) {
    self.pointer = pointer
    self.get = get
    self.set = set
  }

  subscript(index: Index) -> Value {
    get { get(pointer.pointee)(index) }
    nonmutating set { set(&pointer.pointee, index, newValue) }
  }
}

1
大部分回答都没有回答问题。问题不是关于比较一般的方法和下标,而是关于比较特定的 callAsFunction 语法糖和下标,即 someObject[xxx] vs someObject(xxx)。只有你的第二段似乎试图回答这个问题。 - Sweeper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接