Swift:转换集合,并创建自定义可转换协议

Swift: Casting collections, and creating custom convertible protocols

考虑这个 Person 类,它简单地实现 StringLiteralConvertible 并将字符串文字分配给 name:

class Person : StringLiteralConvertible {
    var name : String?

    typealias StringLiteralType = String

    required init(stringLiteral value: StringLiteralType) {
        println("stringLiteral \(value)")
        name = value
    }

    typealias ExtendedGraphemeClusterLiteralType = String

    required init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
        println("extendedGraphemeClusterLiteral \(value)")
        name = value
    }

    typealias UnicodeScalarLiteralType = Character

    required init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
        println("unicodeScalarLiteral \(value)")
        name = "\(value)"
    }
}

这允许我使用字符串创建一个 Person 实例:

let aaron : Person = "Aaron"

我什至可以从一个字符串数组中转换一个 Person 数组:

let names = ["John", "Jane"] as [Person]

然而,这仅适用于字符串 文字 。如果我使用字符串变量,它会失败:

let aaronString = "Aaron"
let aaron : Person = aaronString
// Error: 'NSString' is not a subtype of 'Person'

同样,尝试转换非文字字符串数组失败:

let nameStrings = ["John", "Jane"]
let people : [Person] = nameStrings
// Error: 'String' is not identical to 'Person'

我有三个问题:

  1. 我可以实施另一个协议来将 非文字 字符串转换为 Person 吗?我想这样做,这样我就可以转换整个集合来转换对象。

  2. 如果 #1 不是,那么 map + 初始值设定项是我自己执行转换的最佳方式吗?

    let nameStrings = ["John", "Jane"]
    let people = nameStrings.map{Person(name: [=15=])}
    
  3. 如果#1 是,是否有类似的方法可以用来指定一种方法来转换两个在层次结构中不相关的对象?也就是说,我可以在没有初始化器的情况下解决这个错误吗?

    let rikerPerson : Person = "Riker"
    let rikerEmployee = rikerPerson as Employee
    // Error: 'Person' is not convertible to 'Employee'
    
  1. 您所说的“转换”并不是真正的转换(例如,s = “fred”; ns = s as NSString 是,或者 C++ 中的转换)。

    let names = ["John", "Jane"] as [Person]
    

    只是另一种写法:

    let names: [Person] = ["John", "Jane"]
    

    也就是说,告诉 Swift 使用 StringLiteralConvertible 的许多可能版本中的哪一个(而不是 String 的默认版本)。

    换句话说——您的 as 正在实现与此代码段中的 as 类似的功能,它消除了仅 return 类型不同的两个重载函数的歧义:

    func f() -> String { return "foo" }
    func f() -> Int { return 42 }
    
    let i = f() as Int    // i will be 42
    let s = f() as String // s will be “foo"
    

    这里没有进行“转换”——as 只是用来消除 f Swift 调用的歧义。选择init(stringLiteral:)也是一样。

  2. 肯定(但前提是您在 map{ } 之间放置 space ;-)。

    如果您担心将其全部转换为数组只是为了用它做一些其他事情的浪费,请查看 lazy(a).map

  3. 没有。在 Beta 版中,曾经有一个 __conversion() -> T 方法,你可以在自己的 classes 上实现这样的“转换”——或者更重要的是,允许你通过你的 Person class 转换为接受 Employee 参数并隐式转换的函数。但这消失了。一般来说,这种隐式转换与 Swift 的风格相反,除了极少数情况(Obj-C 和 C 互操作,以及可选中的隐式包装,是主要的)。您必须为 Employee 编写一个 init,它接受一个 Person(或某些 class 或 Person 符合的协议),然后调用它。