探索原生Swift的模式

译者:Christian C

来源:SDK.cn

原文:Discovering Native Swift Patterns


模式(Patterns)是你首选的代码,在使用其他语言的时候,你一定已经对它有了很深的理解。但是当一个具有独特句法和功能的新语言出现之后,你能马上了解它的模式吗?我们必须要发现这个新语言当中的模式;何时应该运用旧有的知识,以及何时应该学习新的知识。


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


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


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


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


Swift中的模式


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


静态单元格


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


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"

}

}


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


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下,解决同样的问题,代码就会变成这样。代码变短了,也更清晰了,哪个编程人员不喜欢这样的代码?


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


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来创建这个枚举对象。


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


我们对最佳Swift模式的看法将会随时间改变


何为最佳Swift模式?


我对这个问题的看法曾经出现过改变,这种改变让我自己都非常惊讶,在使用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。


在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下继续深化,对不对?我们可能太乐观了。


{ /* do some task */ } ~> { /* update some UI */ }


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


所谓优秀的模式,要在维持简洁性的同时提供方便性


优秀的模式,要在维持代码简洁度的同时,为你提供方便。我们所瞄准的,应该是那些第一次写代码的人,那些第一次接触到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里。我和所有人一样,对于出现这样的模式负有一定责任。尽管这里只有两个子视图,但是已经出现了难以控制的趋势。如果视图的数量增多,或是视图控制器变得复杂一些,这种编程方式会很快失去控制。在这样的情况下,我们能做点什么?


在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拆分成独立的初始化闭包,这样一来,你就再也不用猜测每个区域内究竟发生了什么事了。如果在初始化闭包中,它就是在配置视图,返回视图。


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


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


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


@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

}

}


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


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


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


override func viewDidAppear(animated: Bool) {

super.viewDidAppear(animated)

if let paths = tableView.indexPathsForSelectedRows {

for path in paths {

tableView.deselectRowAtIndexPath(path, animated: true)

}

}

}


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


tableView.indexPathsForSelectedRows?.forEach({ (path) in tableView.deselectRowAtIndexPath(path, animated: true )})


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


tableView.indexPathsForSelectedRows?.forEach{

tableView.deselectRowAtIndexPath($0, animated: true )

}


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


简单的模式可以替代大量的依赖


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


let json = try? NSJSONSerialization.JSONObjectWithData(data, options: [])

if let object = json as? Dictionary<String, AnyObject>,

places: [Place] = Unbox(object) {

return places

}


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


struct Place: Unboxable {

let id: String

let address: Int?

let text: String

init(unboxer: Unboxer) {

self.id = unboxer.unbox("id")

self.address = unboxer.unbox("address")

self.placeName = unboxer.unbox("place_name")

}

}


我们必须使用专门的初始化程序来建立结构体。然而你只是得到了一些基本的数据,上面缺少了很多额外的代码,你只能自己去写这些缺失的代码。而如果你可以使用可失败构造器模式的话,你就可以获得更好的效果。在对特定数据进行拆箱的时候,其他的代码也又可能会默认出现。


struct Place {

// ...

init?(json: Dictionary<String, AnyObject>) {

guard let id = json["_id"] as? String else {

return nil

}

self.id = id

self.address = json["address"] as? Int

self.placeName = (json["name"] as? String) ?? "No place name"

}

}


而如果这个过程失败了,你也可以清楚的知道去哪里排除错误,找到究竟是哪里出现了问题。而不需要像在框架里排除错误那样去在某个选择器上设定断点。


架构是一种需要时间积累才能学会的东西,将大规模的架构组合在一起,需要更多的时间。在编程生涯中,你将会获得许多机会,来开发自己的架构决策技巧,这些技巧之后会变成模式,让你运用在未来的工作中,例如网络、核心数据等,还有与应用交互的方式,例如通知中心和KVO委托等。


模式是好东西,我们都热爱模式,我们总是可以不断丰富它,你也可以打造属于自己的模式,现在你要做的就是学会如何开始使用和创建模式。


寻找新模式的技巧


1. 培养自己对代码的直觉。任何你所恐惧的代码都可以变得简单。任何看上去类似Objective?C的代码,都是一个机会,你很有可能把它变成看上去更直观的东西,正因为次,Swift才会成为更适合你的编程语言。如果项目不着急,有时间的时候你完全可以自己去试验一下,这种试验对你是大有裨益的。


2. 在Swift中打开一个playground,或是新建一个项目,在上面尝试一些新的模式。聚沙成塔,你将会最终利用这些新的技能开始一项真正的项目。即使你先发现的模式并不符合当前项目的需要,这对你来说也是一次实验,一个学习的过程。未来不一定什么时候你就可以用上这些模式。


3. 重新阅读语言向导说明书。这份说明是事实上有着密度极大的内容,在你刚开始学习Swift这种语言的时候,你可能会觉得这份说明书将的东西都非常基础,但是里面其实隐藏着很多宝藏,你在最初的时候很可能没有意识到这些东西的存在。当你带着疑问再去看这份说明书的时候,你就会发现它的价值。在实践了一段时间Swift之后,有的时候只是不经意的一撇,这份说明书就能够让你对Swift的理解更加深刻一些。我个人就经历了这个过程,在遇到问题的时候我就会重新读一篇这份指导,然后在里面找到解决问题的办法。我总是在想:“这份向导说明太实用了。我以前怎么没实用它?”


4. 时刻牢记保持代码的可读性。你很可能会陷入简化代码的漩涡中无法自拔。但是你应该想想那些第一次使用Swift的人,你的代码能否让这些人轻易理解?他们能否看懂你的代码是在尝试解决哪个问题?


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值