2016年WWDC-UIKit 中协议与值类型编程实战,Swift定义新类型

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
查看查看116 回复回复2 收藏收藏 分享淘帖 转播转播 分享分享 微信
查看: 116|回复: 2
收起左侧

2016年WWDC-UIKit 中协议与值类型编程实战,Swift定义新类型

[复制链接]
swifter 发表于 2016-6-30 01:19:01 | 显示全部楼层 |阅读模式
快来登录
获取优质的苹果资讯内容
收藏热门的iOS等技术干货
拷贝下载Swift Demo源代码
订阅梳理好了的知识点专辑

本文为 WWDC 2016 Session 419 的部分内容笔记。强烈推荐观看。




1、设计师来需求了

在我们的 App 中,通常需要自定义一些视图。例如下图:

2016年WWDC-UIKit 中协议与值类型编程实战 1

2016年WWDC-UIKit 中协议与值类型编程实战,Swift定义新类型 - 敏捷大拇指 - 2016年WWDC-UIKit 中协议与值类型编程实战 1


我们可能会在很多地方用到右边为内容,左边有个装饰视图的样式,为了代码的通用性,我们在 UITableViewCell 的基础上,封装了一层 DecoratingLayout,然后再让子类继承它,从而实现这一类视图。

[Swift] 纯文本查看 复制代码
class DecoratingLayout : UITableViewCell {
    var content: UIView
    var decoration: UIView

    // Perform layout...
}





2、重构

但是代码这样组织的话,因为继承自 UITableViewCell,所以对于其他类型的 view 就不能使用了。我们开始重构。

2016年WWDC-UIKit 中协议与值类型编程实战 2

2016年WWDC-UIKit 中协议与值类型编程实战,Swift定义新类型 - 敏捷大拇指 - 2016年WWDC-UIKit 中协议与值类型编程实战 2


我们需要让视图布局的功能独立与具体的 view 类型,无论是 UITableViewCell、UIView、还是 SKNode(Sprite Kit 中的类型)

[Swift] 纯文本查看 复制代码
struct DecoratingLayout {
    var content: UIView
    var decoration: UIView

    mutating func layout(in rect: CGRect) {
        // Perform layout...
    }
}


这里,我们使用结构体 DecoratingLayout 来表示这种 layout。相比于之前的方式,现在只要在具体的实现中,创建一个 DecoratingLayout 就可以实现布局的功能。代码如下:

[Swift] 纯文本查看 复制代码
class DreamCell : UITableViewCell {
   ...

    override func layoutSubviews() {
        var decoratingLayout = DecoratingLayout(content: content, decoration: decoration)
        decoratingLayout.layout(in: bounds)
    } 
}

class DreamDetailView : UIView {
   ...

    override func layoutSubviews() {
        var decoratingLayout = DecoratingLayout(content: content, decoration: decoration)
        decoratingLayout.layout(in: bounds)
    } 
}


注意观察上面的代码,在 UITableViewCell 和 UIView 类型的 view 中,布局功能和具体的视图已经解耦,我们都可以使用 struct 的代码来完成布局功能。

通过这种方式实现的布局,对于测试来说也更加的方便:

[Swift] 纯文本查看 复制代码
func testLayout() {
    let child1 = UIView()
    let child2 = UIView()
    var layout = DecoratingLayout(content: child1, decoration: child2)
    layout.layout(in: CGRect(x: 0, y: 0, width: 120, height: 40))

    XCTAssertEqual(child1.frame, CGRect(x: 0, y: 5, width: 35, height: 30))
    XCTAssertEqual(child2.frame, CGRect(x: 35, y: 5, width: 70, height: 30))
}


我们的野心远不止于此。这里我们也想要在 SKNode 上使用上面的布局方式。看如下的代码:

[Swift] 纯文本查看 复制代码
struct ViewDecoratingLayout {
    var content: UIView
    var decoration: UIView

    mutating func layout(in rect: CGRect) {
        content.frame = ...
        decoration.frame = ...
    }
}

struct NodeDecoratingLayout {
    var content: SKNode
    var decoration: SKNode

    mutating func layout(in rect: CGRect) {
        content.frame = ...
        decoration.frame = ...
    }
}


注意观察上面的代码,除了 content 和 decoration 的类型不一样之外,其他的都是重复的代码,重复就是罪恶!

那么我们如何才能消除这些重复代码呢?在 DecoratingLayout 中,唯一用到 content 和 decoration 的地方,是获取它的 frame 属性,所以,如果这两个 property 的类型信息中,能够提供 frame 就可以了,于是我们想到了使用 protocol 作为类型(type)来使用。

[Swift] 纯文本查看 复制代码
protocol Layout {
    var frame: CGRect { get set }
}


于是上面两个重复的代码片段又可以合并为:

[Swift] 纯文本查看 复制代码
struct DecoratingLayout {
    var content: Layout
    var decoration: Layout

    mutating func layout(in rect: CGRect) {
        content.frame = ...
        decoration.frame = ...
    }
}


为了能够在使用 DecoratingLayout 的时候传入 UIView 和 SKNode,我们需要让它们遵守 Layout 协议,只需要像下面这样声明一下就可以了,因为二者都已满足协议的要求。

[Swift] 纯文本查看 复制代码
extension UIView: Layout {}
extension SKNode: Layout {}


这里讲一点我自己的理解,DreamCell 和 DreamDetailView 中能够使用同一套布局代码,是因为传递进去的 view 都拥有公共的父类 UIView,它提供了 frame 信息,而 UIView 和 SKNode 则不行,这里我们使用 protocol 作为类型参数,可以很好的解决这一问题。





3、引入范型

然而,目前的代码中是存在一个问题的,content 和 decoration 的具体类型信息在实际中可能是不一致的,因为这里我们只要求了它们的类型信息中提供 frame 属性,而并没有规定它们是相同的类型,例如 content 可能是 UIView 而 decoration 是 SKNode 类型,这与我们的期望是不符的。

这里我们可以通过引入范型来解决:

[Swift] 纯文本查看 复制代码
struct DecoratingLayout<Child: Layout> {
    var content: Child
    var decoration: Child

    mutating func layout(in rect: CGRect) {
        content.frame = ...
        decoration.frame = ...
    }
}


通过使用范型,我们就保证了 content 和 decoration 类型相同。




4、需求又来啦

设计师说,来,小伙子,完成下面的布局。

2016年WWDC-UIKit 中协议与值类型编程实战 3

2016年WWDC-UIKit 中协议与值类型编程实战,Swift定义新类型 - 敏捷大拇指 - 2016年WWDC-UIKit 中协议与值类型编程实战 3


为了实现上图的效果,我们仿照之前的写法,实现如下代码:

[Swift] 纯文本查看 复制代码
struct CascadingLayout<Child: Layout> {
    var children: [Child]
    mutating func layout(in rect: CGRect) {
        ...
    }
}


2016年WWDC-UIKit 中协议与值类型编程实战 4

2016年WWDC-UIKit 中协议与值类型编程实战,Swift定义新类型 - 敏捷大拇指 - 2016年WWDC-UIKit 中协议与值类型编程实战 4


[Swift] 纯文本查看 复制代码
struct DecoratingLayout<Child: Layout> {
    var content: Child
    var decoration: Child

    mutating func layout(in rect: CGRect) {
        content.frame = ...
        decoration.frame = ...
    }
}


这里我又将前面的代码拿了过来,方便查看。

我们将上面的两种布局方式组合起来,就可以得到下面的效果:

2016年WWDC-UIKit 中协议与值类型编程实战 5

2016年WWDC-UIKit 中协议与值类型编程实战,Swift定义新类型 - 敏捷大拇指 - 2016年WWDC-UIKit 中协议与值类型编程实战 5





5、组合优于继承

那么如何才能将两种布局方式组合起来呢?

来观察我们之前定义的协议 Layout,其实我们关心的并不是 Layout 中的 frame,我们的目的是,让 Layout 能够在特定的上下文中进行相应的布局,所以我们来修改代码:

[Swift] 纯文本查看 复制代码
protocol Layout {
    mutating func layout(in rect: CGRect)
}


这里 Layout 的语义变成了:该类型能够在特定的 CGRect 中进行相应的布局。

同时我们也需要修改代码:

[Swift] 纯文本查看 复制代码
extension UIView: Layout { ... }
extension SKNode: Layout { ... }


这里省略了使用 UIView 和 SKNode 的 frame 来进行布局的代码。
于是我们的代码变成了:

[Swift] 纯文本查看 复制代码
struct DecoratingLayout<Child : Layout> : Layout { ... }
struct CascadingLayout<Child : Layout> : Layout { ... }


看到这里可能有点晕,其实代码表达的意思是,DecoratingLayout 遵循 Layout 协议,而它的 content 和 decoration 两个 property 也同样遵循该协议,即可以在特定的 CGRect 中完成布局操作。而两个结构体本身就包含 layout 操作,所以不需要任何其他的代码,结构体做的事情就是,在自己进行 layout 操作的基础上,将其传递给两个 property 然后分别进行 layout,这就完成了组合

组合之后的执行代码如下:

[Swift] 纯文本查看 复制代码
let decoration = CascadingLayout(children: accessories) // 左边
var composedLayout = DecoratingLayout(content: content, decoration: decoration) // 整体
composedLayout.layout(in: rect) // 执行 layout 操作





6、On step further

2016年WWDC-UIKit 中协议与值类型编程实战 6

2016年WWDC-UIKit 中协议与值类型编程实战,Swift定义新类型 - 敏捷大拇指 - 2016年WWDC-UIKit 中协议与值类型编程实战 6


注意观察上面的视图,视图是有层次结构的,所以我们需要在布局的时候,能够拿到这个子视图数组,之前的视实现方式中,只能布局单个的视图,没有办法拿到整个视图数组进行操作。

我们来修改 Layout 的代码:

[Swift] 纯文本查看 复制代码
protocol Layout {
    mutating func layout(in rect: CGRect)
    var contents: [Layout] { get }
}


这里增加了一个可读属性,返回一个 Layout 数组。同样,这里的代码存在一个问题,contents 可以为不同的 Layout 类型,例如 [UIView(), SKNode()],所以为了让 contents 中的类型一致,我们使用 associatedtype,将上面的代码改写为:

[Swift] 纯文本查看 复制代码
protocol Layout {
    mutating func layout(in rect: CGRect)
    associatedtype Content
    var contents: [Content] { get }
}


相应的 struct 改为:

[Swift] 纯文本查看 复制代码
struct ViewDecoratingLayout : Layout {
   ...
   mutating func layout(in rect: CGRect)
   typealias Content = UIView
   var contents: [Content] { get }
}

struct NodeDecoratingLayout : Layout {
   ...
   mutating func layout(in rect: CGRect)
   typealias Content = SKNode
   var contents: [Content] { get }
}


重复就是罪恶啊!可以看到,这里唯一的不同只是 Content 的类型信息。这里我们还是利用强大的范型来解决:

[Swift] 纯文本查看 复制代码
struct DecoratingLayout<Child : Layout> : Layout {
   ...
   mutating func layout(in rect: CGRect)
   typealias Content = Child.Content
   var contents: [Content] { get }
}


这里,当 Child 范型确定的时候,Child.Content 的类型信息也相应地确定了,所以可以使用上面的代码来消除重复。

范型牛逼!*3

别激动的太早,我们的代码中还存在一个问题。目前我们的代码长这样:

[Swift] 纯文本查看 复制代码
struct DecoratingLayout<Child : Layout> : Layout {
    var content: Child
    var decoration: Child
    mutating func layout(in rect: CGRect)
    typealias Content = Child.Content
    var contents: [Content] { get }
}


这里的 content 和 decoration 使用的是同样的 layout 方式,这与我们的预期是不符的。我们的需求时视图左边和右边使用不同的布局方式。然而我们又需要这个范型的方式来保证它们俩实际的数据类型是相同的,这里需要使用两个范型信息,但是限制它们的实际数据类型相同。修改后的代码如下:

[Swift] 纯文本查看 复制代码
struct DecoratingLayout<Child : Layout, Decoration : Layout
                                where Child.Content == Decoration.Content> : Layout {
    var content: Child
    var decoration: Decoration
    mutating func layout(in rect: CGRect)
    typealias Content = Child.Content
    var contents: [Content] { get }
}


以上。

再一次,推荐你在写 Swift 中定义新类型的时候,把 class 抛在脑后,尝试着从 struct 和 protocol 开始。

Happy Hacking!




作者:Prayer_

都看到这里了,就把这篇资料推荐给您的好朋友吧,让他们也感受一下。

回帖是一种美德,也是对楼主发帖的尊重和支持。

*声明:敏捷大拇指是全球最大的Swift开发者社区、苹果粉丝家园、智能移动门户,所载内容仅限于传递更多最新信息,并不意味赞同其观点或证实其描述;内容仅供参考,并非绝对正确的建议。本站不对上述信息的真实性、合法性、完整性做出保证;转载请注明来源并加上本站链接,敏捷大拇指将保留所有法律权益。如有疑问或建议,邮件至marketing@swifthumb.com

*联系:微信公众平台:“swifthumb” / 腾讯微博:@swifthumb / 新浪微博:@swifthumb / 官方QQ一群:343549891(满) / 官方QQ二群:245285613 ,需要报上用户名才会被同意进群,请先注册敏捷大拇指

嗯,不错!期待更多好内容,支持一把:
支持敏捷大拇指,用支付宝支付10.24元 支持敏捷大拇指,用微信支付10.24元

评分

参与人数 1金钱 +10 贡献 +10 专家分 +10 收起 理由
Anewczs + 10 + 10 + 10 32个赞!专家给力!

查看全部评分

SwiftRobot 发表于 2016-6-30 10:12:19 | 显示全部楼层
学习了~
小开开 发表于 2016-6-30 10:42:29 | 显示全部楼层
重复就是罪恶!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

做任务,领红包。
我要发帖

分享扩散

都看到这里了,就把这资料推荐给您的好朋友吧,让他们也感受一下。
您的每一位朋友访问此永久链接后,您都将获得相应的金钱积分奖励
关闭

站长推荐 上一条 /3 下一条

热门推荐

合作伙伴

Swift小苹果

  • 北京治世天下科技有限公司
  • ©2014-2016 敏捷大拇指
  • 京ICP备14029482号
  • Powered by Discuz! X3.1 Licensed
  • swifthumb Wechat Code
  •   
快速回复 返回顶部 返回列表