WWDC14 Session 402 学习笔记 -- Swift

发布于:2014-06-09 11:08阅读数:

苹果从WWDC Keynote 开场到 这个Session 都一直在提三个词。SAFE/MODERN/POWER,包括Session上面讲解每个feature的时候,都会说这个体现了Swift哪个特性,例如类型推断 就体现了 SAFE 和 Modern 之类的。

 
Swift这个语言各个方面的东西都不齐全,语法上,文文件上,小区上。所以毫无iOS经验或者iOS初学者,我强烈不建议现在就学习这门语言。
 
这里举个非常非常坑的例子:
  1. import Cocoa 
  2.  
  3. var a = [1, 2, 3] 
  4. var b = a[0...2] 
  5. var c = a 
  6.  
  7. a===b 
  8. b===c 
  9. c===a 
 
这个东西吧,第一句话是否注释掉结果截然不同。我们日常讨论的问题好多都是去掉了Cocoa 之后是另外一个样子。可见苹果对于这门语言的设计还是不够规范的。故我不推荐新手们浪费时间去学这个东西,以及Swift 就目前来玩是完全无法替代Objective-C的,我们的项目基本上都会或多或少的混入一些C++的内容,例如自己的移动端通用的网络协议、Facebook Pop、OpenCV之类。Swift是无法直接调用C++的,它需要用OC封装好了去调用OC。
 
在我们开始写正文之前,再啰嗦一句:
注意:这篇文章仅仅是WWDC Session 402 Introduction to Swift的学习笔记,不含任何猜想与揣测。所有代码都是默认有import Cocoa的。
 
Swift Basic

苹果从WWDC Keynote 开场到 这个Session 都一直在提三个词。SAFE/MODERN/POWER,包括Session上面讲解每个feature的时候,都会说这个体现了Swift哪个特性,例如类型推断 就体现了 SAFE 和 Modern 之类的。经常点题很棒,给好评!
 
众所周知:程序 = 代码 + 算法 + 数据结构,而代码的基础是组成每一句的变(常)量。
 
不过在我们在介绍常量之前,先高呼一声:
  1. println("Hello World!"
 
变(常)量
Swift 的 变量定义十分简单:
  1. var lang: String = "Swift" 
 
我们前面说过Swift是内置类型推断的,所以我们往往可以省略 :String
  1. var lang = "Swift" 
 
这里有一个和Objective﹣C以及和Swift长得非常像的js之类的语言不同的地方,Swift的变量一旦声明了,就不能修改类型了,上面的例子中lang只能是字符串类型。之后无论如何赋值都不能修改其类型。
 
所以我们说设置类型是往往可以省略的,但有些地方还是不能省略的。特别是在Collection类型上面,例如一个本来想放NSObject的字典,因为初始化用的字符串,就被推断成了一个完全都是字符串的字典。
 
还有另外一个比较无厘头的和Objective-C不同的地方,Swift 可以用任意Unicode字符当做变量名,也就是说,中文,图释,都是可以的。
  1. var ???? = "cat" 
  2. var 鼠標 = "????" 
 
常量的定义和变量非常相似,将var换成let即可。非常有 Apple Script 范儿,是不是!
 
字符串
声明
字符串的声明我们上面提过了.
注意这里生成的String,并不是NSString。他甚至都不是一个Object,我们可以在def里面看到:
  1. struct String { 
  2.     init() 
 
String和CFString一样,他是一个Struct! 不过,苹果爸爸非常漂亮的给我们提供了便利的解决方案。String可以和NSString互相替代,而且API通用。
 
例如:
  1. let components = ["~/txx/swift"]  
  2. OutPut: [~,"txx","swift"
注意:result中的那个数组,并非是NSArray,而是Array。以及Array和String一样,也是结构体。不过,Array不具有NSArray的API特性。需要显示转换成NSArray才可以调用其方法,这些我们之后再提。
 
枚举
在Swift中,我们可以通过 for in 枚举字符
  1. let str = "swift" 
  2.   
  3. for k in str{ 
  4.     println(k) 
 
这里有个优化,与我们在OC下,用
  1. - (unichar)characterAtIndex:(NSUInteger)index 
 
枚举的Char不同,这个是支持 Unicode 的, 也就是中文再也不会被拆成两个字符了。
 
顺路一说:我们可以通过
  1. let dog: Character = "????" 
生成一个纯字符类型。
 
符号操作
加法:终于摆脱了OC那冗长的扩展 stringByAppendingFormat, 可以直接用+、+=代替了。
 
转义:也摆脱了OC的stringFromFormat和 c style的 %d %@传值。
  1. let a = 3, b = 5 
  2. let ab = "\(a) * \(b) is \(a*b)" 
 
Array
声明
  1. let a = ["a""b""c"
不过这里有点小坑,之后会说
 
枚举
通过循环枚举,即
  1. while flag {enum(arr)} 
  2. for a in arr {} 
  3. for var i=1;i<13;++i {} 
  4. for i in 1..10 {} 
 
这里提一个细节:
.. 代表的是半开半闭区间也就是a..b就是[a,b);
...代表一个完全封闭区间也就是a...b就是[a,b];
 
修改
1. += 操作
  1. var a = ["a"
  2. a += "b" 
  3. a += ["c""d""e"
 
2. = 操作
  1. a[0] = “f” 
  2. a[3..5] = ["c""e"
 
第一个操作很容易理解,第二个操作有点变态。因为他不是在 3..5 换成 [“c”, “e”]数组,如果我写成了
  1. a[3..3] = ["c""e"
 
你会发现他变成了插入,我猜这地方以后一定要出面试题!
 
我们可以这么理解这句话,他干的事情是:
1. 把这个区间清空
2. 在区间开始的位置,插入赋值的数组。
 
这个功能仅在区间状态有效,如果a[3] = [“c”, “e”] 则会报错
 
NS容器和Swift容器的区别
除了我之前提过的:虽说Array和String相仿,是一个 Struct,NSArray是一个Class,但Array的API与NSArray不能通用,不过可以像C#那样通过as语法显示转换。但这里面有点坑,这个Session也没有说,我们就不提了。
 
还有两点不同:
1. Swift的Collection可以放入任何东西,即不只是Object,我还可以放入int\double这些类型。而NS系列必须是NSObject的子类
2. Swift的Collection 是 Typed Collections,也就是我们上面说过的类型推断。
 
如果我们用上面的代码声明了一个数组:
  1. let a = ["a""b""c"
 
那么,a就是一个完全的字符串数组了,不可能再放入其他类型,而NSMutableArray的话,可以放入其他Object。
 
这里稍微引申一下:
  1. let a = ["a", 1] 
  2. let a: Array = ["a", 1] 
  3. let a: Array<any> = ["a", 1] 
 
这三种情况,看起来可能 前两种和第三种不同,但实际上是完全不同的:
第一个被推断为了 NSArray,之后不准做增加操作,会提示没有重载 +=运算符;
第二个被推断为了 NSObject[];
第三个被推断为了 Array;
 
然后还发现了一个Bug
  1. var a = ["a""b", 1] 
  2. a += 1 
貌似是 a 变成了 NSString,b还是String。
 
字典
声明
  1. var animalLegs = [“pig”: 4, "dog":4, "Snake":0] 
 
枚举
除了常规枚举key去找value以外,Swift还支持枚举元组(tuple)
  1. var animalLegs = [“pig”: 4, "dog":4, "Snake":0] 
  2. for (animalName, Legs) in animalLegs 
  3.  blablabla 
 
修改
  1. var animalLegs = [“pig”: 4, "dog":4, "Snake":0] 
  2. animalLegs["chihuahua"] = 5  //Add new Key/Value 
  3. animalLegs["chihuahua"] = 4  //Modify Value 
 
上文中提到,我们在一个 <Stirng, Int> 的字典里面查找信息。但明显遇到了一个问题:Int是一个Value不是引用,也就是不能传回来NSNull之类的东西了。若像NSString的Range一样返回NSNotFound(-1)的话,也不对。毕竟-1以及-2147483648~2147483647都是我们可能要的数据。所以返回数字不成立。那该怎么办?
 
? !
对你没看错,我没有打错,标题就是 ? !,这不是三元运算符,也不是not,而是Swift的新用法。据楷雯说,这是从Ruby抄过来的特性。
 
?
? 表示的是:Optional。写法如下:
  1. var possibleValue: Int? = animalLegs["chihuahua"
 
这样得到的是一个wrapper(注意wrapper的w不发音,读作[?ræp?]),他可以用nil判断了。如下:
  1. if possibleValue { 
  2.   println("Nyan"
  3. else { 
  4.   println("> <"
 
不过这个 类似NSNumber 的东西,不能通过intValue之类的途径获得里面的内容,这时 !就出场了。
 
!
!的作用就是突破Optional,拿到内部值
  1. if possibleValue { 
  2.   let value = possibleValue! 
  3.   println("Chihuahua has \(value) legs"
  4. else { 
  5.   println("> <"
 
不过这里,有个简练的写法,更漂亮优雅。
  1. if let value = possibleValue 
  2.   println("Chihuahua has \(value) legs"
  3. else 
  4.   println("> <"
 
Control Flow
我上高中的时候,选修课老师提到: 无论多么精致的程序,多么炫酷的算法,全都是由顺序,循环,分支组成的。
 
顺序循环前面都介绍过了,这里来把分支介绍了。
 
单路分支
  1. if blablabla { 
  2. else
注意:这里的大括号不可以省略。
 
多路分支
  1. switch num 
  2. case 1: 
  3.   
  4. case 2: 
  5.   
  6. default
  7.   
注意:不再需要写break,且default必须有!且必须所有条件都要覆盖到,一般情况下都会有default,但是若在enum中使用,就可以用case搞定了。
 
对于case来说,不仅可以写 case 1, case 2, 还可以写 case 1, 2, 3, 4, 甚至 case, 1, 2, 3, 4, 7..10。
 
Function
函数是编程语言必不可少的元素,Swift的函数声明相对于OC也有很多灵活的地方。
 
先从最基础的函数样式开始说起:
  1. func printHelloWorld(){ 
  2.   println("Hello World"
  3.   
  4. printHelloWorld() 
 
传参数:
  1. func printMessage(message:String, repeat: Int){ 
  2.   for i in 1...repeat   { 
  3.       println(message) 
  4.   } 
  5.   
  6. printMessage(message: "Hello World", repeat: 2) 
  7. printMessage("Hello World", 2)//在没有默认参数的情况下可以这么写 
  8. 与OC不同,这里可以预置默认参数 
  9. func printMessage(message:String = "Hello World", repeat: Int = 1){ 
  10.     for i in 1...repeat     { 
  11.         println(message) 
  12.     } 
  13.   
  14. printMessage() 
  15. printMessage(repeat: 2) 
 
返回值:
  1. func plus(a: Int, b: Int)->Int 
  2.   return a + b 
  3.   
  4. println(plus(1, 2)) 
 
返回值的写法虽说挺难看的吧,但支持tuple:
  1. func requst(url: String)->(statusCode: Int, response: String) 
  2.     return (404, "Not Found!"
  3.   
  4. let (statusCode, message) = requst("http://google.com"
  5.   
  6. println(statusCode) 
  7. println(message) 
 
觉得let (a,b) = function() 丑?没关系,还有更优雅的写法:
  1. func requst(url: String)->(statusCode: Int, response: String) 
  2.     return (404, "Not Found!"
  3.   
  4. let result = requst("http://google.com"
  5.   
  6. println(result.statusCode) 
  7. println(result.response) 
 
闭包(Closures)
和 js 差不多,直接是:
  1. let a = { 
 
但与js不同的是:他其实是下面代码的缩写
  1. let a:()->() = { 
 
也就是说,闭包其实是 Swift 的一种类型,写作()->(),那么他就可以当做 func 的参数嵌套了。写成这样:
  1. func exec(repeat: Int, block:()->()) 
  2.   for i in 1...repeat{ 
  3.       block() 
  4.   } 
  5.   
  6. exec(10, {println("Hello World")}) 
 
Swift 还提出了一个特殊的词汇:Trailing Closures,他的意思是:如果闭包是func的最后一个传参,那么他可以写在外面。如下:
  1. func exec(repeat: Int, block:()->()) 
  2.     for i in 1...repeat{ 
  3.         block() 
  4.     } 
  5.   
  6. exec(10){ 
  7.     println("Hello World"
 
Class
和Objective-C 不同,Swift的Class,没有头文件。
 
定义
  1. class nyan{ 
  2.   
 
声明
  1. var a = nyan() 
 
和Struct不同,只要是Class类型,就是Ref,而不是Value,且我们不用管理内存。也就是说:它是自动内存管理的。
 
初始化
init关键词,和OC一样。不过这块非常像CPP,如下面这东西
  1. class nyan{ 
  2.     var num: Int = 1024 
  3.     init() 
  4.     { 
  5.         num = 1; 
  6.     } 
  7.   
  8. class miao:nyan 
  9.     var num1: Int = 1 
  10.     init() { 
  11.         num1 = 2 
  12.     } 
  13.   
  14. var a = miao() 
  15.   
  16. println(a.num) 
  17. println(a.num1) 
 
如果以OC的思维来看,它输出的应该是 1024, 2。因为OC只要没有call super,就是覆盖掉了。
 
但实际上Swift输出的是 1, 2。倘若,我们在两个init加上打log的话,会发现,子类的init先运行,然后运行父类的。
 
那么什么时候在子类的init里面call super.init()呢?
 
显而易见:当在子类里面要调用父类的成员变量时,要先调用super.init()去初始化它,若没有写的话,XCode会报编译错误。
 
重载
和C# java一样,前面要顶着override关键词。
 
Property
Swift 拥有两种 Property,一个是Stored Property, 另一个是 Computed Property
  1. Stored Property 
  2. class nyan{ 
  3.     var num = 3 
  4.   
  5. var a = nyan() 
  6.   
  7. println(a.num) 
  8. a.num = 1 
  9.   
  10. println(a.num) 
 
由于Class是Ref,所以哪怕我们用 let a = nyan(),我们也可以修改a中的内容。只是我们不能修改 a 指向的地址罢了。
 
注意,如果我没理解错的话:
Class的所有成员变量都是Stored Property,没有private关键词来修饰它的。
 
Computed Property
顾名思义,Property的值是经过计算而来的。一般常常用在readOnly属性上,和C#一样也是用get set关键词来做。
  1. class nyan{ 
  2.     var num: Int 
  3.     { 
  4.    
  5.     get{ 
  6.         return 1024 
  7.     } 
  8.     } 
  9.   
  10. var a = nyan() 
  11.   
  12. println(a.num) 
 
如果没有set关键词的时候,get{}可以省略:
  1. class nyan{ 
  2.     var num: Int 
  3.     { 
  4.      return 1024 
  5.     } 
  6.   
  7. var a = nyan() 
  8.   
  9. println(a.num) 
 
如果我没有理解错的话,如果需要set关键词的时候,那就一定要一个 Stored Property。因为没有私有变量…
  1. class nyan{ 
  2.     var fakePrivateNum: Int = 1024 
  3.   
  4.     var num: Int 
  5.     { 
  6.     set{ 
  7.         self.fakePrivateNum = newValue 
  8.     } 
  9.   
  10.     get{ 
  11.         return self.fakePrivateNum 
  12.     } 
  13.     } 
  14.   
  15. var a = nyan() 
  16.   
  17. println(a.num) 
  18. a.num = 1 
  19.   
  20. println(a.num) 
 
继承修改set / get 的时候要注意,这里和OC还不太一样。如果我这么写了:
  1. class nyan{ 
  2.     var num: Int = 1024 
  3.   
  4. class miao:nyan 
  5.     override var num: Int{ 
  6.     set{ 
  7.         num = 2 * newValue 
  8.     } 
  9.     get{ 
  10.         return num 
  11.     } 
  12.     } 
  13.   
  14. var a = miao() 
  15.   
  16. println(a.num) 
  17. a.num = 1 
 
编译程序会报错,而且会出现栈溢出,大量递归调用在 return 那一步。正确的写法应该是:
  1. class nyan{ 
  2.     var num: Int = 1024 
  3.   
  4. class miao:nyan 
  5.     override var num: Int{ 
  6.     set{ 
  7.         super.num = 2 * newValue 
  8.     } 
  9.     get{ 
  10.         return super.num 
  11.     } 
  12.     } 
  13.   
  14. var a = miao() 
  15.   
  16. println(a.num) 
  17. a.num = 1 
  18.   
  19. println(a.num) 
  20. willSet didSet 
 
如果我们想监视,StoredProperty的修改,可以这样:
  1. class nyan{ 
  2.     var num: Int = 1024 
  3.     { 
  4.     willSet{ 
  5.         println("newValue: \(newValue)" ) 
  6.     } 
  7.     didSet{ 
  8.         println("oldValue: \(oldValue)"
  9.     } 
  10.     } 
  11.   
  12. let a = nyan() 
  13. a.num = 1 
 
OOP这面的东西好乱…信息量好大…..写的也好乱….
 
Struct
一句话形容它:是一个Value类型的不能继承的Class。
 
不过注意两点
1. 虽说不能继承,但是可以实现接口!
2. 虽说和class功能一样,但是他所有method是只读权限,和cpp的const一样..如果想要在其中修改struct的member,就要在前面加上 mutating
 
例如:
  1. struct nyan{ 
  2.     var num: Int = 1 
  3.   
  4.     func changeNum(x:Int) 
  5.     { 
  6.         if (x>10) 
  7.         { 
  8.             num += x 
  9.         } 
  10.     } 
  11.   
  12.     init() 
  13.     { 
  14.         changeNum(11) 
  15.     } 
这么写直接CE, 要在前方加上 mutating
 
Enumerations
和 C家族的 Enum 不太一样,和 Java 的比较像。
 
他可以不包含实际类型,例如:
  1. enum compassPoint{ 
  2.   case North, South, East, West 
 
在调用的时候,可以 var a = compassPoint.West 当a被推断为 compassPoint 类型的时候,就可以用 a = .East赋值了。
 
他可以包含多种类型,例如:
  1. enum TrainStatus{ 
  2.     case OnTime 
  3.     case Delayed(Int) 
  4.   
  5.     init(){ self = OnTime } 
  6.   
  7.     var description: String{ 
  8.     switch self{ 
  9.     case OnTime: 
  10.         return "On Time" 
  11.     case Delayed(let minutes): 
  12.         return "delayed by \(minutes) minutes" 
  13.         } 
  14.     } 
  15.   
  16. var status = TrainStatus() 
  17. println(status.description) 
  18. status = .Delayed(42) 
  19. println(status.description) 
enum 的定义可以内嵌到 class中
 
Extensions
有点像OC的Category,但是Swift是静态语言,他不再需要msg_send这样的方法来调用method,所以,Extension不能做出来Swift类的method Swizzling。但同时可以扩展的东西不局限在class上了,一切的一切都可以扩展。
 
例如Int:
  1. extension Int 
  2.     func repetitions(task:()->()) { 
  3.         for i in 0..self 
  4.         { 
  5.             task() 
  6.         } 
  7.     } 
  8.   
  9. 500.repetitions(){ 
  10.     println("Hello"
注意:Int也好,String也好。他们虽说是值类型,但是和C的int还不是一个意思。他是值类型的对象,并不是一个值。正是因为这个特性,我们才可以在Int内部call self,给他写extension。
 
泛型
不用过多解释了和其他语言的一样。
  1. struct Stack<T>{ 
  2.     var elements = T[]() 
  3.   
  4.     mutating func push(element: T){ 
  5.         elements.append(element) 
  6.     } 
  7.   
  8.     mutating func pop() -> T { 
  9.         return elements.removeLast() 
  10.     } 
  11.   
  12. var intStack = Stack<Int>() 
  13. intStack.push(50) 
  14. let lastIn = intStack.pop() 
 
总结

终于写完了三篇笔记。短短50分钟的视频,信息量太大了。开始写的时候觉得这门语言是个坑,但整篇写下来觉得这门语言很优雅。
 
拥有动态语言的书写风格,静态语言的运行速度,现代语言的特性,传统语言般严谨的oop。
 
不过相对的,学习成本高多了:
首先,你要理解oop的继承,重载,多态,泛型
其次,你要熟悉动态语言的书写风格,
然后,你要对高级现代工业语言Java/C#的特性有所了解,语法看起来非常不同,其实和C#还真的是蛮像的。
最后,你要熟悉OC以及相关库,毕竟iOS/OSX开发,目前来看离不开OC。
 
总之,这门语言对于新手来说,门坎太高,但是非常优秀。不愧是LLVM的作者写的语言啊!

CocoaChina是全球最大的苹果开发中文社区,官方微信每日定时推送各种精彩的研发教程资源和工具,介绍app推广营销经验,最新企业招聘和外包信息,以及Cocos2d引擎、Cocos Studio开发工具包的最新动态及培训信息。关注微信可以第一时间了解最新产品和服务动态,微信在手,天下我有!

请搜索微信号“CocoaChina”关注我们!

搜索CocoaChina微信公众号:CocoaChina

顶部