没有哪种教育能及得上逆境 [登录·注册]

吕滔博客

首页 开发 运维 工具 摄影

protocol 组合

Swift TIP memory 发布于January 21, 2015 标签: Swift

众所周知,在 Swift 中我们可以使用 Any 来表示任意类型。充满好奇心的同学可能已经发现,Any 这个类型的定义十分奇怪,它是一个 protocol<> 的同名类型。

protocol<> 这样形式的写法在日常 Swift 使用中其实并不多见,这其实是 Swift 的接口组合的用法。标准的语法形式是下面这样的:

protocol<ProtocolA, ProtocolB, ProtocolC>

尖括号内是具体接口的名称,这里表示将名称为 ProtocolA,ProtocolB 以及 ProtocolC 的接口组合在一起的一个新的匿名接口。实现这个匿名接口就意味着要同时实现这三个接口所定义的内容。所以说,这里的 protocol 组合的写法和下面的新声明的 ProtocolD 是相同的:

protocol ProtocolD: ProtocolA, ProtocolB, ProtocolC {

}

那么,在 Any 定义的时候的里面什么都不写的 protocol<> 是什么意思呢?从语意上来说,这代表一个 "需要实现空接口的接口",其实就是任意类型的意思了。

除了可以方便地表达空接口这一概念以外,protocol 的组合相比于新创建一个接口的最大区别就在于其匿名性。有时候我们可以借助这个特性写出更清晰的代码。因为 Swift 的类型组织是比较松散的,你的类型可以由不同的 extension 来定义实现不同的接口,Swift 也并没有要求它们在同一个文件中。这样,当一个类型实现了很多接口时,在使用这个类型的时候我们很可能在不查询相关代码的情况下很难知道这个类型所实现的接口。

举个理想化的例子,比如我们有下面的三个接口,分别代表了三种动物的叫的方式,而有一种谜之动物,同时实现了这三个接口:

protocol KittenLike {
    func meow() -> String
}

protocol DogLike {
    func bark() -> String
}

protocol TigerLike {
    func aou() -> String
}

class MysteryAnimal: CatLike, DogLike, TigerLike {
    func meow() {
        return "meow"
    }

    func bark() {
        return "bark"
    }

    func aou() {
        return "aou"
    }
}

现在我们想要检查某种动物作为宠物的时候的叫声的话,我们可能要重新定义一个叫做 PetLike 的接口,表明其实现 KittenLike 和 DogLike;如果稍后我们又想检查某种动物作为猫科动物的叫声的话,我们也许又要去定义一个叫做 CatLike 这样的实现 KittenLike 和 TigerLike 的接口。最后我们大概会写出这样的东西:

protocol PetLike: KittenLike, DogLike {

}

protocol CatLike: KittenLike, TigerLike {

}

struct SoundChecker {
    static func checkPetTalking(pet: PetLike) {
        //...
    }

    static func checkCatTalking(cat: CatLike) {
        //...
    }
}

虽然没有引入定义任何新的内容,但是为了实现这个需求,我们还是添加了两个空 protocol,这可能会让人困惑,代码的使用者 (也包括一段时间后的你自己) 可能会去猜测 PetLike 和 CatLike 的作用 -- 其实它们除了标注以外并没有其他作用。借助 protocol 组合的特性,我们可以很好的解决这个问题。protocol 组合是可以使用 typealias 来命名的,于是可以将上面的新定义 protocol 的部分换为:

typealias PetLike = protocol<KittenLike, DogLike>
typealias CatLike = protocol<KittenLike, TigerLike>

这样既保持了可读性,也没有多定义不必要的新类型。

另外,其实如果这两个临时接口我们就只用一次的话,如果上下文里理解起来不会有困难,我们完全可以直接将它们匿名化,变成下面这样:

struct SoundChecker {
    static func checkPetTalking(pet: protocol<KittenLike, DogLike>) {
        //...
    }

    static func checkCatTalking(cat: protocol<KittenLike, TigerLike>) {
        //...
    }
}    

这样的好处是定义和使用的地方更加接近,这在代码复杂的时候读代码时可以少一些跳转,多一些专注。但是因为使用了匿名的接口组合,所以能表达的信息毕竟少了一些。如果要实际使用这种方法的话,还是需要多多斟酌。

虽然这一节已经够长了,不过我还是想多提一句关于实现多个接口时接口内方法冲突的解决方法。因为在 Swift 的世界中没有人限制或者保证过不同接口的方法不能重名,所以这是有可能出现的情况。比如有 A 和 B 两个接口,定义如下:

protocol A {
    func bar() -> Int
}

protocol B {
    func bar() -> String
}

两个接口中 bar() 只有返回值的类型不同。我们如果有一个类型 Class 同时实现了 A 和 B,我们要怎么才能避免和解决调用冲突呢?

class Class: A, B {
    func bar() -> Int {
        return 1
    }

    func bar() -> String {
        return "Hi"
    }
}

这样一来,对于 bar(),只要在调用前进行类型转换就可以了:

let instance = Class()
let num = (instance as A).bar()  // 1
let str = (instance as B).bar()  // "Hi"

相关推荐

添加新评论

网站状态

  • 栏目分类:49个
  • 发布文章:1542篇
  • 用户评论:798条
  • 开博至今:4221天

正则速查

[abc] 匹配中括号中的单个字符,如a或b或c
[^abc] 匹配除了a、b、c等字符的其他单个字符
[a-z] 匹配一个字符范围,如a到z
[a-zA-Z] 匹配一个字符范围,如a-z 或 A-Z
^ 匹配行的开始
$ 匹配行的结束
\A 匹配一个字符串的开始
\z 匹配一个字符串的结束
. 匹配任意单个字符
\s 匹配空白字符,如空格,TAB
\S 匹配非空白字符
\d 匹配一个数字
\D 匹配非数字
\w 匹配一个字母
\W 匹配非字母
\b 匹配字符边界
(...) 引用所有括号中的内容
(a|b) a或者b
a? 零个或1个a
a* 零个或多个a
a+ 1个或多个a
a{3} 3次重复的a
a{3,} 3次或3次以上重复的a
a{3,6} 3到6次重复的a

修正符

/g 查找所有可能的匹配
/i 不区分大小写
/m 多行匹配
/s 单行匹配
/x 忽略空白模式
/e 可执行模式,PHP专有
/A 强制从目标字符串开头匹配
/D 使用$限制结尾字符,则不允许结尾有换行
/U 只匹配最近的一个字符串;不重复匹配

最新回复

  • : 感谢,我现在才知道还有快捷键这个东东!!!
  • 1: 天书啊
  • memory: 可以去官方网站找找手册了,好久不玩它了.
  • xiaoyan: 怎么解决的啊
  • 银行建设: 生产环境应该怎么搭建redis集群呢?
  • memory: 好久没折腾WIN了?不确认哟。
  • 虫虫: tomcat apache nginx能装吗
  • 咚咚: 开源的放一个吧
  • memory: 好几年不更新了。。。
  • liqitian: 不能用呀
  • 广州网站建设: 了解
  • memory: 这是测试,不建议正式环境这样使。
  • 广州网站建设: 构建redis集群时候,不要使用生产环境
  • memory: 照着大差不着的改一下就成了rewrite ^/sort/([0-...
  • mage: 按照上面设置的伪静态url是类似这种形式的 /listinfo-...
  • mage: 比如这种URL:帝国7.5伪静态如何可以做到这种形式(拼音或者英...
  • memory: 额,难住我了,哈哈。我项目中没有用过这么复杂的。