与程序员们共勉:

  1. 你无法断定程序会在什么地方耗费运行时间。瓶颈经常出现在想不到的地方,所以别急于胡乱找个地方改代码,除非你已经证实那儿就是瓶颈所在。
  2. 估量。在你没对代码进行估量,特别是没找到最耗时的那部分之前,别去优化速度。
  3. 花哨的算法在 n 很小时通常很慢,而 n 通常很小。花哨算法的常数复杂度很大。除非你确定 n 总是很大,否则不要用花哨算法(即使 n 很大,也优先考虑原则 2 )。比如,解决常见问题时,最简单的树——二叉树(binary tree),总是比那些复杂的树(AVL树,伸展树(splay tree)和红黑树、B-树(B-tree),多叉树(trie))来的高校。
  4. 花哨的算法比简单算法更容易出 bug 、更难实现。尽量使用简单的算法配合简单的数据结构。只要掌握了数据结构中的四大法宝,就可以包打天下,他们是:array 、linked list 、hash table、binary tree 。这四大法宝可不是各自为战的,灵活结合才能游刃有余。比如,一个用hash table组织的symbol table,其中是一个个由字符型array构成的linked list。
  5. 以数据为中心。如果已经选择了正确的数据结构并且把一切都组织得井井有条,正确的算法也就不言自明。编程的核心是数据结构,而不是算法。

距离Swift发布已经过去快2个月了,各路前辈和大神写教程、写出、录教学视频忙的不亦乐乎,我们也从中学习到了很多关于Swift有用的知识。在学习的过程中也见证了Apple一次次对Swift修正的历程,在正式版来临之际,我想说说被很多前辈和大神视为玩物的东西–Playground。

我觉得Swift提供的Playground非常有趣,至少对初学者来说是一个能抓住眼球的重要因素。并且我认为Playground其实挺有用的,比如学习Swift的各种新特性、写一些有趣的代码片段、验证一些算法、甚至可以用它写一个小游戏等等,包括TopCoder的Swift Challange都要求使用Playground。但是很关键的一点是以上这些Playground的应用场景与真实的项目、产品开发之间没有太多的交际,换句话说就是Playground在实际开发中基本帮不上什么忙,这也正式很多人觉得目前的Playground没什么大用的原因。

但事实真的是如此吗?是,好像又不是,今天我在这里就介绍一个Playground在实际开发中的用途 – 使用Playground在开发中测试自己的库或第三方的库。在80%的第三方常用库还是Objective-C的情况下,我们可以用Playground来测试在Swift中的使用健康程度,从而能更从容的选择继续使用还是更换方案。

  • 第一步:创建iOS Framework & library project或打开已有的项目。
  • 第二步:将其存为workspace。
  • 第三步:新建Playground文件。
  • 第四步:在64位的模拟器(iPhone5s)上编译运行你的Framework。
  • 第五步:在Playground中导入库并编写测试代码。

这样你就可以在Playground中看到每一行代码的运行情况,是不是很酷呢!

通常情况下,我们在使用数组(Array)或字典(Dictionary)时会使用到下标。其实在Swift中,我们还可以给类(class)自定义下标,下面就让我们来看看Swift中是如何自定义下标的。

通过Subscript赋值和获取值

我们先看看下面这个类:


class DailyMeal
{

enum MealTime
{

case Breakfast
case Lunch
case Dinner
}

var meals: [MealTime : String] = [:]
}

我们使用该类时可以直接用meals字典,以枚举作为key来查询,像这样:


var monday = DailyMeal()

monday.meals[.Breakfast] = "Toast"

if let someMeal = monday.meals[.Breakfast]
{
println(someMeal)
}

到目前呢,我们创建了DailyMeal类的实例变量monday,并可以使用DailyMeal类中的meals字典进行查询。但是大家有没有感觉monday.meals[]这种写法很累赘呢?至少我看meals就很不顺眼,有没有更简单快捷的方法让我们直接使用monday变量就能赋值或获取Breakfast的值呢?别着急,今天的主角要登场了,让我们先在DailyMeal类中添加如下代码:


subscript(requestedMeal : MealTime) -> String?
{
get
{
return meals[requestedMeal]
}
set(newMealName)
{
meals[requestedMeal] = newMealName
}
}

上面的代码就是一个自定义下标,看起来是不是有点像计算类属性的gettersetter方法的写法呢?但是它们还是有区别的,首先下标使用subscript关键字,然后跟一个圆括号,里面是该下标的参数和参数类型(在实际使用中该参数就相当于数组的index和字典的key一样),最后有该下标的返回值类型。

从上面代码可以看到,在下标的gettersetter方法中,其实还是在对meals数组进行操作,但是我们通过下标就可以将对meals数组的操作屏蔽掉。现在来看看我们应该怎样使用:


var monday = DailyMeal()

monday[.Breakfast] = "Toast"

if let someMeal = monday[.Breakfast]
{
println(someMeal) // Toast
}

现在是不是已经没有碍眼的meals了呢,使用起来更加简洁,语义也更加明确了呢,这就是下标最简单的一个用法。

如果上面的代码中我们不给monday[.Breakfast]赋值,直接输出值会得到什么结果呢?细心的同学可能会注意到,在定义下标时它的返回值是Optional类型的,所以不赋值直接输出的结果是nil,这样就显得太没礼貌了,所以我们再来改造一下下标的代码:


subscript(requestedMeal : MealTime) -> String
{
get
{
if let thisMeal = meals[requestedMeal]
{
return thisMeal
}
else
{
return "Ramen"
}
}
set(newMealName)
{
meals[requestedMeal] = newMealName
}
}

我们看到下标的返回值从String?改为了String,那么相应的我们要在getter方法中对meals[requestedMeal]的值进行判断,如果没有赋值的话,我们将返回一个默认值Ramen(兰州拉面让人欲罢不能)。现在我们就可以这样用啦:


var monday = DailyMeal()

monday[.Lunch] = "Pizza"

println(monday[.Lunch]) //Output: "Pizza"

println(monday[.Dinner]) //Output: "Ramen"

现在使用DailyMeal类是不是感觉到很简介,语义很明确也很健壮呢,答案是肯定的。我们通过下标避免向用户暴露不必要的API,同时也达到了高维护性的目的。

DailyMeal类的完整代码如下:


class DailyMeal
{

enum MealTime
{

case Breakfast
case Lunch
case Dinner
}

var meals: [MealTime : String] = [:]

subscript(requestedMeal : MealTime) -> String
{
get
{
if let thisMeal = meals[requestedMeal]
{
return thisMeal
}
else
{
return "Ramen"
}
}
set(newMealName)
{
meals[requestedMeal] = newMealName
}
}
}

只读下标

何为只读下标,顾名思义就是不能通过下标赋值,只能通过下标查询。这种下标的应用场景一般是实现一些数据公式、数据函数,它们一般都是只需要你指定一个数字,然后返回该公式对该数字的计算结果。下面我们用一个阶乘的例子来说明只读下标:


struct FactorialGenerator
{

subscript(n: Int) -> Int
{
var result = 1

if n > 0
{
for value in 1...n
{
result *= value
}
}

return result
}
}

同学们可能已经注意到了,上面的下标并没有gettersetter方法。这是因为,如果你想定义一个只读的下标,那么可以不实现setter方法,并且可以省略getter方法的get关键字。Swfit的编译器会判断出这是一个只读的下标,如果你强行通过下标赋值,那么编译器会报错。

让我们来使用以下这个阶乘结构体:


let factorial = FactorialGenerator()

println("Five factorial is equal to \(factorial[5]).")
//Output: "Five factorial is equal to 120."

println("Ten Factorial is equal to \(factorial[10]).")
//Output: "Ten Factorial is equal to 3628800."

当然上面这个示例只是展示了只读下标的语法和应用场景,阶乘的实现逻辑在这就不累赘了。总的来说,我们可以通过下标简化暴露给用户的API,你可以在用户毫不知情的情况下更改某个API的功能。不仅使代码更易读,同时也大大提高了代码的可维护性,是不是很酷呢!

参考原文:Custom Subscripts in Swift

闭包在Swift中非常有用。通俗的解释就是一个Int类型里存储着一个整数,一个String类型包含着一串字符,同样,闭包是一个包含着函数的类型。有了闭包,你就可以处理很多在一些古老的语言中不能处理的事情。这是因为闭包使用的多样性,比如你可以将闭包赋值给一个变量,你也可以将闭包作为一个函数的参数,你甚至可以将闭包作为一个函数的返回值。它的强大之处可见一斑。

在Swift的很多文档教材中都说函数是“一等公民”,起初我还不是很理解“一等公民”是什么意思,但当我理解了闭包以及它的强大功能后,我恍然大悟、茅塞顿开、醍醐灌顶。原来闭包的这些特性就是“一等公民”的特性啊!参见维基百科First-class citizen

Swift中的闭包类似Objective-C中的Block。其实,如果你想在Swift中实现Objective-C里的Block功能,你可以直接使用闭包来代替。Block和闭包的区别只是语法的不同而已,而且闭包的可读性比较强。

函数是闭包吗?

虽然你还没有意识到,但我们确实已经在Swift中这么用了。Swift中的函数就是闭包,在Apple的官方文档中有这样的描述:

闭包有三种形式:

  1. 全局函数是一个有名字但不会捕获任何值的闭包。
  2. 嵌套函数是一个有名字并可以捕获到其封闭函数域内的值的闭包。
  3. 闭包表达式是一个利用轻量级语法所写的,可以捕获其上下文中变量或常量值的匿名闭包。

今天,我们要讨论的是第三种形式,尤其讨论它是如何将繁复的、可读性比较差的业务逻辑代码压缩成高可读性、简明明了的形式。

大家还记得数组的map方法么?它的参数就是一个闭包,它会将数组里的每一个元素放在闭包中进行处理,然后返回一个新的数组,甚至是与原数组不同元素类型的新数组。

map函数的原型如下:


func map<U>(transform: (T) -> U) -> [U]

我们可以看到该函数使用了泛型。(T) -> U是一个泛型闭包,它的意思就是类型T将会在闭包中进行逻辑处理,然后返回U类型。最后map函数会返回一个U类型的数组。

用一个例子来说明。今天我办生日聚会,要迎接很多人,并且为每个人都准备了一句欢迎词。我们要怎么做呢?首先我们将迎接的人放进一个数组名叫guestList,然后用一个名叫greetPeople的函数为每个人生成欢迎词:


func greetPeople(person: String) -> String
{
return "Hello, \(person)!"
}


let guestList = ["Chris", "Jill", "Tim"]
let fullGreetings = guestList.map(greetPeople)

然后将greetPeople函数作为guestList数组的map函数的参数传入,并返回一个新的数组fullGreetings,这个数组就包含了每个人的欢迎词。

如果我们想展示一下每个人的欢迎词,我们甚至可以这样写:


fullGreetings.map(println)

这时也许有人要质疑了,println函数不是没有返回值么?那么map函数会返回什么呢?其实每一个没有返回值的函数,都会返回一个空的元组(tuple),所以说上述代码的返回值其实是Array<()>

上面的例子中我们就是将一个全局函数greetPeople作为一个闭包来使用的。

简明扼要的闭包表达式

其实Swift已经为我们提供了很多简化的语法,可以让我们保证代码的高可读性和维护性。还用上面的例子来说明,对于greetPeople这个全局函数来说,其实只需要使用一次,所以我们没必要单独定义这个函数。我们可以直接使用闭包表达式来处理:


let fullGreetings = guestList.map({(person: String) -> String in return "Hello, \(person)!"})

闭包表达式其实是函数的字面值,官方一般称之为匿名函数。一般当我们需要使用函数快速的实现一个简短的处理逻辑并且只使用一次的时候,我们可以省去函数名,使用简化的语法。上面的代码中可以看到关键字in之前是闭包表达式的参数和返回值,in之后是闭包表达式实际处理逻辑的代码区域。

下面我们将使用Swift更多的特性来进一步简化闭包表达式。

我们知道Swift中有类型推断的特性,所以我们可以取掉参数类型:


let fullGreetings = guestList.map({(person) -> String in return "Hello, \(person)!" })

像我们示例中的这种单一闭包表达式,编译器可以根据in之前的返回值类型和return之后的返回数据类型自动判断,所以我们可以省略返回值和return关键字:


let fullGreetings = guestList.map({person in "Hello, \(person)!" })

其实在Swift中还提供了参数的简写方式:$0代表第一个参数、$1代表第二个参数以此类推。所以我们又可以将参数名称省略:


let fullGreetings = guestList.map({ "Hello, \($0)!" })

当函数的最后一个参数是闭包时,可以将闭包写在()之外,这也是Swift的一个特性,所以我们还可以继续简化:


let fullGreetings = guestList.map(){ "Hello, \($0)!" }

当函数有且仅有一个参数,并该参数是闭包时,不但可以将闭包写在()外,还可以省略()


let fullGreetings = guestList.map{ "Hello, \($0)!" }

到目前为止,示例中的闭包表达式已经被我们根据Swift的特性,简化为简明扼要、高可读性的闭包表达式了,是不是很酷呢!

参考原文:Closure Expressions in Swift

这篇文章将带我们探索在Swift中如何使用Optional类型保证强类型的安全性。我们将创建一个Swift版本的Objective-C的API。虽然在Swift中这个API存在的意义不是很大,但是这将会是一个很有趣的例子。

在Objective-C中,NSDictionary类有一个方法,名为-objectsForKeys:notFoundMarker:,它的作用是根据第一个NSArray类型参数中的值作为该字典的key,查找这些key对应的字典中的值,并放到一个新的NSArray中返回,如果找不到对应的值,那么就返回第二个参数指定的对象。在官方文档中对该方法有这么一句描述“返回的数组中的第N个对象,对应着第一个数组参数中的第N个值”。假如说以第一个数组参数中的第三个值作为key在字典中查不到值怎么办呢?这时候就需要notFoundMarker参数登场了。这种情况下就会返回notFoundMarker参数指定的对象了。在Foundation框架中还有专门针对该情况适用的一个类,那就是NSNull,就是当你也没有备选返回对象的时候,就可以返回NSNull对象。

在Swift中,Dictionary类型并没有类似objectsForKeys的方法。所以接下来的练习中,我们将使用类型的扩展机制为Dictionary类型添加一个类似objectsForKeys的方法,为保持Swift的风格我们起名为valuesForKeys:notFoundMarker:


extension Dictionary {
func valuesForKeys(keys: [K], notFoundMarker: V) -> [V] {
// To be implemented
}
}

在Swift中实现该方法与Objective-C有点不同,因为Swift中强类型的特性使返回的数组中只能包含某一种类型的元素,也就是说我们不能在一个字符串数组中添加一个NSNull类型的元素,这就使notFoundMarker的参数类型显得非常尴尬。这怎么解决呢?别着急,在Swift中我们有更好的选择:我们可以返回一个Optional类型的数组。从Dictionary中查出的值全部被包在Optional类型中,这样当使用的key没有对应值的时候,我们就可以使用nil来替代NSNull类型了。


extension Dictionary {
func valuesForKeys(keys: [Key]) -> [Value?] {
var result = [Value?]()
result.reserveCapacity(keys.count)
for key in keys {
result.append(self[key])
}
return result
}
}

注意:此时可能已经有人认为Dictionary类型中的这个方法可能没必要写的这么繁琐,你们可能已经想到了这种情形:


extension Dictionary {
func valuesForKeys(keys: [Key]) -> [Value?] {
return keys.map { self[$0] }
}
}

这段代码和上面那段代码的作用和结果是完全一样的,当keys调用map方法时,其实已经将查出的所有值都包在了Optional类型中了。这就足以说明了为什么Swift中类型的API都那么短小精干,因为实现复杂逻辑就像上述代码中直接调用map方法一样简单。

现在我们可以试着用我们扩展的方法做一些例子:


let dict = ["A": "Amir", "B": "Bertha", "C": "Ching"]

dict.valuesForKeys(["A", "C"])
// [Optional("Amir"), Optional("Ching")]

dict.valuesForKeys(["B", "D"])
// [Optional("Bertha"), nil]

dict.valuesForKeys([])
// []

内嵌Optional类型

现在我们来看看使用last属性返回数组的最后一个元素会发生什么?


dict.valuesForKeys(["A", "C"]).last
// Optional(Optional("Ching"))

dict.valuesForKeys(["B", "D"]).last
// Optional(nil)

dict.valuesForKeys([]).last
// nil

看着结果我们是不是觉得很奇怪呢?我们在上述代码的第一种情况下得到了嵌套的Optional类型,而在第二种情况下缺得到了包含nilOptional类型,为什么得到的不是Optional("Ching")nil呢?

冷静下来,我们回忆一下last属性的是如何申明的:


var last: T? { get }

恍然大悟,原来last属性的类型是Optional类型,这也就是说如果TOptional类型的话,那么T?自然就是Optional(Optional)了,也就是T??。所以上面的情况就很容易解释了,因为T的类型是Optional(String),所以我们得到的结果就是Optional(Optional(String))

那么Optional(nil)这种情况如何解释呢?为什么不是Optional(Optional(nil))呢?

我们现在Objective-C中执行一下上面那三种情况看一看:


[dict valuesForKeys:@[@"A", @"C"] notFoundMarker:[NSNull null]].lastObject
// @"Ching"

[dict valuesForKeys:@[@"B", @"D"] notFoundMarker:[NSNull null]].lastObject
// NSNull

[dict valuesForKeys:@[] notFoundMarker:[NSNull null]].lastObject
// nil

我们看到,不论在Swift中还是在Objective-C中,当第一个参数的数组是空数组的时候,取最后一个元素的返回结果都是nil,意思就是“数组是空数组,那么最后一个元素肯定不存在啦”。那么在Swift中返回Optional(nil)和在Objective-C中返回NSNull的情况表明这个所谓的最后一个元素在数组中其实是存在的,只不过它就代表没有。当这种情况发生时,Objective-C只能用一个占位符对象来表示,而在Swift中就可以用一个系统类型来表示。

提供默认值

如果我们想当在Dictionary中查不到对应值的时候返回一个我们指定的默认值要怎么做呢?其实这也很简单:


extension Dictionary {
func valuesForKeys(keys: [Key], notFoundMarker: Value) -> [Value] {
return self.valuesForKeys(keys).map { $0 ?? notFoundMarker }
}
}


dict.valuesForKeys(["B", "D"], notFoundMarker: "Anonymous")
// ["Bertha", "Anonymous"]

当Objective-C只能用占位符对象来做到这一点的时候,Swift却可以使用系统类型来呈现,并且提供了丰富的语法支持多样化的返回结果。

原文地址:Optionals Case Study: valuesForKeys