您的位置:首页 > 移动开发 > Swift

探索原生Swift的模式

2016-07-18 17:17 253 查看
译者:Christian C来源:SDK.cn原文:DiscoveringNative 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 Nickoneillcase Objctoswiftcase Whyareyousodumbfunc 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 taskdispatch_async(dispatch_get_main_queue(), ^{// update some UI});});我们来看看Objective-C语言中的另一个例子。加入你正在后台线程代码中进行某种处理,而这个处理需要花费大量的时间。在你完成之后,你又回到主线程来升级UI。在Swift下,我们可以用几行简短的句法完成这项工作,然后将其运用在许多地方。func mainToBackground(background:() -> (), main: () -> ()) {let priority = DISPATCH_QUEUE_PRIORITY_DEFAULTdispatch_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".uppercaseStringarrivalLabel.font = UIFont(name: "Lato", size: 11)arrivalLabel.textColor = UIColor.blueColor()arrivalLabel.textAlignment = .CenterarrivalLabel.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: Stringlet address: Int?let text: Stringinit(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 = idself.address = json["address"] as? Intself.placeName = (json["name"] as? String) ?? "No place name"}}而如果这个过程失败了,你也可以清楚的知道去哪里排除错误,找到究竟是哪里出现了问题。而不需要像在框架里排除错误那样去在某个选择器上设定断点。架构是一种需要时间积累才能学会的东西,将大规模的架构组合在一起,需要更多的时间。在编程生涯中,你将会获得许多机会,来开发自己的架构决策技巧,这些技巧之后会变成模式,让你运用在未来的工作中,例如网络、核心数据等,还有与应用交互的方式,例如通知中心和KVO委托等。模式是好东西,我们都热爱模式,我们总是可以不断丰富它,你也可以打造属于自己的模式,现在你要做的就是学会如何开始使用和创建模式。寻找新模式的技巧1. 培养自己对代码的直觉。任何你所恐惧的代码都可以变得简单。任何看上去类似Objective?C的代码,都是一个机会,你很有可能把它变成看上去更直观的东西,正因为次,Swift才会成为更适合你的编程语言。如果项目不着急,有时间的时候你完全可以自己去试验一下,这种试验对你是大有裨益的。2. 在Swift中打开一个playground,或是新建一个项目,在上面尝试一些新的模式。聚沙成塔,你将会最终利用这些新的技能开始一项真正的项目。即使你先发现的模式并不符合当前项目的需要,这对你来说也是一次实验,一个学习的过程。未来不一定什么时候你就可以用上这些模式。3. 重新阅读语言向导说明书。这份说明是事实上有着密度极大的内容,在你刚开始学习Swift这种语言的时候,你可能会觉得这份说明书将的东西都非常基础,但是里面其实隐藏着很多宝藏,你在最初的时候很可能没有意识到这些东西的存在。当你带着疑问再去看这份说明书的时候,你就会发现它的价值。在实践了一段时间Swift之后,有的时候只是不经意的一撇,这份说明书就能够让你对Swift的理解更加深刻一些。我个人就经历了这个过程,在遇到问题的时候我就会重新读一篇这份指导,然后在里面找到解决问题的办法。我总是在想:“这份向导说明太实用了。我以前怎么没实用它?”4. 时刻牢记保持代码的可读性。你很可能会陷入简化代码的漩涡中无法自拔。但是你应该想想那些第一次使用Swift的人,你的代码能否让这些人轻易理解?他们能否看懂你的代码是在尝试解决哪个问题?

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  iOS开发 语言 swift