探索原生Swift的模式 Discovering Native Swift Patterns

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

探索原生Swift的模式 Discovering Native Swift Patterns

[复制链接]
swifter 发表于 2016-7-7 06:28:19 | 显示全部楼层 |阅读模式
快来登录
获取优质的苹果资讯内容
收藏热门的iOS等技术干货
拷贝下载Swift Demo源代码
订阅梳理好了的知识点专辑
模式(Patterns)是你首选的代码,在使用其他语言的时候,你一定已经对它有了很深的理解。但是当一个具有独特句法和功能的新语言出现之后,你能马上了解它的模式吗?我们必须要发现这个新语言当中的模式;何时应该运用旧有的知识,以及何时应该学习新的知识。

在这篇文章中,我将会谈到Objective-C(以及其他语言)中的普遍模式,并且在Swift中找到它的模式。

Patterns are your go-to code, the things you know exactly how to do in other languages because you’ve done them many times before. But what happens when a new language is released with its own unique syntax and features? We have to discover the patterns that feel at home in this new language; when to stick with what we know and when to branch out and do something new and different. This talk will cover some common patterns you might see in Objective‑C (and other languages) and developing equivalent patterns using clear, concise code through the lens of native Swift.

探索原生Swift的模式 Discovering Native Swift Patterns

探索原生Swift的模式 Discovering Native Swift Patterns - 敏捷大拇指 - 探索原生Swift的模式 Discovering Native Swift Patterns





1、介绍 Introduction

我是Nick O’Neill,今天我们要学习如何发现Swift模式。

设计模式总的来说,是编程中的一个组成部分,它可以解决一个非常具体的问题。应用正是由各种各样的这些模式所组成的。

一个简单的模式可以是这样的:通过一次点击,应用就进入下一屏。而复杂一些的模式则是那些你用来获取核心数据的东西。一名优秀的编程人员,就必须要知道哪种模式可以解决哪种问题。但是这些模式并不是静止不动的,尤其是当一种新的编程语言出现的时候,例如Swift,我们就要重新审视这些模式,看看这些模式能否被运用在新的语言中。

Thank you very much. I’m Nick O’Neill, and this is Discovering Swift Patterns.

Design patterns are generally small pieces of code that solve a very particular problem. Apps are made up of lots of these patterns all sort of put together.

A simple pattern can be where a tap takes you to the next screen. Something larger is something that you would use to manage access to core data. The idea may be that being a good programmer is knowing what patterns to use for which problems. But these patterns aren’t necessarily static, and particularly when a new language comes along, like Swift, we need to think about how to reexamine these patterns that we’re using in the context of another language and the language’s tools that are available to us.




2、Swift中的模式 That Thing in Swift

我写过一篇名叫《That Thing in Swift》的博客,那时我还是一名Objective-C开发人员。当Swift出现的时候,我就开始考虑这个问题,将Objective-C中的模式转移到Swift中。

I write a blog called That Thing in Swift, and I started it as an Objective‑C developer. When Swift came out, and I wanted to think about ways that we could take patterns in Objective‑C and translate them to Swift.


2.1、How can I express what I’m doing more clearly?

We kept on finding new ways to express what we wanted to say in more expressive ways with Swift. How can I express what I’m doing more clearly? So not just you when you come back to this code later on can understand what’s going on, but maybe you’re working with somebody else, and when you’re developing your code, you want them to be able to come in and pick right up where you left off and make changes.




3、静态单元格 Static Table Cell

这是一个基本的静态单元格视图。

This is a basic static table view.



3.1、基本方法?Objective-C下的表达方式 The Objective‑C way

[Objective-C] 纯文本查看 复制代码
if (indexPath.section == 0) {
  if(indexPath.row == 0) {
    cell.textLabel.text = @"Twitter"
  } else if (indexPath.row == 1) {
    cell.textLabel.text = @"Blog"
  } else {
    cell.textLabel.text = @"Contact Us"
  }
} else {
  if(indexPath.row == 0) {
    cell.textLabel.text = @"nickoneill"
  } else if (indexPath.row == 1) {
    cell.textLabel.text = @"objctoswift"
  } else {
    cell.textLabel.text = @"@whyareyousodumb"
  }
}


你需要不断的拆分这些段落和索引行,而且这段代码中有着大量的嵌套,看上去让人晕晕乎乎的,如果你在选择了这样的写法,那么在之后的编码过程中,你就要不断地复制这段代码。于是,代码的体积就会异常庞大,内容也会显得非常杂乱,编程人员肯定不会喜欢这样的事情。

You’d key off of all these sections and row indexes, and there’s lots of nesting going on and it’s confusing, and if you’re doing things after you select these cells, then you have a duplicate of all this stuff right below this. So, it’s a giant mess, and really not something we wanna do.



3.2、更好一点?Swift! A better? Swift way

[Swift] 纯文本查看 复制代码
let shortPath = (indexPath.section, indexPath.row)
switch shortPath {
  case (0,0):
    cell.textLabel.text = "Twitter"
  case (0,1):
    cell.textLabel.text = "Blog"
  case (0,2):
    cell.textLabel.text = "Contact Us"
  case (1,0):
    cell.textLabel.text = "@nickoneill"
  case (1,1):
    cell.textLabel.text = "@objctoswift"
  case (1,2):
    cell.textLabel.text = "@whyareyousodumb"
  default:
    cell.textLabel.text = " ̄\\_(θ)_/ ̄"
}


而在Swift下,解决同样的问题,代码就会变成这样。代码变短了,也更清晰了,哪个编程人员不喜欢这样的代码?

And a better Swift way might be to do it like this. We make a short little tuple out of the section and the row, and then we switch on that. Is this better?

所有的section都整齐的排列,你可以轻松的分辨section和row。如果你看到了枚举之外的语句,你也许应该考虑一下它对枚举会起到什么样的作用。

We have all the sections right on the left there. Everything is lined up. You can sort of see the distinction between the sections and the rows, and it’s a little more clear. If you do see a switch statement outside of an enum maybe you should at least perhaps think about how it would work if you had an enum involved.



3.3、最极致的写法?Swift! The best? Swift way

[Swift] 纯文本查看 复制代码
enum TwitterHandles: Int {
  case Nickoneill
  case Objctoswift
  case Whyareyousodumb

  func labelText() -> String {
    switch self {
      ...
    }
  }
}

let rowData = TwitterHandles(rawValue: indexPath.row)
cell.textLabel.text = rowData.labelText()


Swift的编写方式显然要比Objective-C更好。这段代码中有一个枚举,一个整数的原始数值,它代表了Tabel view cell中的一个section,这样就保留了这里的指令,每一次当我们创建一个Table cell的时候,我们其实都是在使用当前正在处理的row来创建这个枚举对象。

This is a better way to do it. So here we have an enum, a raw value with an integer, and it represents one section of our table view cell, thus preserving the order there, and every time we create a table cell, we’re creating this enum object with the row that we’re handing.

之后,我们会命令枚举对象生成适当的单元值。我们放弃的不仅仅是这些单元对于枚举的命令,还有这些单元在枚举中组织起来的内容。这样,我们就将所有东西放在了一起,从而让代码变得简洁。同时,如果我们想要在中间添加新的代码,也不用像在使用Objective-C语言的时候一样,对所有section和索引进行调整。

And then we ask the enum object for the proper cell values. We’re relinquishing not just the order of these cells to the enum, but also the content for all of these cells is organized in the enum, and we gain the clarity of having this all in one location, but also the bonus of being able to add a new case right in the middle there without having to adjust all of our sections and indexes like we would have to do in the Objective‑C way or the naive Swift way.




4、我们对最佳Swift模式的看法将会随时间改变 Our Idea of the Best Swift Pattern Will Change Over Time

何为最佳Swift模式?

我对这个问题的看法曾经出现过改变,这种改变让我自己都非常惊讶,在使用Swift的过程中,我不断遇上优秀的模式,它们有点像是隐藏在这种语言中的秘密。刚开始你在使用某种方法解决一个问题,因为你已经对这种方式非常熟悉。但是突然有一天,在和另一个人的合作过程当中,他教了你另一种解决问题的方式,你一定会非常惊讶。在学会一种新模式之后,你一定迫不及待的想要使用它,你会觉得这个模式可以在各个地方都适用。但是事实并非如此,并不是我们所做的每一次改进,都能成为优秀的模式。将复杂的代码简化成一行代码,并不一定能让它成为更好的东西,它并不是放之四海而皆准的定律。

I was surprised that my idea of what is the best Swift changed over time, and these great patterns that we run into, they feel like little hidden secrets in the language, and you start solving a problem one way, and you just keep solving it that way, because it’s very familiar and you sort of go along not knowing that there’s anything else, and then one day you’re pairing with somebody and they show you an entire different approach to a problem, and it feels amazing, right? Of course, this is a particularly dangerous trap to fall into, because it’s super tempting to just wanna use that new pattern that you learned that feels amazing absolutely everywhere, and the truth is, not every piece of clever code, not every improvement that we make, is a really great pattern. Just because we can simplify something to, say, a single line of code, doesn’t necessarily make it better, it definitely doesn’t make it clearer in all the cases.




5、并不是每一条简化后的代码都是优秀的模式 Not Every Piece of Clever Code is a Great Pattern

[Swift] 纯文本查看 复制代码
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  // do some task
  dispatch_async(dispatch_get_main_queue(), ^{
    // update some UI
  });
});


我们来看看Objective-C语言中的另一个例子。加入你正在后台线程代码中进行某种处理,而这个处理需要花费大量的时间。在你完成之后,你又回到主线程来升级UI。

So let’s take a look at another example in Objective‑C. You go off to the background thread to do some sort of processing that takes some significant amount of time, and then when you’re done, you come back to the main thread to update your UI.

在Swift下,我们可以用几行简短的句法完成这项工作,然后将其运用在许多地方。

In Swift, we can really easily wrap this up in a few short pieces of syntax so that we can use it in multiple places.

[Swift] 纯文本查看 复制代码
func mainToBackground(background:() -> (), main: () -> ()) {
  let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
  dispatch_async(dispatch_get_global_queue(priority, 0)) {
    background()
    dispatch_async(dispatch_get_main_queue()) {
      main()
    }
  }
}

mainToBackground({
  // do some task
}, {
  // update the ui
})


我们一共要进行两次闭包,一个在背景线程中运行。之后我们在进行第二次闭包,让它在主线程中运行。那么问题来了,你觉得我们能够在Swift下继续深化,对不对?我们可能太乐观了。

We take two closures, one we run on the background thread, once that’s finished we take the other closure, which we run on the main thread. And we’ve called this main to background. Now, the problem with this is that we can go further with Swift, right? We can get too fancy.

[Swift] 纯文本查看 复制代码
{ /* do some task */ } ~> { /* update some UI */ }


上面是一个自定义的操作符,左边的闭包运行在背景线程中,右边的闭包运行在主线程中,对不对?当然,但是它并不清楚其意图,如果你在左边或是右边的闭包里放入大量代码的话,这个操作符就会迷失方向。当你使用代码库的时候,如果你一定要学习这些不标准的模式,这并不是聪明的工作方式。我觉得你也不会喜欢这样的项目。这是好的模式吗?并不是,它只是看上去比较简洁而已。

Here’s a custom operator that executes the left hand side closure on the background thread and the right hand side closure on the main thread, right? Sure, it’s one line, but it’s not really clear what the intention is, and that little operator might get lost once you have any significant amount of code in either the left or the right hand side closure. And if you started to work on a code base and you came in and had to learn all these little non-standard patterns, that’s not super clear, and I think you would not enjoy working on that project very much. So is it clear? Not really, but sure, it’s concise.




6、所谓优秀的模式,要在维持简洁性的同时提供方便性 Great Patterns Provide Convenience While Maintaining Clarity

优秀的模式,要在维持代码简洁度的同时,为你提供方便。我们所瞄准的,应该是那些第一次写代码的人,那些第一次接触到Swift的人,要让这些人也能轻松理解这些模式。无论这些人最先接触的是哪种语言,他们已经被各种操作符搞得焦头烂额了。因此,我对于自定义操作的态度是:不要碰这种东西。

Great patterns provide convenience while maintaining clarity. We should be aiming for people who are even first time programmers, people who are coming to Swift for the first time, understanding this. They have a hard enough time adapting to operators like modulo, which are present in lots of languages. So that’s my general take on custom operators, just don’t.




7、建立能给视图控制器添加视图的模式 Setup Pattern for Adding Views to a View Controller

[Swift] 纯文本查看 复制代码
class ViewController: UIViewController {
  let imageView = UIImageView()
  let goButton = UIButton()

  override func viewDidLoad() {
    imageView.image = UIImage(named: "profile")
    view.addSubview(imageView)

    goButton.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
    goButton.setTitle("GO!", forState: .Normal)
    view.addSubview(goButton)
  }
}


上面是一个传统的设置模式,用来给视图控制器添加视图。但是它并不算是一个模式,甚至还比不上直接把所有东西都丢进viewDidLoad里。我和所有人一样,对于出现这样的模式负有一定责任。尽管这里只有两个子视图,但是已经出现了难以控制的趋势。如果视图的数量增多,或是视图控制器变得复杂一些,这种编程方式会很快失去控制。在这样的情况下,我们能做点什么?

Something like this is a traditional setup pattern for adding views to a view controller. It’s not much of a pattern as much as you just throwing everything into viewDidLoad, and I am as guilty of this as anybody else. Even here where there’s just two subviews, this is already getting sort of out of hand. You really can’t tell what’s happening when, and with a realistic number of views and even a moderately complex view controller, this gets out of hand very, very quickly. What can we do in this case?

在Swift中,初始化闭包是我最喜欢的模式之一。

Initialization closures are one of my favorite patterns in Swift.

[Swift] 纯文本查看 复制代码
class ViewController: UIViewController {
  let imageView: UIImageView = {
    let imageView = UIImageView()
    imageView.image = UIImage(named: "profile")
    return imageView
  }()

  let goButton: UIButton = {
    let button = UIButton()
    button.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
    button.setTitle("GO!", forState: .Normal)
    return button
  }()

  override func viewDidLoad() {
    view.addSubview(imageView)
    view.addSubview(goButton)
  }
}


在上面这段代码中,我们是要创建一个可以配置我们所需种类的闭包,并且将其返回。这就是这段代码的作用。当类被装载完成之后,它会被立即调用。它还可以轻松的将这个单一、巨大的viewDidLoad拆分成独立的初始化闭包,这样一来,你就再也不用猜测每个区域内究竟发生了什么事了。如果在初始化闭包中,它就是在配置视图,返回视图。

What we’re doing here is creating a closure that configures the type we want and returns it. That’s all it does. It’s called immediately when the class is loaded. And it’s super easy to break this single, monolithic viewDidLoad into all these separate initialization closures, and you never have to guess what’s going on in each area. If it’s in an initialization closure, it’s configuring a view, it’s returning a view.

我这样喜欢模式的原因,在于我终于不用把所有东西都丢进viewDidLoad里面了。我终于可以使用独立的视图生命周期,视图最初的意图本来就是这样的。

这样的代码编写方式够清晰吗?当然,它足够简洁吗?虽然它看上去有一些样板花,但是相比于杂乱的viewDidLoad,它还是作古简洁的。这些模式非常适合你用来配置视图,我也高度推荐。

Why I like this pattern so much is that I finally feel like I’ve escaped the confines of throwing everything into viewDidLoad. I’m finally using all these separate view lifecycle methods as they were originally intended, or at least as their named, right? So is it clear? For sure. Is it precise, or is it concise? It’s got a little bit of boilerplate associated with it, but I’d say, considering the mess of viewDidLoad, it’s probably awash at the end of the day. So these guys are great for organizing your view configuration. I highly recommend it.

我们还可以使用类似的模式来处理故事板视图配置。

We can use a very similar pattern to help with storyboard view configuration, as well.

[Swift] 纯文本查看 复制代码
@IBOutlet weak var arrivalLabel: UILabel! {
  didSet {
    arrivalLabel.text = "Arriving in 10 minutes".uppercaseString
    arrivalLabel.font = UIFont(name: "Lato", size: 11)
    arrivalLabel.textColor = UIColor.blueColor()
    arrivalLabel.textAlignment = .Center
    arrivalLabel.numberOfLines = 1
  }
}


故事板非常适合自动排版,但是它在依赖注入方面却不够好,而且非常不擅长处理视图配置。因此我更倾向于在故事板中加入大量的基本排版,然后在视图控制器中对视图进行配置。

Storyboards are great for auto layout, but they’re terrible for dependency injection, and they’re awful for view configuration. So I tend to do a lot of basic layout in storyboards, and then I keep the view configuration in these didSet blocks in the view controller.

在使用IBOutlet完成了UILabel的配置之后,它会运行在所有这些配置之上。

Once the UILabel gets set via the IBOutlet, runs through all this configuration.

可选值是一个非常好的工具,它可以用来对应用中的数据建模,但是同时它也会在相对简单的操作中添加大量代码。例如下面这个:

Optionals are super great for modeling data in your app, whether or not you’re expected to have data as it’s flowing through your app, but they can add a lot of bulk to relatively simple operations, like this one, where you’re saying, “Take these selected index paths, “and deselect all of them.”

[Swift] 纯文本查看 复制代码
override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)

  if let paths = tableView.indexPathsForSelectedRows {
    for path in paths {
      tableView.deselectRowAtIndexPath(path, animated: true)
    }
  }
}


加入你从另一个试图控制器中回到viewDidAppear里,你想要取消对所有索引路径的选择。虽然很好理解,但是在实际操作的时候,你会发现这里有很多额外的代码,这些代码与当前的操作无关。这些代码就是可选数组所带来的。我们可以使用很多工具来帮助我们处理这些代码。例如forEach就可以处理可选数组,我们可以用它来替代整个可选值。

You’re doing this, let’s say, in viewDidAppear as you’re doing here, where, let’s say, you’re coming back from another view controller, you wanna deselect all the index paths of those table view cells. And, it’s straightforward to understand, but it just feels like there’s more code here than is necessary for the basic thing that we’re doing. And it’s mostly because we have to handle this optional array. There’s no guarantee that the array is gonna return anything at all, so rather than it being empty, which would make sense to me, it’s also an optional. So there are a lot of built in tools that can help us out with this. ForEach takes optional arrays, so we can replace our entire optional check here just with forEach, and it does the natural thing.

[Swift] 纯文本查看 复制代码
tableView.indexPathsForSelectedRows?.forEach({ (path) in tableView.deselectRowAtIndexPath(path, animated: true )})


如果这里没有东西,它就不会进行任何操作,如果这里有索引路径,它就会给我们提供每一条路径,之后我们就可以对其进行取消选择操作了,然后移除一些不必要的注释。我们甚至还可以使用结尾闭包把它变得更简洁,用实参位置来替代实参名称。

If there’s nothing, it just takes no operations, and if there are index paths in there, it’ll give us each index path, and then we can deselect that, and we shave off some unnecessary annotation. We can make this even more compact using trailing closure, and admitting the argument name in favor of argument position.

[Swift] 纯文本查看 复制代码
tableView.indexPathsForSelectedRows?.forEach{
  tableView.deselectRowAtIndexPath($0, animated: true )
}


在这个例子中,我觉得这是一个可以接受的交换。很明显你得到了索引路径。这些路径只在一个情况下会被使用,那就是这个闭包更复杂的情况下。

And in this case, I actually think it’s an acceptable trade off. It’s clear that you’re getting index paths back. They’re only used in one obvious case. If this closure was any more complex,




8、简单的模式可以替代大量的依赖 Simple Patterns can Replace Large Dependencies

很多看上去很复杂的东西,其实它们原本并不需要这么复杂。很多时候,你并不需要一些体积很大的框架,因为一个简单的模式就可以起到相同的效果。下面是一个我非常喜欢的例子,它是一个有关JSON的例子:拆箱。

Plenty of things that feel like they should be complex aren’t necessarily complex, and you don’t always need some large, general purpose framework when a simple pattern will do. Here’s an example of a JSON parsing library that I love to use, Unbox.

[Swift] 纯文本查看 复制代码
let json = try? NSJSONSerialization.JSONObjectWithData(data, options: [])

if let object = json as? Dictionary<String, AnyObject>,
  places: [Place] = Unbox(object) {
    return places
  }


它有着很多优秀的功能,可以将复杂的JSON变成结构体。在处理网络问题上,我经常使用它。但是,在很多时候,我并不需要这样做。如果你正在使用网络管理员程序,做过JSON序列化的人都清楚,你需要提取数据,将其转化为某种对象,我们要验证它是某种代码字典,然后将其拆箱,之后及期望于它能给我提供结构清晰的结构体。

It has a lot of great features for turning complex JSON into structs. I do it all the time for network stuff. But, a lot of the time, I just don’t need to. It’s just not necessarily required. If you’re in your network manager here, this should be very clear for people who have done JSON serialization before, you’re taking your data, you’re turning it into some sort of an object, we’re verifying that it’s a dictionary, and then we pass it to Unbox and we hope it gives us a struct that’s appropriately typed.

[Swift] 纯文本查看</