了解swift 3.0新特性

ready-for-siwft-3.jpg

在2016年的WWDC,swift 3.0随着iOS 10和Xcode 8一起发布,建议尽早升级swift 3.0,因为行动越早后面改动越少。使用Xcode 8适配swift 3.0的过程中,你会意识到swift 3.0有很大的改动,也许你认为从swift 1.2到2.0已经是很大的改动,我觉得这真的不算什么,因为你还没有见到swift 3.0的全貌,它的改动更多更大。

在这边文章,我将通过尽量多而全的代码示例向读者着重介绍swift 3.0主要改变和优化的地方,并希望在接下来的时间里,对你升级Xcode 8、适配swift 3.0有所帮助。除了以下所列的相对重要的改动,swift 3.0还有其他很多的改动,但是下面所列的改动对于你当前适配swift 3.0的工作会有很大的帮助。

Notes:

1: swift 3.0有很多很多的改动和优化,有些改动可以说琐碎而又微不足道,然而这一次swift 3.0的更新对于我们开发者而言也是一种新的希望,因为这些改动更像是swift诞生两年以来一次大而彻底的更新,它让swift更优秀更简洁,也意味着经过这一次大刀阔斧改动、优化之后,以后的swift版本更新应该将显著地减少。

2: 如果你还未阅读swift 2.2新特性的相关文章,建议你查阅相关文章,作简单了解,swift 2.2中舍弃(deprecated)的语法,例如 ++, --C语言风格的for循环等语法在swift 3.0中已经被移除了。

调用函数时写出所有的形参名称

我们调用函数的方式在swift 2.0时候已经有过一些改变,这一次swift 3.0更新再一次改变调用函数的语法,对之前的swift甚至可以说是一次颠覆。在swift 2.x和之前的版本中,调用方法func不需要显式写出第一个参数名称,所以大多数时候方法第一个参数名已经包含在方法名称中了,看看下面的代码示例,

// swift 2.x及之前的版本
names.indexOf("Taylor")
"Taylor".writeToFile("filename", atomically: true, encoding: NSUTF8StringEncoding)

SKAction.rotateByAngle(CGFloat(M_PI_2), duration: 10)
UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)

override func numberOfSectionsInTableView(tableView: UITableView) -> Int

func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView?

NSTimer.scheduledTimerWithTimeInterval(0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)

在新版本的swift 3.0中,方法调用时需要写出所有的参数名,当然我们可以在定义一个方法的使用_下划线,这也意味着以后调用该方法的时候不需要再写出参数名,参考下面的代码,

struct Person {
    func run(name: String) -> Void {
        print("\(name) running...")
    }
    
    func run(withName name: String) -> Void {
        print("\(name) running...")
    }
    
    func run(_ name: String) -> Void {
        print("\(name) running...")
    }
    
}

现在swift 3.0要求方法调用时候写出所有的参数名,下面的代码比较了swift 2.2和swift 3.0在方法调用时候语法的区别,

names.indexOf("Taylor")
names.index(of: "Taylor")

"Taylor".writeToFile("filename", atomically: true, encoding: NSUTF8StringEncoding)
"Taylor".write(toFile: "somefile", atomically: true, encoding: String.Encoding.utf8)

SKAction.rotateByAngle(CGFloat(M_PI_2), duration: 10)
SKAction.rotate(byAngle: CGFloat(M_PI_2), duration: 10)

UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
UIFont.preferredFont(forTextStyle: UIFontTextStyle.subheadline)

override func numberOfSectionsInTableView(tableView: UITableView) -> Int
override func numberOfSections(in tableView: UITableView) -> Int

func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView?
func viewForZooming(in scrollView: UIScrollView) -> UIView?

NSTimer.scheduledTimerWithTimeInterval(0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)
Timer.scheduledTimer(timeInterval: 0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)

需要注意的是,最后一个例子中,调用方法时,我们使用Timer取代了NSTimer,这是因为在swift 3.0中,SDK中一些基本的类型已经去除了NS前缀,例如FileManager, UserDefaults, Data, Date, URL, NSURLRequest, UUID, NotificationCenter等。

Swift支持命名空间,这相比Objective-C而言无疑是一种优化和进步。在swift中,可以使用命名空间来避免出现命名冲突,只要是来自不同的命名空间,类class名字即使相同也不会出现冲突。Swift命名空间的使用不是在一个项目中,而是需要跨项目使用,在同一个项目中都是同一个命名空间,此时全局变量和函数共享,不需要使用import导入。

大多数情况下,建议开发者调用方法时写出所有的参数名,但是一些系统方法又要求遵循以前的调用规则,即省略第一个参数名,因为系统方法在定义是使用了_下划线,我们来对比一下在swift 2.2和swift 3.0中,UIKit的一些方法还保持了一致,下面是swift 2.2中一些SDK方法的定义,

// swift 2.2
// 方法定义没有 _ 下划线
override func viewWillAppear(animated: Bool)
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
override func didMoveToView(view: SKView)
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?)
func textFieldShouldReturn(textField: UITextField) -> Bool

在swift 3.0中,上述方法在定义时第一个参数都使用了_下划线,这就表明了调用方法时候无需写出第一个参数名,下面是swift 3.0中一些SDK方法的定义,

// swift 3.0
// 为了保持调用与swift 2.2一致,使用 _ 下划线定义方法
override func viewWillAppear(_ animated: Bool)
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
override func didMoveToView(_ view: SKView)
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
func textFieldShouldReturn(_ textField: UITextField) -> Bool

删除多余的词汇

当swift在2015年12月开源的时候,它的新的简洁的API设计规范中包含三个重要的词语 - Ommit Needless Words,即删除多余的词汇。这样一个API设计规范给swift 3.0带来一个另一个颠覆性的改变,因为它意味着在新的API设计原则下,开发者在定义swift方法时应该删除方法名中明确的,不言而喻的(self-evident)的词汇,先通过下面的代码看一下swift 2.2 SDK中的方法名,

// swift 2.2 
// 方法中包含一些略显多余的描述性词汇
let blue = UIColor.blueColor()
let min = numbers.minElement()
attributedString.appendAttributedString(anotherString)
names.insert("Jane", atIndex: 0)
UIDevice.currentDevice()

你能识别上面代码中多余的词汇吗?当你使用UIColor时,理所当然地,blue就是你想要的颜色,所以使用blueColor()显得多余;当你添加一段富文本字符串到另一段字符串中,其实你并不需要通过方法appendAttributedString详细地说明该方法是添加富文本字符串。

那么我们来看一看在swift 3.0中,怎样删除方法名中多余的词汇,如下代码所示,

let blue = UIColor.blue
let min = numbers.min()
attributedString.append(anotherString)
names.insert("Jane", at: 0)
UIDevice.current

如你所见,swift 3.0 SDK中删除方法中多余的词汇,让方法名的长度明显变短,语义也更加明确易懂。

这个API设计规范尤其深刻地影响了字符串(strings),原先,SDK中的String大多数方法中包含了大量重复而又多余的描述性词汇。我们通过下面的代码来说明swift 2.2和swift 3.0中的String方法名的重大改变吧,

// 删减字符串空格
"Hello".stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()) // swift 2.2
"Hello".trimmingCharacters(in: .whitespacesAndNewlines) // swift 3.0

// 包含
"Taylor".containsString("ayl") // swift 2.2
"Taylor".contains("ayl") // swift 3.0

// 以","分割为数组
"1,2,3,4,5".componentsSeparatedByString(",") // swift 2.2
"1,2,3,4,5".components(separatedBy: ",") // swift 3.0

// 添加文件后缀
myPath.stringByAppendingPathComponent("file.txt") // swift 2.2
myPath.appendingPathComponent("file.txt") // swift 3.0

// 替换
"Hello, world".stringByReplacingOccurrencesOfString("Hello", withString: "Goodbye") // swift 2.2
"Hello, world".replacingOccurrences(of: "Hello", with: "Goodbye") // swift 3.0

// 根据index取子字符串
"Hello, world".substringFromIndex(7) // swift 2.2
"Hello, world".substring(from: 7) // swift 3.0

// 转换为大写
"Hello, world".capitalizedString // swift 2.2
"Hello, world".capitalized // swift 3.0

NOTE: capitalized仍然是一个属性,String的大小写转换在swift 2.2到3.0的更新中,由lowerCaseString, uppercaseString替换为lowercased(), uppercased()。

本文到此已经列举了很多的例子,通过这些例子我想说明的是,在新的API设计规范下,从swift 2.0到swift 3.0并不是特别巨大的跨越。但是在新的swift 3.0 SDK中有相当一部分的方法名有相当大的改变,以至于初次看见这些方法时,我感觉一脸懵逼 - 这也是由于新的API设计规范所导致的方法名太短,以至于对于很多开发者来说,新的方法名不能见名知意,例如下面的代码,

dismiss(animated: true, completion: nil)

也许当你第一次看见这样的语句时,会感到一脸茫然,也许你心里会想“dismiss什么?”,这大概是作为iOS开发者这么长时间以来,自然而然产生的一种斯德哥尔摩综合征,但是一旦我们将该方法中删除的多余词汇还原,将swift 3.0的语法还原为swift 2.2的语法,此时你立刻就会明白该方法的作用,如下所示,

// swift 3.0
dismiss(animated: true, completion: nil)
// swift 2.2
dismissViewControllerAnimated(true, completion: nil)

实际上,参数completion: nil也是可选的,在swift 3.0调用该方法甚至还可以进一步简写,如下所示,

dismis(animated: true)

这种简洁简短的写法,可能会带来一些疑惑,希望开发者尽快适应。

NOTE: 斯德哥尔摩综合征,是指犯罪的被害者对于犯罪者产生情感,甚至反过来帮助犯罪者的一种情结。这个情感造成被害人对加害人产生好感、依赖心、甚至协助加害人。

在本文,我们将“斯德哥尔摩综合征”理解为对于之前冗长语法的依赖,对于新的简洁的语法的慢接受。

因为从Objective-C时代开始,一直到swift 2.2,在iOS开发定义方法时,方法命名一般来说总是很长,所以swift 3.0提倡新的API设计规范时,对于大多数开发者来说感觉有点突兀。实际上从长期来说,新的API设计规范会减轻开发者的工作,但是由于由于我们长期以来形成的习惯,对于这些有益的变化,很难快速接受,反而会更怀念swift 2.2之前、甚至Objective-C时代那种冗长啰嗦甚至“有害”的方法命名方式。

枚举值和属性驼峰命名规则由UpperCamelCase改为lowerCamelCase

为class(类),结构体,属性,枚举等命名时,我们几乎都遵循了这样的一个约定(follow a convention fairly closely),即:class, struct, enum使用首字母大写UpperCamelCase的驼峰命名规则,如MyClass, MyStruct, WeatherType.Cloudy;属性和参数命名使用首字母小写lowerCamelCase的驼峰命名规则,例如eamilAddress, requestString。

之所以说“几乎(fairly closely)”,是因为swift 2.2的一些异常在swift 3.0时将不再是异常,在swift 3.0中参数和属性命名遵循首字母小写的驼峰命名规则。在swift 2.2中,我们通过NSURLRequest(URL: someURL)来创建NSURLRequest对象,注意这里的参数URL;而在swift 3.0中,将NSURLRequest初始化方法重写为NSURLRequest(url: someURL),这也意味着开发者使用webView.request?.url?.absoluteString这样的语法来读取一个webView的URL信息。

上面所说的属性命名采用首字母小写的驼峰命名规则可以应对大多数情况,不过也会出现一些违背该原则的情况,一种情况就是属性名中包含前缀,例如CGColor和CIColor使用CG, CI前缀,在swift 3.0中,将前缀都改为小写,如下代码所示,

let red = UIColor.red.cgColor

这样的改变有助于保持属性和参数命名的一致性,也就是按照swift 3.0提倡的标准,为属性和参数命名时,应该保证首字母小写,没有其他的选择。

同时,枚举值的命名标准也由首字母大写驼峰命名,改为首字母小写驼峰命名,这样就与属性、参数的命名规范保持一致。这其实也是可理解(make sense)的,可以做一下类比,一个枚举类型(enum)相当于一个结构体(struct),枚举值(enum values)相当于结构体的属性(properties)。在swift 3.0中使用枚举值,如下代码所示,

UIInterfaceOrientationMask.Portrait // old
UIInterfaceOrientationMask.portrait // swift 3.0

NSTextAlignment.Left // old
NSTextAlignment.left // swift 3.0

SKBlendMode.Replace // old
SKBlendMode.replace // swift 3.0

对于开发者来说,swift 3.0中,属性、参数和枚举值新的命名规范并不难理解,但是这一点小小的改变也带来了相当大的影响,因为在swift中可选值(Optional)实际上就是SDK定义的枚举类型,如下所示,

// before swift 3.0
enum Optional {
    case None
    case Some(Wrapped)
}

这意味着如果你之前使用.Some来处理可选值,现在你需要使用.some来替代。由于通常使用if let来进行解包,所以我们完全不必要直接使用.some,如下两种对可选值解包的代码,效果完全一样,

// swift 3.0
for case let .some(datum) in data {
    print(datum)
}

for case let datum? in data {
    print(datum)
}

可使用swift语法调用C函数

Swift 3为C函数引入了新的语法特性,它允许C语言功能库(library)开发者通过全新的、优雅的方式将他们的C语言代码导入swift。例如,所有已"CGContext"开头的函数(functions)在swift 3.0都对应于一个CGContext对象的实例方法,这样改动之后,更加符合swift的语法风格。是的,这意味着类似CGContextSetFillColorWithColor这样像丑陋的肿瘤一样的晦涩难懂的语法,终于从swift开发中切离了。

为了说明这样的语法改变,先看一下swift 2.2中调用C函数的代码示例,如下所示,

let ctx = UIGraphicsGetCurrentContext()

let rectangle = CGRect(x: 0, y: 0, width: 512, height: 512)
CGContextSetFillColorWithColor(ctx, UIColor.redColor().CGColor)
CGContextSetStrokeColorWithColor(ctx, UIColor.blackColor().CGColor)
CGContextSetLineWidth(ctx, 10)
CGContextAddRect(ctx, rectangle)
CGContextDrawPath(ctx, .FillStroke)

UIGraphicsEndImageContext()

在swift 3中,"CGContext"可以被实例化为一个swift对象,这样你可以直接调用该对象的方法,而不用一遍一遍地重复CGContext的代码。所以,在swift 3,我们可以重写之前冗长的代码,如下所示,

if let ctx = UIGraphicsGetCurrentContext() {
    let rectangle = CGRect(x: 0, y: 0, width: 512, height: 512)
    ctx.setFillColor(UIColor.red.cgColor)
    ctx.setStrokeColor(UIColor.black.cgColor)
    ctx.setLineWidth(10)
    ctx.addRect(rectangle)
    ctx.drawPath(using: .fillStroke)

    UIGraphicsEndImageContext()
}

NOTE: 不管在swift 2.2还是swift 3.0中,UIGraphicsGetCurrentContext()函数都返回一个可选值CGContext对象,但由于在swift 3中我们调用了该对象的相关方法,所以需要对其安全解包之后才能使用。

在其他模块,也可以使用swift语法调用C函数,例如你可以读取CGPDFDocument的numberOfPages属性,并且CGAffineTransform模块由于使用swift语法调用,可读性和使用效率的提升也相当引人注目。下面是swift旧版本和新版本调用C函数的代码,

CGAffineTransformIdentity // old
CGAffineTransform.identity  // swift 3.0

CGAffineTransformMakeScale(2, 2)    // old
CGAffineTransform(scaleX: 2, y: 2)  // swift 3.0

CGAffineTransformMakeTranslation(128, 128) // old
CGAffineTransform(translationX: 128, y: 128) // swift 3.0

CGAffineTransformMakeRotation(CGFloat(M_PI)) // old
CGAffineTransform(rotationAngle: CGFloat(M_PI)) // swift 3.0

动词和名次的命名规范

这一个模块的内容可能会让很多读者陷入迷惑,过多地解释这一块内容也许显得多余,但这确实很重要。

NOTE: 由于本人英语能力有限,在翻译以下内容时,感觉太过晦涩,但是关于命名的规范还是很重要的,所以我将原文贴出,本来我在生僻的单词后面添加了中文备注,但未免画蛇添足,希望聪明的读者们自己理解。

下面的文字内容引用自Swift API guidelines:

  • "When the operation is naturally described by a verb, use the verb’s imperative for the mutating method and apply the “ed” or “ing” suffix to name its nonmutating counterpart "

  • "Prefer to name the nonmutating variant using the verb’s past participle"

  • "When adding “ed” is not grammatical because the verb has a direct object, name the nonmutating variant using the verb’s present participle"

  • "When the operation is naturally described by a noun, use the noun for the nonmutating method and apply the “form” prefix to name its mutating counterpart"
    Got that? It's no surprise that Swift's rules are expressed using lingustic terminology – it is after all a language! – but this at least gives me a chance to feel smug that I did a second degree in English. What it means is that many methods are changing names in subtle and sometimes confusing ways.

依据上述的命名规范,我们来看一些简单的例子,

myArray.enumerate()  // swift 2.3
myArray.enumerated() // swift 3.0

myArray.reverse()   // swift 2.3
myArray.reversed()  // swift 3.0

可以看出,swift 3将之前的方法都添加了ed后缀,该方法表明这是一个被返回的值。好吧我在这块翻译感觉怪怪的,原文是this is a value that's being returned.,英语渣伤不起啊。

上面的命名规则足够清晰明确,但是有时它也会让开发者感到迷惑,例如当我们处理数组的排序的时候。在swift 2.3之前,使用sort()返回一个排序好的新数组,使用sortInPlace()将原数组排序;而在swift 3.0中,使用sorted()替代sort(),使用sort()替代sortInPlace(),所以也可以说,在swift 3.0中,已经没有sortInPlact()方法了,呃呃呃,好乱。

这意味着你需要特别的小心,因为在swift 2.3中,sort()返回一个排序好的新数组,这是一个新变量;而在swift 3.0中,表示sort()将当前数组排序,并不会创建一个新变量。

Swift 3.0为什么要这样改变

回顾一下文章讨论的swift 3.0的改变,理解这些改变并不难,有些语法规则的改变看起来很小,但是对于之前版本的swift代码却带来了巨大的破坏,不禁让我们这些iOS平台的码农猜测,苹果的swift工程师是不是想让我们这些码农生活更加多艰。然而,事实却是苹果的swift工程师正在尽他们的努力来保证swift语言足够简单,让我们更容易去学习、使用、让swift语言更快,这是苹果swift工程师优先考虑的三个重点。

本人对swift 3.0的一些感想

目前,本人还在使用swift 2.3版本进行app开发,但工作中也会使用swift 3.0做一些demo,有一些改变确实让我感觉无所适从,但是在摸索一段时间以后也就感觉得心应手了,我觉得swift 3.0最让我满意的就是方法调用时要传递全部参数这样一个规定,这就保证了调用方法和init方法保持了参数的一致;其次,我比较喜欢的,就是swift 3.0提倡简洁的语法,将这种约定当做语言的规范,让开发者在命名方法和变量时,都会力求保证简洁。

参考链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容