探索原生Swift的模式

▲点击上方“CocoaChina”关注即可免费学习 iOS 开发


译者: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,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的人,你的代码能否让这些人轻易理解?他们能否看懂你的代码是在尝试解决哪个问题?


微信号:CocoaChinabbs


▲长按二维码“识别”关注即可免费学习 iOS 开发

月薪十万、出任CEO、赢娶白富美、走上人生巅峰不是梦

--------------------------------------

投稿邮箱:support@cocoachina.com

if (indexPath.section == 0) {
  ifrow {
    celltextLabeltext = @"Twitter"
  } else 1@"Blog"
  else @"Contact Us"
  }@"nickoneill"
  @"objctoswift"
  @"@whyareyousodumb"
  }

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。