原文:Swift Language Highlights: An Objective-C Developer’s Perspective
本文从一名 Objective-C 程序员的视角介绍 Swift 中引进的部分新特性:类型揣度、泛型、switch 句子和常量。
假如这个星期一你和我相同,正坐在那里享受着主题讲演,并振奋地开端测验所有新的心爱的 API。然后,当你听到关于一门新言语的论题时,你的耳朵就竖了起来:Swift! 你忽然想到,这不是 Objective-C 的扩展,而是一门全新的言语。或许你很振奋?或许你很高兴?或许你不知道该如何是好。
Swift 势必会改动咱们未来编写 iOS 和 Mac 运用的办法。在这篇文章中,我会概述 Swift 言语的一些亮点,并测验将它们与 Objective-C 中的同类言语进行比照。
类型
Swift 提供的榜首个严重改善是类型揣度。在运用类型揣度的编程言语中,程序员不需求用类型信息来注释变量。编译器会从变量被设置的值来揣度。例如,编译器可以主动将这个变量设置为 String
类型:
// 主动揣度
var name1 = "Matt"
// 清晰界说类型(这儿可以不这样做)
var name2: String = "matt"
除了类型揣度之外,Swift 还带来了类型安全特性。在 Swift 中,编译器(除了少数特殊情况外)知道一个目标的完好类型信息。这样给编译器一个挑选如何编译代码的时机,由于编译器有满足的信息。
这与 Objective-C 形成了鲜明的比照,由于 Objective-C 的本质是动态的。在 Objective-C 中,没有类型在编译时是真实已知的。这在必定程度上是由于你可以在运行时为现有的类增加办法,增加全新的类,乃至改动实例的类型。
让咱们更详细地看看这个问题。考虑以下 Objective-C 代码:
Person *matt = [[Person alloc] initWithName:@"Matt Galloway"];
[matt sayHello];
当编译器看到对 sayHello
的调用时,它可以查看是否有一个叫做 sayHello
的办法在 Person
类的头文件中被声明。假如没有,它可以犯错,但这是它能做的所有工作。这一般足以抓住你可能引进的榜首行过错。它会抓住比如错别字之类的东西。可是由于动态的性质,编译器不知道 sayHello
是否会在运行时改动,乃至不必定存在。例如,它可能是一个协议中的可选办法,还记得那些运用 respondsToSelector:
的查看吗?
由于缺乏这种强类型,编译器在 Objective-C 中调用办法时能做的优化作业十分少。处理动态调度的办法叫做 objc_msgSend
。相信你在很多回溯中都见过这个办法吧!在这个函数中,挑选器的完成被查找,然后跳转。你不能说这不会增加开支和复杂性。
现在看看 Swift 中相同的代码:
var matt = Person(name:"Matt Galloway")
matt.sayHello()
在 Swift 中,编译器对任何办法调用中的类型了解得更多。它清楚地知道 sayHello()
在哪里被界说。正由于如此,它可以经过直接跳转到完成而不是经过动态调度来优化某些调用。在其他情况下,它可以运用 vtable 风格的调用,这比 Objective-C 中的动态调度开支小得多。这也是 C++ 对虚拟函数运用的调度办法。
在 Swift 中,编译器的协助更大。它将有助于阻止细微的类型相关的过错进入你的代码库。它还会经过启用智能优化使你的代码运行得更快。
泛型
Swift 中另一个大的特点是泛型。假如你了解 C++,那么你可以以为这些就像模板相同。由于 Swift 对类型的要求很严厉,你必须声明一个函数来承受某些类型的参数。但有时你有一些功用对多种不同类型是相同的。
这方面的一个比如便是经常有用的一对结构。你期望一对值被存储在一起。你可以在 Swift 中对 Int
类型完成这样的功用:
struct Intpair {
let a: Int!
let b: Int!
init(a: Int, b: Int) {
self.a = a;
self.b = b;
}
func equal() -> Bool {
return a == b
}
}
let intPair = Intpair(a: 5, b: 10)
intPair.a // 5
intPair.b // 10
intPair.equal() // false
似乎有点用处。但现在你期望这个结构体也能用于处理 Float
类型。你当然可以再界说一个 FloatPair
类,但那会看起来十分类似。这便是泛型的作用。与其声明一个全新的类,你可以简略地这么做:
struct Pair<T: Equatable> {
let a: T!
let b: T!
init(a: T, b: T) {
self.a = a;
self.b = b;
}
func equal() -> Bool {
return a == b
}
}
let intPair = Pair(a: 5, b: 10)
intPair.a // 5
intPair.b // 10
intPair.equal() // false
let floatPair = Pair(a: 3.14159, b: 2.0)
floatPair.a // 3.14159
floatPair.b // 2
floatPair.equal() // false
适当有用! 或许现在看起来还不清楚为什么你会想要这种功用,可是相信我:时机是无穷的。你很快就会开端看到你可以在自己的代码中运用这些功用。
调集
你现已知道而且十分喜爱 NSArray
, NSDictionary
和它们的可变目标。那么,现在你将不得不学习它们在 Swift 中的对应类型。走运的是,它们十分类似。下面是你如何声明数组和字典的办法:
let array = [1, 2, 3, 4]
let dictionary = ["dog": 1, "elephant": 2]
这对你来说应该适当了解。不过有一个小问题。在 Objective-C 中,数组和字典可以包含你所期望的任何类型。可是在 Swift 中, 数组和字典是类型化的. 而且它们的类型化是经过运用咱们上面的朋友——泛型来完成的。
上面的两个变量可以用它们的类型来重写(虽然记住你实际上不需求这样做!)像这样:
let array: [Int] = [1, 2, 3, 4]
let dictionary: [String: Int] = ["dog": 1, "elephant": 2]
请注意,现在你不能向数组中增加任何非 Int
类型的内容。这听起来可能是件坏事,但它却十分有用。你的 API 不再需求记载从某个办法返回的数组中存储了什么,或许存储在某个属性中。你可以将这些信息直接提供给编译器,这样它就可以更聪明地进行前面描述的过错查看和优化。
可变性
Swift 中关于调集的一个风趣的工作是它们的可变性。在 Array
和 Dictionary
中没有 “mutable” 可变类型,而是运用标准的 let
和 var
。关于那些还没有读过这本书,或许底子没有深入研究 Swift 的人来说(我建议你尽快去读!),let
是用来声明一个变量为常量,而 var
是用来声明一个变量为,嗯,变量!let
就像在 C/C++/Objective-C 中运用 const
相同。
这与调集的联系是,**运用 let
声明的调集不能改动。也便是说,它们不能被追加或删去。**假如你测验这样做,那么你会得到一个过错,就像这样:
let array = [1, 2, 3]
array.append(4)
// error: cannot use mutating member on immutable value: 'array' is a 'let' constant
这相同适用于 Dictionary
类型。这一现实使得编译器可以对这类调集进行揣度,并适当地进行优化。假如大小不能改动,那么存放这些值的底层存储就永久不需求重新分配来容纳新的值。出于这个原因,关于那些不会改动的调集,总是运用 let
关键字来声明是一个好的做法。
字符串
Objective-C 中的字符串是出了名的烦人。即使是简略的使命,如衔接很多不同的值,也会变得很庸俗。以下面的比如为例。
Person *person = ...;
NSMutableString *description = [[NSMutableString alloc] init];
[description appendFormat:@"%@ is %i years old.", person.name, person.age];
if (person.employer) {
[description appendFormat:@" They work for %@.", person.employer];
} else {
[description appendString:@" They are unemployed."];
}
这是适当繁琐的,包含了很多与被操作的数据无关的字符。相同的在 Swift 中会是这样的:
var description = ""
description += "(person.name) is (person.age) years old."
if person.employer {
description += " They work for (person.employer)."
} else {
description += " They are unemployed."
}
清楚多了!请注意从格式中创建字符串的办法更简练,现在你可以简略地运用 +=
来衔接字符串。 不再有可变的字符串和不可变的字符串。
Swift 的另一个奇妙的新增功用是字符串的比较。你会知道,在Objective-C 中,运用 ==
来比较字符串的等同性是不正确的,而应该运用 isEqualToString:
办法。这是由于前者是在执行指针比较。Swift 去掉了这个层次的间接性,而是让你可以直接运用 ==
来比较字符串。这也意味着可以在 switch
句子中运用字符串。不过鄙人一节会有更多的内容。
最终一个好消息是 Swift 支持完好的 Unicode 字符集。你可以在你的字符串中运用任何 Unicode 编码点,乃至是函数和变量名。假如你想的话,你现在可以运用一个叫做 的函数(一堆大便!)。
另一个好消息是,现在有一个内置的办法来核算字符串的实在长度。当涉及到完好的 Unicode 规模时,字符串的长度是不容易核算的。你不能只说是用 UTF8 存储字符串的字节数,由于有些字符需求超过1个字节。在 Objective-C 中,NSString
是经过核算 UTF16、2 字节对的数量来进行核算的,用来存储字符串。但这在技术上是不正确的,由于一些 Unicode 码点占用了两个,2 个字节对。
走运的是,Swift 有一个便利的函数来核算字符串中真实的码点数量。它运用了名为 countElements()
的顶级函数。你可以这样运用它。
var poos = "u{1f4a9}u{1f4a9}" //
poos.count // 2
注:
countElements()
办法被弃用了。在 Swift1.2 中运用count
办法。
但它并不完全适用于所有情况。它仅仅核算 Unicode 码点的数量。它不考虑改动其他字符的特殊码点。例如,你可以在前一个字符上加一个乌龙,在这种情况下,countElements()
会核算出 Unicode 码点的数量。在这种情况下,countElements()
会返回 2 个字符对,虽然它看起来仅仅一个字符。就像这样:
var eUmlaut = "eu{0308}" //
eUmlaut.count // 1
说了这么多,我想你必定会同意,Swift 中的字符串是十分棒的!
Switch 条件
在咱们简略介绍 Swift 的过程中,我最终想谈的是 switch
句子。它在 Swift 中比它在 Objective-C 中对应的语法有了很大的改善。这是一个很风趣的东西,由于它是不可能被增加到 Objective-C 中的东西,而不会打破 Objective-C 是 C 的严厉超集这一基本现实。
榜首个令人振奋的功用是开启字符串。这是你以前可能想做却做不到的工作。要在 Objective-C 中 “开关”字符串,你必须运用大量的 if-statements 与 isEqualToString:
这样的句子:
if ([person.name isEqualToString:@"Matt Galloway"]) {
NSLog(@"Author of an interesting Swift article");
} else if ([person.name isEqualToString:@"Ray Wenderlich"]) {
NSLog(@"Has a great website");
} else if ([person.name isEqualToString:@"Tim Cook"]) {
NSLog(@"CEO of Apple Inc.");
} else {
NSLog(@"Someone else);
}
这个不是特别好读。也是打了很多字。相同在 Swift 中是这样的:
switch person.name {
case "Matt Galloway":
println("Author of an interesting Swift article")
case "Ray Wenderlich":
println("Has a great website")
case "Tim Cook":
println("CEO of Apple Inc.")
default:
println("Someone else")
}
除了在 switch 上的切换,注意到这儿有一些风趣的东西。没有看到任何 break
句子。这是由于开关中的箱子不再会掉到下一个。再也不会不小心掉过虫子了!
现在,下一个 switch 声明很可能会让你大吃一惊,所以要做好准备!
switch i {
case 0, 1, 2:
println("Small")
case 3...7:
println("Medium")
case 8..<10:
println("Large")
case _ where i % 2 == 0:
println("Even")
case _ where i % 2 == 1:
println("Odd")
default:
break
}
首要,现在有一个 break
。这是由于 switch 语法需求涉及到所有值,也便是说,它们需求处理所有的情况了。在这种情况下,咱们期望默认情况下什么都不做,所以增加了一个 break
来声明什么都不应该发生的目的。
下一个风趣的工作是你在里面看到的…
和 …<
。这些是新的运算符,用来界说规模。前者界说了一个规模,最多包含右手边的数字。后者界说了一个规模,最多包含右手的数字。这些都是十分有用的。
最终一点是可以界说一个事例作为输入的核算。在这种情况下,假如值不符合从 0 到 10 的任何东西,假如是偶数,它就打印 “Even”,假如是奇数,就打印 “Odd”。奇特!
何去何从?
期望这能让你感受到 Swift 言语的魅力,以及其中的精彩。但还有更多的东西! 我鼓舞你去阅读Apple的书和其他Apple的文档,它们将协助你学习这门新言语。你迟早要做的!
另外,假如你想了解更多关于 Swift 的信息,请查看咱们三本全新的 Swift 书本–涵盖了你需求了解的关于 Swift、iOS 8 以及更多的内容。
咱们很想听听你对 Swift 言语到目前为止的看法,或许是否有任何让你振奋的酷炫亮点。请鄙人方留言,表达你的主意。