“Swift is a new programming language for iOS and OS X apps that builds on the best of C and Objective-C, without the constraints of C compatibility. Swift adopts safe programming patterns and adds modern features to make programming easier, more flexible, and more fun. Swift’s clean slate, backed by the mature and much-loved Cocoa and Cocoa Touch frameworks, is an opportunity to reimagine how software development works.”
摘录来自: Apple Inc. “The Swift Programming Language”。 iBooks. https://itunes.apple.com/cn/book/swift-programming-language/id881256329?mt=11
Swift是供iOS和OS X应用编程的新编程语言,基于C和Objective-C,而却没有C的一些兼容约束。Swift采用了安全的编程模式和添加现代的功能来是的编程更加简单、灵活和有趣。界面则基于广受人民群众爱戴的Cocoa和Cocoa Touch框架,展示了软件开发的新方向。
Swift已经存在了多年。Apple基于已有的编译器、调试器、框架作为其基础架构。通过ARC(Automatic Reference Counting,自动引用计数)来简化内存管理。我们的框架栈则一直基于Cocoa。Objective-C进化支持了块、collection literal和模块,允许现代语言的框架无需深入即可使用。(by gashero)感谢这些基础工作,才使得可以在Apple软件开发中引入新的编程语言。
Objective-C开发者会感到Swift的似曾相识。Swift采用了Objective-C的命名参数和动态对象模型。提供了对Cocoa框架和mix-and-match的互操作性。基于这些基础,Swift引入了很多新功能和结合面向过程和面向对象的功能。
Swift对新的程序员也是友好的。他是工业级品质的系统编程语言,却又像脚本语言一样的友好。他支持playground,允许程序员实验一段Swift代码功能并立即看到结果,而无需麻烦的构建和运行一个应用。
Swift集成了现代编程语言思想,以及Apple工程文化的智慧。编译器是按照性能优化的,而语言是为开发优化的,无需互相折中。(by gashero)可以从"Hello, world"开始学起并过渡到整个系统。所有这些使得Swift成为Apple软件开发者创新的源泉。
Swift是编写iOS和OSX应用的梦幻方式,并且会持续推进新功能的引入。我们迫不及待的看到你用他来做点什么。
以上一些关于swift的介绍,相信耐不住性子的你也懒得看了,让我们开始敲代码吧!
————————————————————————————————————————————————
Hello world 我们来了
使用新的关键字,println 打印就是这么简单,
然后用OC的NSLog尝试了一下
so easy,但是在输出的时候就有不同了,
是不是感觉println比较像是C级别的,NSLog是OC的方式。
如果你写过C或Objective-C代码,这个语法看起来很熟悉,在Swift,这就是完整的程序了。你无需导入(import)一个单独的库供输入输出和字符串处理。全局范围的代码就是用于程序的入口,所以你无需编写一个
main()
函数。你也无需在每个语句后写分号。
也就是说,你完全可以忘记;这个你习惯了的断句用的东东。。。
使用 let 来定义常量, var 定义变量。常量的值无需在编译时指定,但是至少要赋值一次。这意味着你可以使用常量来命名一个值,你发现只需一次确定,却用在多个地方。
来看看let 定义的常量如果给他改变值会怎么样
直接报错了,告诉你不能给let定义的字段在赋值。
是不是有点像C里的const或者说类似 宏define?
而且初始化的时候必须得给其赋值。
这里的常量定义类似于函数式编程语言中的变量,一次赋值后就无法修改。多多使用有益健康。
一个常量或变量必须与赋值时拥有相同的类型。因此你不用严格定义类型。提供一个值就可以创建常量或变量,并让编译器推断其类型。在上面例子中,编译其会推断myVariable是一个整数类型,因为其初始化值就是个整数。
类型与变量名绑定,属于静态类型语言。有助于静态优化。与Python、JavaScript等有所区别。
如果初始化值没有提供足够的信息(或没有初始化值),可以在变量名后写类型,以冒号分隔。
let imlicitInteger = 70
let imlicitDouble = 70.0
let explicitDouble: Double = 70
这是官方给出的例子,然后我自己反复试验了一下,当:后面接的是double型,你去打印%d的结果。。。就是0!打印%f才对,这样怎么说呢,比较严谨,恩,否则就是错误的。。
而且当你把一个double类型赋值给int也会报错,比如
所以只需要注意相关问题应该就能正常使用。
练习
创建一个常量,类型为Float,值为4。
值永远不会隐含转换到其他类型。如果你需要转换一个值到不同类型,明确的构造一个所需类型的实例。
很有趣不是么,这个自己帮你定义了一个label,然后上面显示的是The width is 但是呢因为是白色的字和白色底,你看不到,看最下面的,你就知道了,至于这个width是个比较有趣的。。。那个小蓝圈就是表示的94,具体后面讨论。
如果去掉那个类似强转的string会有什么结果呢?
它提示错误了,,,这里有点C++运算符重载木有定义的味道。
还有更简单的方法来在字符串中包含值:以小括号来写值,并用反斜线("")放在小括号之前。例如:
这种方式类类似于将 \ 作为一个通配符,什么类型都能强行转换成字符串?(这个以后回头在看看)
练习
使用 () 来包含一个浮点数计算到字符串,并包含某人的名字来问候。
当然这里的float完全不用去写,因为Money的类型会根据后面的值来取类型。
创建一个数组和字典使用方括号 "[]" ,访问其元素则是通过方括号中的索引或键。
在oc里面@[]使用便利构造器构造一个数组,swift里面可以用来构造数组和键值对。
可以直接通过c的写法给对应的元素赋值,数组使用下标,而词典使用键值对形式。
那么来看看两种特殊情况,1是没有这个元素的数组给他赋值
like this:
数组越界,首先,数组开始最多也只有4个,也就是到下标为3的,虽然变异没有报错,但是后面的一系列现实都没有了,执行后报错为
另外一种情况就是给不存在的key赋值
毫无问题,输出后的结果是3对键值对。
恩,数据比较类似C语言的数组类型,也比较像NSArray。
要创建一个空的数组或字典,使用初始化语法:
这个比较好理解吧,数组里面的值都是string,然后词典里面的都是字符串对应的浮点型
如果类型信息无法推断,你可以写空的数组为 "[]" 和空的字典为 "[:]",例如你设置一个知道变量并传入参数到函数:
shoppingList = [] //去购物并买些东西 by gashero
//吐槽一下这个ios6的playground 最右边的眼睛旁边的相当于终端显示按钮,多点几次,从最开始的圆整个显示,到后面的连一半都木有了。。。苹果的工程师们要闹哪样啊
控制流
使用 if 和 switch 作为条件控制。使用 for-in 、 for 、 while 、 do-while 作为循环。小括号不是必须的,但主体的大括号是必需的。
首先定义了一个常量数组,然后定义了一个变量
使用for in来便利这个数组,然后做出相应操作,可以看到,确实小括号不写也木有问题了。。而且这个score也需要给定类型了。
然后再看看右边的结果,5个元素分别告诉你哪个循环里面进了几次。
在 if 语句中,条件必须是布尔表达式,这意味着 if score { ... } 是错误的,不能隐含的与0比较。
如果我比较的条件不是bool值,那么就会出现上面的错误。
你可以一起使用 if 和 let 来防止值的丢失。这些值是可选的。可选值可以包含一个值或包含一个 nil 来指定值还不存在。写一个问号 "?" 在类型后表示值是可选的。
需要注意的一个问题就是,不要给常量赋值变量,这个其实在c中是比较常见的,而在oc中多是使用对象,用的比较少了!
但是当你用可选值来赋值就没问题:
这里,let name = optionalName optionalName就是一个变量,但是之前用:?来表示可选了之后就可以使用
通过
optionalName
==
nil
进入if语句里面来判断,name的值为可选值,当可选值为nil时,他会走else语句
也就是说,根据可选值来判断,为nil走对应,反正会把值赋值给 let定义的对象
switch 支持多种数据以及多种比较,不限制必须是整数和测试相等。
上面的例子中
1.使用的是一对一比较
2.使用的是一对二比较
3.使用的是是否包含
相对于以前的switch灵活许多,以前的这样的写法基本都是采用if语句来写。
在执行匹配的情况后,程序会从 switch 跳出,而不是继续执行下一个情况。所以不再需要 break 跳出 switch 。
但是还是需要default这个语句,否则还是会报错
然后就是switch的值可以是变量,其他的好像没多少需要注意的吧。
可使用 for-in 来迭代字典中的每个元素,提供一对名字来使用每个键值对。
这个代码其实很好理解,for in遍历数组,分别取出每一对键值对,然后取出每个number的值来比较。最后取出最大。
最有趣的是后面的这个5 times,也就是进去执行了5次,按我们经常的写法,然后呢,键值对的无序导致这个times不确定,虽然实验了几次,个人觉得他再内部比较是遵从一个Z->A的顺序吧。(当然这需要大量的实验,咋不纠结其内部具体如何实现)。这里的实现是直接比较Square的值之后在与上面的值进行比较所以只有5次,另外实事绘图也是这么比较的
当然不排除巧合。这里不做深入研究,只是为了说明一下这个同步显示的功能
练习
添加另一个变量来跟踪哪个种类中的数字最大,也就是最大的数字所在的。
var maxKeyString :String循环外面定义个字符串类型的,然后再if语句块里执行 maxKeyString = kind 就能够得出结果了。
需要注意的时候,你不能直接写成 var maxKeyString必须制定类型。
使用 while 来重复执行代码块直到条件改变。循环的条件可以放在末尾来确保循环至少执行一次。
这个结合右边的结果来看,进入循环6次的while语句,和do ,…while,这个就不说了,基本都知道的
你可以在循环中保持一个索引,通过 ".." 来表示索引范围或明确声明一个初始值、条件、增量。这两个循环做相同的事情:
上面和下面是两种不同的写法,结果是一样的,,习惯就好 :) (可以理解为添加了一个简单..,其实个人感觉差不多)
使用
.. 构造范围忽略最高值,而用
... 构造的范围则包含两个值。
函数与闭包
使用
func 声明一个函数。调用函数使用他的名字加上小括号中的参数列表。使用
-> 分隔参数的名字和返回值类型。
我觉得在return后应该没有输出才好,而是在函数调用后面有输出,这样不会歧义,最起码我知道我这个函数调用结果是啥。
这里说说一点不同点,在c系列的里面都是习惯性的将返回值放在最前面,然后是函数名,后面跟的参数,在swift里面不一样了,他是放在后面用 -> 箭头来表示返回值。。。
那么上面的函数就好理解,用func来申明这是一个函数,然后 函数名叫 greet ,参数两枚 ,(这里需要说明的,就是它这个写法跟C的是相反的,C的是类型后面跟变量名,这个是变量名后面用:类型 来表示类型,好像这就是这个语言申明他类型的一个特色吧,像那种语言,小弟才疏学浅啊)
然后用 -> 类型 最后才是函数体。
调用基本上没变化。
使用元组(tuple)来返回多个值。
这个是一个相对于OC来说新的特色,可以同时返回多个值。
函数可以接受可变参数个数,收集到一个数组中。
看看后面的两个执行结果,传参用...表示参数个数是可变的但是类型必须都是int型的。不给参数也行。很奇特不是么,目前暂时想不到有啥用o(╯□╰)o
练习
编写一个函数计算其参数的平均值。
这个比较简单,定义一个数来记录个数,最后返回结果,注意为零时候的情况,否则执行结果异常
函数可以嵌套。内嵌函数可以访问其定义所在函数的变量。你可以使用内嵌函数来组织代码,避免过长和过于复杂。
函数里面写函数我觉得吧,好吧,不发表看法。。。当然肯定是有用的
函数是第一类型的。这意味着函数可以返回另一个函数。
这个写法很奇特,特别是外面的->(int->int)这句,表示的是返回的是一个参数为整数返回值为整数的函数.
var 所定义的变量不局限于普通的数据类型,也包括了函数。
一个函数可以接受其他函数作为参数。
这里就是hasAnyMatches函数有两个参数,一个为数组,另外一个是函数。
函数实际是闭包的特殊情况。你可以写一个闭包而无需名字,只需要放在大括号中即可。使用 in 到特定参数和主体的返回值。
咋一看,确实不太好理解,但是慢慢来理,->这个符号后面就是返回值,而从结果4次来看,通过in把numbers全部遍历了一遍。
每次便利的值就是number,然后处理完成后返回。
这里就涉及到一个关键字map。这个不知道怎么说,映射?
总的来说,就是通过map(映射?),然后再闭包里面将numbers给挨个遍历取值操作并返回。具体还要实验下
练习
重写一个闭包来对所有奇数返回0。
这里需要注意的一个问题是,这个闭包返回的最终结果没有改变原来的numbers的值。
编写闭包时有多种选项。当一个闭包的类型是已知时,例如代表回调,你可以忽略其参数和返回值,或两者。单一语句的闭包可以直接返回值。
这句语句的返回结果和之前的一样。就是忽略了两者,返回类型和参数类型。
你可以通过数字而不是名字来引用一个参数,这对于很短的闭包很有用。一个闭包传递其最后一个参数到函数作为返回值。
一个系统函数,排序,参数是一个数组,然后参数块设定一个排序方式,经历9次,然后输出排序完成后的结果。。
结果输出就是12,5,3,2,1
我很好奇他这个9次怎么来的,如果只是换的次数那么只有6次,如果是比较的次数应该是1...4 之和为10,少了一,那么有办法验证么?无聊的时候可以排排看
对象和类
使用
class
可以创建一个类。一个属性的声明则是在类里作为常量或变量声明的,除了是在类的上下文中。方法和函数也是这么写的。
class 声明一个类,shape为类名,定义了一个变量,一个方法
练习
通过 "let" 添加一个常量属性,以及添加另一个方法能接受参数。
这个问题比较简单,算是巩固一下之前写的东西
通过在类名后加小括号来创建类的实例。使用点语法来访问实例的属性和方法。
解释一下,定义一个实例对象,用点语法给实例对象赋值,然后函数也能用点语法调用。。。。
这个版本的 Shape 类有些重要的东西不在:一个构造器来在创建实例时设置类。使用
init
来创建一个。
注意 self 用来区分 name 属性和 name 参数。构造器的生命跟函数一样,除了会创建类的实例。每个属性都需要赋值,无论在声明里还是在构造器里。
使用 deinit 来创建一个析构器,来执行对象销毁时的清理工作。
子类包括其超类的名字,以冒号分隔。在继承标准根类时无需声明,所以你可以忽略超类。
子类的方法可以通过标记 override 重载超类中的实现,而没有 override 的会被编译器看作是错误。编译器也会检查那些没有被重载的方法。
Square是NamedShape的子类,他有自己的的一个Double类型的变量 sideLength,然后有个初始化方法需要2个参数,有个方法返回的是一个浮点型的值,通过关键字override 来重载父类的一个方法(这里是个好方法,因为经常有可能会不经意间重写了父类的方法当继承关系特别深入的时候。。。)
最后就是可以用let 来定义一个实例对象!恩,const的味道。
练习
编写另一个 NamedShape 的子类叫做 Circle ,接受半径和名字到其构造器。实现 area 和 describe 方法。
属性可以有getter 和 setter 方法
上面的代码中定义一个新的NamedShape的子类——EquilateralTriangle
定义一个新的双精度的变量,初始化方法
然后定义一个变量 perimeter,他有一个get ,set方法,
在 perimeter 的 setter 中,新的值的名字就是 newValue 。你可以提供一个在 set 之后提供一个不冲突的名字。
来结合输出结果解释下这个get和set方法吧:
初始化的时候给定了sideLength的值为3.1,那为啥下面的perimeter为9.3呢,
因为我们在输出或者是打印什么的时候作为等号右边的,会去调用get方法,那么紧接着使用triangle.perimeter的值为9.3就不奇怪了,注意是使用,而不是赋值。
然后下面的把9.9赋值给他,他作为等号的左边会调用set方法,那么对应的sideLength的值也就会改变为3.3了。
注意 EquilateralTriangle 的构造器有3个不同的步骤:
- 设置属性的值
- 调用超类的构造器
- 改变超类定义的属性的值,添加附加的工作来使用方法、getter、setter也可以在这里
如果你不需要计算属性,但是仍然要提供在设置值之后执行工作,使用
willSet
和
didSet
。例如,下面的类要保证其三角的边长等于矩形的变长。
这个类咋看上去其实也蛮简单的,但是有个比较奇怪的问题,就是这个willset的方法。
类的方法与函数有个重要的区别。函数的参数名仅用于函数,但方法的参数名也可以用于调用方法(除了第一个参数)。缺省时,一个方法有一个同名的参数,调用时就是参数本身。你可以指定第二个名字,在方法内部使用。
看看这里的numberOfTimes 和times,在外面赋值时使用的是numberOfTimes,但在内部使用的时候简化了。当然这个times只能在函数内部使用。
当与可选值一起工作时,你可以写 "?" 到操作符之前类似于方法属性。如果值在"?"之前就已经是 nil ,所有在 "?" 之后的都会自动忽略,而整个表达式是 nil 。另外,可选值是未包装的,所有 "?" 之后的都作为未包装的值。在两种情况中,整个表达式的值是可选值。
神马都能够可选了。。这个可选是有多强大!
枚举值和结构
使用 enum 来创建枚举。有如类和其他命名类型,枚举可以有方法。
来解析一下这段代码,咋一看,好像一点switch的感觉,用case来表示每一个枚举值,只是通过从简化可以直接写出1-10
另外枚举值里面居然可以放函数,奇葩啊。好吧应该是有这个需求,否则苹果不会干这种奇葩事的。
下面的定义一个常量枚举值 是Rank里的Ten的值ace,然后可以通过toRaw()这个方法来取Ten所表示的枚举值,ace直接通过点语法来调用枚举里的函数。
练习
编写一个函数比较两个 Rank 的值,通过比较其原始值。
这个写法够奇葩吧,中文的函数名,对,swift支持多国语言的函数名和变量名,简单点说,我们自定义的非系统的关键字的类名,函数名,变量名都只是一个字面量,这个字面量相对于计算机就是一堆字符串,所以在解析上应该是没有问题的
在如上例子中,原始值的类型是 Int 所以可以只指定第一个原始值。其后的原始值都是按照顺序赋值的。也可以使用字符串或浮点数作为枚举的原始值。
Ace = 1这里,原值是1,Ace是实际值
使用 toRaw 和 fromRaw 函数可以转换原始值和枚举值。
这个使用fromRaw,取到jack,然后通过调用函数输出枚举值名
枚举的成员值就是实际值,而不是其他方式写的原始值。实际上,有些情况是原始值,就是你不提供的时候。
不给值那么就没有toRaw和fromRaw方法。。。,会报错
其他的好像也没什么好说的了。。。
练习
添加一个 color 方法到 Suit 并在 spades 和 clubs 时返回 "black" ,并且给 hearts 和 diamounds 返回 "red" 。
写法很简单,比较人性化吧,没有过多的操作符,用逗号隔开即可
然后有个东西比较奇特的就是,switch里面的case后的值会在之前加上一个点,之前控制流的却没有,尝试了一下,去掉点没有任何影响,
注意上面引用Hearts成员的两种方法:当赋值到 hearts 常量时,枚举成员 Suit.Hearts 通过全名引用,因为常量没有明确的类型。在 switch 中,枚举通过 .Hearts 引用,因为 self 的值是已知的。你可以在任何时候使用方便的方法。
结构体
使用
struct 创建结构体。结构体支持多个与类相同的行为,包括方法和构造器。一大重要的区别是代码之间的传递总是用拷贝(值传递),而类则是传递引用。
这里定义了一个扑克牌的结构体,一个字面量,一个花色,因为不是类,没有初始化方法,但是在初始化的时候传递了2个对应的参数,注意参数的值是带.的。。。然后输出这张牌的信息。。
这个枚举值的.必不可少!
练习
添加方法到 Card 类来创建一桌的纸牌,每个纸牌都有合并的rank和suit。(就是个打字员的活二,by gashero)。
一个枚举的实例成员可以拥有实例的值。相同枚举成员实例可以有不同的值。你在创建实例时赋值。指定值和原始值的区别:枚举的原始值与其实例相同,你在定义枚举时提供原始值。
例如,假设情况需要从服务器获取太阳升起和降落时间。服务器可以响应相同的信息或一些错误信息。
解释这段代码,定义枚举,注意这里的枚举的值类型不在是单一的了!
然后定义两个枚举值
在switch语句里做判断,对这个“.” 的用法要把握好,要记得!
根据输出结果就明白了,这个是干啥滴了!switch方便了许多。
练习
给 ServerResponse 添加第三种情况来选择。
注意日出和日落时间实际上来自于对 ServerResponse 的部分匹配来选择的。
接口和扩展
使用
protocol
来声明一个接口。
类、枚举和结构体都可以实现接口。
先看看这个非常简单的类,解释下代码:
额,发现其实也没啥好解释的就是实现了接口的一个变量,一个方法。后面的执行都属于正常范畴没有跳出我们的思考范围。。。
在看看这个简单的结构体类,和类一样,没啥说的!
注意声明
SimpleStructure
时候
mutating
关键字用来标记一个会修改结构体的方法。
SimpleClass
的声明不需要标记任何方法因为类中的方法经常会修改类。
练习:
写一个实现这个接口的枚举。
这里虽然我给他写入了一个set方法,但是他就是不实现,首先从语法上来说,没有出错,在考虑是否是因为他只定义了get方法,于是我想到去给他声明一个set方法,然后编译器就报错了!!!
另外还发现一个不知道是不是问题的问题,就是有一句语句不实现后面的无法执行,不过这样比较有效的知道代码是否有错
资源框架服务终止,因为编辑功能暂时有限。。
题外话,为了实现这么个小小的联系,发现居然各种不能啊,熟练程度不够。。。。
使用extension
来为现有的类型添加功能,比如添加一个计算属性的方法。你可以使用扩展来给任意类型添加协议,甚至是你从外部库或者框架中导入的类型。
相当于给int型添加了功能,OC里的类目?那为啥要用延展关键字呢 ,
category这个关键字跑哪去了!
难道是因为直接可以添加属性,虽然OC里不能直接通过类目添加属性!
好吧停止吐槽,继续往下走。
练习:给Double
类型写一个扩展,添加absoluteValue
功能。
这里有个需要注意的地方,如果是负数记得加上括号,否则会先执行点运算符然后在执行-号,返回-5
你可以像使用其他命名类型一样使用接口名——例如,创建一个有不同类型但是都实现一个接口的对象集合。当你处理类型是接口的值时,接口外定义的方法不可用。
这里的a是之前定义的一个类。
即使
protocolValue
变量运行时的类型是
simpleClass
,编译器会把它的类型当做
ExampleProtocol
。这表示你不能调用类在它实现的接口之外实现的方法或者属性。
关于上面说的这点,比较奇怪,
protocolValue
应该是个simpleClass,而这里却是告诉编译器他是
ExampleProtocol
的,反而还不能调用
simpleClass的类的方法和属性。
也就是说,在给顶它的类型为接口时,在runtime机制时就是作为一个接口存在,不过经验告诉我,可以潜入回归类的怀抱?
泛型
在尖括号里写一个名字来创建一个泛型函数或者类型。
通过这样一比较就能知道泛型大概是个神马意思了,相当于新定义一个类似OC的 id类型,只是这个类型包含所有类型,然后你给他一个别名。 itemType,随便一个可用的类型。有点类似C++的模板,不过不用你去管,通用类型。
你也可以创建泛型类、枚举和结构体。
对这个枚举的点语法残念很深啊。不过还是需要说明下,当确定变量具体的类之后,然后直接就可以用点语法给定枚举值了!
在类型名后面使用
where
来指定一个需求列表——例如,要限定实现一个协议的类型,需要限定两个类型要相同,或者限定一个类必须有一个特定的父类。
简单起见,你可以忽略
where
,只在冒号后面写接口或者类名。
<T: Equatable>
和
<T where T: Equatable>
是等价的。
练习:修改anyCommonElements
函数来创建一个函数,返回一个数组,内容是两个序列的共有元素 。