第一部分
简介
在学习任何一门语言的说或写的技巧时,人或机器总是会先从自己熟悉的语言中去寻找那些相似熟悉的语法或元素等从而进行类比。编程语言也是如此,毕竟编程语言也是语言不是╮(╯▽╰)╭
Swift也没什么可拽不一样的,它是由Objective-C,Rust,Haskell,C#和JavaScript基础上发展的。假定你会其中一门语言,那么恭喜你,毫无疑问的,你会发现循着这本书的学习,swift是如此的so easy!
前面两部分的内容会讲的比较简单,覆盖了一些Swift的简单语法,让你能快速的对Swift有一定的了解,认知,较为轻松的过渡到Swift这门语言中。
特别是对于那些有着多年Object-C开发的人来说,这两章会着重讲解Swift和Object-C之间的区别,让你能更快的上手。而且两种语言相似的地方非常多,毕竟苹果自家人也不能太坑自家人不是(⊙v⊙)。
好了,简介说完,是时候high起来了。
Getting Started - 开始
以前看过我们“by tutorials”系列教程书的人都知道,我们习惯建立实际的apps应用来进行练习学习。
因为这本书的前面几章的内容集中讲解的是语法的基本元素,每章都建一个实际app显然是不合适的,不过好在,Apple 引入了一个全新类型的xcode文件,专门用来练习基本语法,那就是playgrounds。
打开xcode,选择File\New Playground… ,选择iOS platform 然后保存文件到你觉得合适的位置:
一个playground便是一个单独的Swift文件模板如下所示:
当你在左边部分的view中敲入Swift代码时,右边的view会自动显示分配的值以及常用的与应用程序状态有关的结果。你无须每次都调用bulid方法刷新当前页面的值。在playground里,xcode会不断的bulid执行你写入的代码。有没有很高大上,和html开发一样,实时更新~~
所以说playground是你练习Swift技能的不二之选!反正不让我在这里练习,臣妾做不到。
在playground中你将能见到各种变量语法的特点。好了,你也该准备好开始学习Swift了。
Variables, constants and strings - 变量,常量和字符串
删除模板中的内容并用下面的代码来替换
var greeting: String = "Hello"
print(greeting)
你应该能够立马看到“hello”在右边用来分配值以及输出的面板中显示。右边的面板适合用来展示比较短的输出,但是较长的调试输出语句通常都会用到xcode的控制台。
为了能展示控制台的输出,选择View/Assistant Editor/Show Assistant Editor 菜单选项:
你当前用了一个var的关键字来定义了一个叫greeting的string类型的变量。并且给这个变量赋了个“Hello”为初始值。
更新你的代码如下:
var greeting: String = "Hello"
greeting = "Bonjour"
print(greeting)
你可以看到控制台的输出变为“Bonjour”,也就是说你可以给变量赋一个新的值。
再看一点让你出乎意料点的东西,你可以删除代码中关于类型定义的句子:
var greeting = "Hello"
有没有发现输出没有改变,也木有报错~~
这个例子简单的示范了下关于类型推断的功能,编译器能够明白你定义的greeting变量的类型,当你在双引号内输入一系列的字符时,Swift便能立马推断出这是一个用于赋初始值的String类型值。所以你无需指定变量的类型,在接下来的内容中你还会看到更多这样的例子。
他有能力推断类型,但Swift是静态类型的编译语言,是在编译的时候检查类型。动态类型的语言相比类型推断的规则就宽松的多。做个简单示范,看看静态编译类型的强制性。更新代码如下:
var greeting = "Hello"
greeting = 23
print(greeting)
编译器会报错,提示 cannot assign value of type ‘Int’ to type ‘String’
类型推断不会以任何方式对你妥协。他提示你移除掉错误的类型用于确保你的类型是安全的。上面的例子只是简单展示了下文字类型变量的推断,事实上在这个Swift语言里到处都有这样自动推断例子。
小技巧:我们推荐你大量的使用类型推断,这样能让你的代码更加简洁易读,当然,如果一个变量用一个明确的类型指定可以更易读的话,明确定义变量的类型当然是最好的!
让我们做一些简单的string操作,更新代码:
var greeting = "Hello"
greeting = greeting + " World"
print(greeting)
控制台输出“Hello World ”(这么简单的语法和1+1一样,还需要控制台来看?(。・∀・)ノ゙)
也就是说Swift的string是可变的,他的变化操作比NSMutableString中的语法还要简单很多。比如,你可以直接用 += 完成合并操作。
你有时也会需要生成永远不会变的值,毕竟变量值可能就因为你的一些蛋疼操作就改变了,银行的利息为0.05被你折腾为5,人生就美丽了╮(╯▽╰)╭
想要改为常量也非常容易,改变上面的一行代码:
let greeting = "Hello"
拽不拽!!!(反正我是觉得不拽,挺戳的(⊙﹏⊙)b)
提示报错:cannot assign to value: ‘greeting’ is a ‘let’ constant
你可以用关键词 var 或 let 来控制Swift 的类型是否是可变的(String,Int,Dictionary…同样)。 这就立马体现出和Object-C以及其他一些语言的不同之处。其他语言是在内部直接实现判断是否是可变还是不可变的,比如NSMutableArray 和NSArray ,直接在类里就定义了是否可变。
小技巧:你应该尽可能的将值都定义为常量。这样不仅可以让你的代码更安全,也可以让编译器进一步优化你的代码,提高运行效率。因此,你可能会发现以后你用let 的次数远远多于 var 。
用 let是为了声明一个不可变的实例,在Swift语言中,任何的结构体也可以定义为常量。但是结构体和类还是有些许区别,可在第三章“Classes and Structs ”中详细了解。
通过修改let为var让greeting重新变回变量,然后在打印语句前添加如下代码:
greeting.append(Character("!"))
在字符串结尾添加了一个感叹号,控制台输出:“Hello World! ”(废话啊!!!)
Swift string类型有如上的api方法让你对值进行操作。然而,仅仅只有有限的几个方法。还好,Swift为了弥补方法的不足,可以让你直接使用你熟悉的Object-C中所有的api方法。
先来介绍下使用方法:
import Foundation
var greeting = "hello world".capitalizedString
print(greeting)
输出:“Hello World”.
你先要导入包含了NSString 的基础框架Foundation,然后你就可以直接用NSSting的方法了。
既然都知道了,还不试下NSString api中其他方法在playground中的表现?!你甚至可以用stringByAppendingString 来继续填补字符串,只要你不嫌长,不嫌挤!
import Foundation
var greeting = "hello".stringByAppendingString(" world")
print(greeting)
最后提一下,Swift的String是一个值(value)类型,也就是说,当你分配一个string给变量,常量,或者作为传给方法的参数值。他的值是赋值的,也就是Object-C中的copy,生成新的值,有新的引用指针,和过来赋值的string没有关联。
看下下面的例子:
var alternateGreeting = greeting
alternateGreeting += " and beyond!"
print(alternateGreeting)
print(greeting)
alternateGreeting复制了一份“hello world”字符串,是和原来的字符串没有半毛钱关系的字符串。也就是说你修改了alternateGreeting,greeting不会进行改变
Semicolons - 分号
到目前为止,你有没有发现嘛不一样的地方?没错,你答对了,就是他,就是他,分号君。上面写的代码都没有分号。
更新上面的代码添加分号如下所示:
import Foundation;
var greeting = "hello world".capitalizedString;
print(greeting);
输出并没有改变,和其他语法不同的是,Swift并不需要用分号为每个语句块做结尾!
试试移除所有的回车换行:
import Foundation; var greeting = "hello world".capitalizedString; print(greeting);
依然正确。
但是如果你不换行又不打分号就要报错了
只有当你需要在一行写多个语句块时才需要添加分号。
小技巧:不要添加分号!
从上面的类型推断功能可以看出,Swift是非常简洁的语言。虽然分号只是一个字符,但是他的添加对于理解代码是没有任何意义的,所以何不去掉呢?(虽然改变习惯挺难的)
Numeric types and conversion -数值类型和转换
现在来让我们看看Swift的一些其他简单的类型。
替换掉playground上的代码:
var radius = 4
let pi = 3.14159
生成了两个数值类型的值:Int类型的radius,Double类型的pi。再次说明下,Swift能够从你分配上去的两个值来推断两个变量的类型。
Swift有不同大小的整数类型,例如Int8(有符号的整数),Int16(无符号的整数)。也有单精度类型Float。
除非你有某些特殊的需求,否则Int和Double应该是你不二之选。这两个类型的最大值足够大多数的应用使用。而且编译器会自动随着32位或64位机器来挑选适合大小的Int值。
在Swift中你可以用下划线来作为千位的分割线,代码如下:
let million = 1_000_000
这定义了一个等价于1,000,000的整型数值
你可以在Swift的变量中执行所有常用的数学操作符,做下简单的练习:
var area = radius * radius * pi
如果数学都忘光了,可以提示下你这是求圆的面积!(不明觉厉,差点真不晓得圆的面积咋求的了)
这时你应该会惊讶的发现编译器提示你两个乘法操作是错误的:binary operator ‘*’ cannot be applied to operands of type ‘Int’ and ‘Double’。
那这到底是为什么呢?(真想go die,和哄小孩一样~~(>_<)~~)
所有的操作符都被定义为了方法,上面的编译明确的指出了无法执行乘法,因为左边的是Int,右边的pi是Double。
这说明了Swift的一个非常重要的特点:所有的数值类型转换都必须是明确转换的,无论是变大还是变小,即无法自动将int转换为double,也无法自动将double转换为int。为了修复这个问题,代码如下:
var area = Double(radius) * Double(radius) * pi
计算得知面积area=50.265.
如上所示,解决的方案是将int型的radius转换为Double,你可能会说这只是假装变成了Double类型欺骗编译器。但其实不是的,是真的又初始化生成了一个新的Double(radius)。你将在第三章的“Classes and Structs ”了解到更多有关初始化的内容。
你可以Cmd+点击 任何一个Swift类型去查看他的“header”文件,你可以在里面查看类型,方法,协议等等。你会在里面发现相当多有趣的东西,比如说乘法操作符有各种各样的重载方法,但是他们的数值类型并没有混淆。
显示的类型转换可以减少编码中的错误。另外一个检查是临界值检查。代码如下:
var overflow = Int.max + 1
然后会提示报错:arithmetic operation ‘9223372036854775807 + 1’ (on type ‘Int’) results in an overflow 。 换句话说,Swift防止你的值超过定义类型值的最大值。在其他语言中,超过最大值溢出后数值会返回一个负值,但Swift在运行时立马处理为错误。
小技巧: 溢出错误是Swift实现算术运算符中的一部分,你也可以选择允许溢出,苹果提供的操作符有:&+, &-, &*, &/, &%
,这在处理一些整数值时应该是比较常用的。
Booleans - 布尔值
Swift有boolean 类型 Bool,它包含有true或false。再强调一次,你可以利用生成变量或常量时的初始值来推断类型。如下:
let alwaysTrue = true
Swift的另一方面的强制要求有,Bool中的值只能为Bool类型。与其他语言如Object-C对比,Swift不会将非0整数值处理为“true”。这也就是说你不能用整形值1或0来表示boolean,必须用真实的boolean(true,false)类型来定义这个值。
Tuples - 元组
Tuples(元组)是多个值合到一个组的单独数据类型,和类以及结构体不同的是,你在生成元组时无需明确定义他的类型。
生成一个元组测试:
var address = (742, "Evergreen Terrace")
print(address.0)
print(address.1)
第一行生成了一个包含了int和string的叫address的元组,后面的内容说明你可以通过索引值来调用address的值。
尝试访问元组使用越界的索引值。What happens? 我妥妥的能想到你已经猜到了:编译器报错。
你可以通过元组的索引值来修改部分元组的内容:address.0 = 744
上面的元组第一个值变为了744,如果你想让你的元组是不可变的,只需要将var改为let即可,编译器妥妥的就会给你报错,提示你let值内容不可修改。
编译器利用你元组中提供的值自动推断出每个元素位置的类型,如果你想要明确定义元组中每个值得类型,可以使用类型别名:
var address: (Int, String) = (742, “Evergreen Terrace”)
你会发现结果和上面是完全一样,最上面元组的类型便是(Int,String)。
做到这一步可以尝试下修改address中门牌号为double类型。这里有三种不同的方法可以实现
//用Double类型指定
var address1: (Double, String) = (742, "Evergreen Terrace")
// 明确定义值为Double型
var address2 = (Double(742), "Evergreen Terrace")
//将门牌号直接定义为Double
var address3 = (742.0, "Evergreen Terrace")
//你也可以解析元组里面每个单独的元素值,如下:
var address = (742, "Evergreen Terrace")
let (house, street) = address
print(house)
print(street)
输出结果自然没变。我们元组的值定义了常量house和street,这种方法提供了我们一种更为易读的方式来访问每个元组的元素。
你也可以将元组赋值给一个新的变量,修改每个变量的值,原来元组的内容不受改变,和上面提到的String是一样的。
最后一种定义元组的方法是使用它们的名字,如下:
var address = (number: 742, street: "Evergreen Terrace")
print(address.number)
print(address.street)
即使你是用名字定义的元组的每个元素,你也还是可以通过索引值进行访问。
元组是一种数据类型,和Swift中其他类型的值一样的。只要你愿意,你可以在元组中创建元组!
小技巧:
在后面的章节中,你将会了解到类和结构体可以提供和元组一样的方法以及更多的内容,更灵活的方式。元组做为一种构建复合类型的数据类型是非常简单好用的。
String interpolation - 字符串的插入
为了将你的address元组内容呈现给用户,在app开发中常用的方法便是格式化字符串。
更新代码:
var address = (742, "Evergreen Terrace")
let (house, street) = address
print("I live at " + String(house) + ", " + street)
控制台输出:I live at 742, Evergreen Terrace
上面的代码将一个字符串以及由一个Double转换为的字符串(重新初始化了一个String)组合到一起输出你想要的结果。这显然相对于Object-C更易读,但Swift还有更爽的技巧,更新代码:
print("I live at \(house), \(street)")
控制台输入一致。
上面的方法使用了Swift语言中被叫做字符串插入(string interpolation
)的方法,允许你在字符串中嵌入常量,变量以及表达式。
字符串嵌入不是简单的替换了NSLog方法,你可以构建你希望构建的字符串。如下:
import Foundation
var address = (742, "Evergreen Terrace")
let (house, street) = address
let
str = "I live at \(house + 10), \(street.uppercaseString)"
print(str)
控制端输出:“I live at 752, EVERGREEN TERRACE” 。这里示范了带有表达式的较为复杂的字符串嵌入。
note: 如果你要输出一个反斜杠字符,你需要再多加一个反斜杠:\ 。
Control flow - 控制流
For循环
你已经利用playground学会了使用简单的变量赋值操作,现在可以来试试控制结构,更新代码:
let greeting = "Swift by Tutorials Rocks!"
for i in 1...5 {
print("\(i) - \(greeting)")
}
这个for循环的区间为【1,5】,输出内容如下:
1 - Swift by Tutorials RObject-Cks!
2 - Swift by Tutorials RObject-Cks!
3 - Swift by Tutorials RObject-Cks!
4 - Swift by Tutorials RObject-Cks!
5 - Swift by Tutorials RObject-Cks!
上面演示了下for循环的封闭区间范围循环。范围区间类型一般是和for循环一起使用,但也可以拿出单独使用。
更新代码:
var range = 1...5
for i in range {
print("\(i) - \(greeting)")
}
NOTE:当使用for循环结构时,你没有用var来定义循环内容i,因为你实际用到的不是一个变量,相反,for循环会将其设置为常量常量,如果你修改循环的i会怎么样呢?
修改i的值会报错,如下图所示。
明显1…5是一种数据类型,那这种数据类型是什么呢?
封闭范围的操作符是一个Range的结构体,下面的操作和上面定义相同:
var range = Range(start: 1, end: 6) ,不过此方法在Swift3.0中被移除。
输出结果相同
上面用了一个名字叫Range的结构体来生成了range的实例,你将在本书的后面继续学习和结构体相关的内容。
x…y符号是一种用来生成Range的简单方法,顺带一提,你也可以使用x..
While loops - while 循环
Swift支持while循环和repeat-while(等效do-while,在Swift2.0被废弃改为了repeat-while)循环,当循环语句检查到最后一位值时则结束迭代,更新代码:
let greeting = "Swift by Tutorials RObject-Cks!"
var i = 0
while i < 5 {
print("\(i) - \(greeting)")
i += 1 //i++在Swift3.0废弃了
}
repeat-while循环自个儿试试把,就当操作下
If statements - if 语句
Swift支持标准的if,if/else和if/else-if/else 结构,更新代码:
import Foundation
let greeting = "Swift by Tutorials Rocks!"
for i in 1...5 {
if i == 5 {
print(greeting.uppercaseString)
} else {
print(greeting)
}
}
正如前面提到的,if条件判断的语句块必须是Bool,因此,不能用interger或其他类型的值来推断true/false.另外一个Swift的安全机制特点,if语句块的内容必须有花括号包围,哪怕是if里面只有一个语句也要用到花括号。
for i in 1...5 {
if i == 5
print(greeting.uppercaseString)
else
print("Inside the else:")
print(greeting) // uh oh
}
这样写会报编译错误。Swift必须添加花括号,能有效避免因为没有花括号在代码缩进时造成的代码编写错误或理解错误。
if语句开发还有另外的小技巧,你将会在下面了解到。别走开,上个厕所(。・∀・)ノ゙,广告之后,同样精彩哦~~
Switch statements - switch语句
Swift支持标准的switch-case结构,比较值会和switch中的条件值进行匹配选择。更新代码来瞧瞧:
var direction = "up"
switch direction {
case "down":
print("Going Down!")
case "up":
print("Going Up!")
default:
print("Going Nowhere")
}
direction变量匹配了“up”的case,打印出“Going Up”,拽爆了有没有,有没有!试试将direction修改为其他的case值。
和Object-C形成鲜明对比的是,Swift语句块可以用任何语句而不是仅仅最基本的数值,而且编译器能确保每个case语句类型的匹配变量是和条件类型一样,比如这个case里面你就只能用字符串不可以用int之类的。
第二点,这代码语句块中没有break。在Swift中,当变量匹配到一个case语句块后,程序会执行这个语句块中的内容,执行完毕后便退出switch。他不会像其他语言那样,如果没有break则会一直执行到最底部。这也是Swift的安全机制之一。可以很容易的避免因为丢失break而造成的意想不到的后果。
修改下代码:
var direction = "up"
switch direction {
case "down":
print("Going Down!")
case "up":
print("Going Up!”)
}
会收到错误消息提示“Switch must be exhaustive, consider adding a default clause” ,Swift希望你能提供每一个传递到Swift语句可能的匹配值,但是因为你不可能匹配每一个可能匹配的字符串值,所以Swift提供了一个默认的匹配值default。
如果你的switch的类型可以提供每一个匹配的值类型,比如枚举,那么你可以不使用default!
NOTE:你可能已经发现Swift的有些规则非常严格,我将其称为安全机制。
Swift的switch相当的灵活,更新代码:
switch direction {
case "down", "up":
printl"Going Somewhere!")
default:
print("Going Nowhere")
}
如果direction是“up”或“down”则执行第一个case语句。这示例可以看出一个case语句块中可以有多个case条件语句。
你也可以匹配range类型的值,如下所示:
var score = 570
switch score {
case 1..<10:
print("novice")
case 10..<100:
print("proficient")
case 100..<1000:
print("rock-star")
default:
print("awesome")
}
这的代码依靠半封闭范围区间来进行判断匹配。Swift的switch语句拥有非常强大的适应能力,能让你甚至于用匹配元组,你可以部分匹配也可以全部匹配,你也可以匹配条件语句。在第六章还有更多和switch匹配有关的内容。
接着该干嘛?
在这章内容中,你了解了Swift最基本的核心语法,包括变量,常量和Swift的类型等。你也看到了一些和循环控制流有关的操作内容。我相信你会发现在Swift代码中到处都有循环语句。
在下一章节,你会遇到可选类型optionals ,简化了和Object-C代码之间的互操作,Swift的数组,字典,以及让人好奇的常量数组等。