图1. 打开游乐场
效果如下图:
图2. Playground 执行效果图
注意:Swift 1.x 中的println 函数在Swift 2.x 中已重命名为print 。
变量、常量和字符串
稍微懂点编程知识的人都知道,程序需要存储数据,并对数据进行各种计算(操作)。因此,我们的程序中就会有很多变量和常量。变量相当于内存中的一块存储空间。可以存储一个值并且可以更改该值;常量也是内存中的一个存储空间,但其值在第一次赋值后就不能改变。
vargreeting:String="你好,世界!"print(问候语)
上面的代码中,greeting是一个变量,它的类型是String。既然是变量,就意味着它的值是可以修改的,如下图:
vargreeting:String="你好,世界!"print(greeting)greeting="再见世界!"print(greeting)
当然,声明变量时也可以这样写:
vargreeting="你好,世界!"
如果这样做,编译器将执行类型推断并根据您分配给变量的值确定变量的类型。在上面的代码中,变量greeting的类型被推断为字符串。相信这并不难理解。当然,由于类型推断的存在,Swift 是一种静态类型语言,这意味着编译器会在编译过程中检测到类型错误,并提示开发人员尽快纠正程序中的问题。
vargreeting="你好,世界!"问候语=123print(问候语)
对于上面的代码,编译器会在第2 行报告错误“Cannot allocate value of type "Int" to type "String""”。
建议:您可以省略类型标签并尽可能使用类型推断,以使代码更短并获得更好的可读性。
让num=123num=321
esmp;上面的代码中,我们用let替换了声明变量的关键字var,这样num就不再是一个变量而是一个常量,这意味着它的值在第一次赋值后就不能再被修改了,所以上面的代码会报错“第2 行将报告无法分配给value: "num" 是一个let 常量”。
建议:只要有可能,您应该声明常量而不是变量。这样做不仅会让你的代码更加健壮,而且编译器还可以进一步优化代码以获得更好的性能。
Swift 允许你在一行代码中声明多个变量时将type 标签放在最后,如下所示:
var x, y, z: 双精度;
另一点需要注意的是,对于命名常量和变量,Swift 可以使用几乎任何Unicode 字符,除了空格、数学符号、箭头、特殊用途或无效的Unicode 字符。如果名字中有数字,数字可以放在第一个字母中。外面的任何地方。请看下面的例子:
let =3.1416let hello="你好,世界"let="猪"
注意:从上面的例子中不难发现中文、希腊字母甚至Emoji字符都可以作为变量名。在Xcode 中,您可以选择通过编辑菜单中的“Emoji Symbols”菜单项输入Emoji 字符,也可以使用快捷键Control++Space 启用Emoji 字符输入。
图3. 表情符号
分号
很多语言都使用分号作为语句分隔符,而有些语言则要求一行上只能写一条语句(相当于使用换行符作为语句分隔符)。在这一点上,斯威夫特做出了妥协。如果一行中只写一条语句,则不需要使用分号来表示该语句的结束;如果一行中有多个语句,则各语句之间需要用分号分隔。代码如下所示:
var x=10;打印(x)
建议:在Swift 编程中,尝试每行一条语句而不使用分号。分号会占用一个字符的空间,但对你的程序没有任何帮助。
数值类型和类型转换
var 半径=4let PI=3.1416
上面的代码创建了一个数值变量和一个数值常量,其类型分别为Int 和Double。 Swift 中有多种类型的整数,例如:Int8(8 位有符号整数)、UInt16(16 位无符号整数),Float 也用于表示十进制类型。需要指出的是,除非你的程序有特殊需要,否则整数和小数类型应首选Int和Double。大多数应用程序都可以使用Int 和Double 来处理所有值,编译时会根据您的系统是32 位还是64 位来决定Int 类型的大小(占用多少字节内存)。
在Swift 中,您可以在编写数字文字时使用下划线来增强数值的可读性,如下所示:
var 百万=1_000_000
当然,给变量赋值时允许使用运算符来计算值,但运算中涉及到的各部分的类型需要保持一致,如下所示:
var 半径=4let PI=3.1416var 面积=半径* 半径* PI
上面的代码会报编译错误“二元运算符‘*’不能应用于‘Int’和‘Double’类型的操作数”。在Swift 中,所有运算符本质上都是函数。上面的错误告诉我们乘法函数不能接受Int 类型作为左值和Double 类型作为其右值。正确的代码如下:
var 面积=Double(半径) * Double(半径) * PI
你可能认为上面的操作就是所谓的类型转换,但事实并非如此。代码Double(radius) 创建一个新的Double 对象,然后使用半径值对其进行初始化。如果您不知道这意味着什么,没关系,请继续阅读。
提示:可以按住键盘上的Command键,点击Swift中的类型,查看头文件中类型、函数、协议等的定义。
当变量的值超出变量的表示范围时,Swift会报告编译错误以阻止此类操作。这是Swift 实现编程安全的方法。
布尔值
Swift 中有一个Boolean 类型,Boolean 类型只有两个值,true 或false。以下代码演示了如何创建布尔类型的常量:
让alwaysTrue=true
布尔类型常用于循环和分支结构中。在Swift中,循环和分支结构中的条件只能使用布尔值,这与C、C++和Objective-C不同。
元组
元组是由多个值组成的单一类型。与类和结构不同,您不需要定义元组类型即可使用它。
var address=(610000, "四川成都")print(address.0)print(address.1)address.0=618000address.1="四川德阳"let(邮政编码,城市)=addressprint(邮政编码)print(城市)
上面的代码使用类型推断来获取元组的数据类型。当然,你也可以编写如下代码来指定元组中数据对应的数据类型:
var address:(Int, String)=(610000, "四川成都")
此外,您可以按如下方式命名元组中的数据:
var address=(zipcode: 610000, city: "成都四川")print(address.zipcode)print(address.city)
提示:元组只适合快速构造简单的复合数据类型。更多时候,我们需要类和结构。
字符串插值
如果你用过其他语言中字符串连接的操作,你就能体会到Swift 中字符串插值是多么方便。这是在Objective-C 中连接字符串所必需的。
NSInteger a=123;NSInteger b=321;NSLog(@"%ld + %ld=%ld", a, b, a + b);
不过,在Swift中你可以使用字符串插值来优雅地完成上述操作,代码如下:
var a=123var b=321print("(a) + (b)=(a+b)")
过程控制
如果您有任何其他编程语言的经验,您一定会熟悉过程控制的概念。无论你使用什么语言,无论你的程序是简单还是复杂,程序中的代码都只有三种结构:顺序结构、分支结构和循环结构。相信顺序结构并不难理解,就是一步步执行,从第一步到最后一步;分支结构是程序中需要做出决策的地方。如果决策的结果不同,程序就会沿着不同的分支执行,比如编写一个程序控制机器人从流水线上取出生产出来的球。如果是红色,则放入A盒,如果是蓝色,则放入B盒。判断球的颜色就是决定的地方,将球放入A盒或B盒是两个不同的分支;循环结构是指程序中的某些步骤需要重复执行。例如,球会被不断地送到流水线上的机器人手上,然后机器人就会重复刚才的动作。根据球的颜色连续将球放入A盒或B盒中。
以下是对1-100 求和的最简单程序,用于演示Swift 中的循环。
for循环
var sum1: Int=0for var i=1;我=100; i++ { sum1 +=i}print(sum1)
可以在for循环中使用范围运算符,代码如下:
var sum2: Int=0for i in 1.100 { sum2 +=i}print(sum2)
var sum2: Int=0for i in 1.101 { sum2 +=i}print(sum2)
while 循环
var sum3: Int=0var i=1while i=100 { sum3 +=i++}print(sum3)
重复while循环
var sum4: Int=0var j=1repeat { sum4 +=j++} while j=100print(sum4)
提示:repeat-while 循环是在Swift 2.x 中引入的,并取代了do-while 循环,因为do 关键字在Swift 2.x 中具有新的含义和用法。
我们再看一下Swift 中的分支结构。
if-else
以下代码实现分段函数求值。
var x=1.5var y: Doubleif x -1 { y=3 * x + 5}else if x=1 { y=x}else { y=5 * x - 3}print(y)
switch-case-默认值
var dir="up"switch dir { case "down": print("往下走!") case "up": print("往上走!") default: print("哪儿也不去!")}
对于有其他语言编程经验的程序员来说,上面的代码并不困难。事实上,无论开发程序员以前做过什么,总能从Swift 中找到自己熟悉的语言的影子,所以对于非零基础的初学者来说,上手Swift 是比较容易的。不过,Swift中的循环和分支结构仍然有自己的特点。首先,省略了使用括号来设置条件(这与C、C++、C#、Java 和Objective-C 不同)。另外,条件只能是Boolean类型值(这一点与C、C++、Objective-C不同,但与Java、C#一致)。另外,switch-case-default不需要在每个case后面都写break。只有一种情况或者default才会执行,必须写default;如果执行完一个case后确实想继续执行后续的case,可以在case后面写fallthrough;并且每种情况都可以编写多个与switch 中的常量或范围匹配的表达式进行相等匹配,并且switch 接受的表达式也可以是Swift 中的元组类型。
让工资=18000switch工资{ case 0: print("流浪者") case 1.3000: print("代码动物") case 3001.6500: print("代码农民") case 6501.10000: print(" IT农民工") case 10001.20000: print("IT工程师") case 20001.50000: print("IT人才") default: print("IT精英")}
可空类型
使用过C或者C++的程序员一定都被空指针问题所困扰过。我们可以在程序中声明一个指针,并将其默认值设置为NULL。然而,在后续的程序中我们可能会忘记初始化和使用指针。这样的程序在运行时(注意是运行时)会遇到异常情况。 Swift 1 中没有运行时异常机制。很容易纠正,修改代码的成本更小)。为了避免空指针引起的运行时异常,Swift 引入了可空类型的概念(可选),我们通过以下代码进行说明。
var str="你好,世界"print(str)
上面的代码会输出Hello, world,这是大家都熟悉的,但是如果我们尝试去掉第一行代码中的赋值语句,看看编译器会说什么。
var strprint(str)
编译器将报告“模式中缺少类型注释”错误。在没有赋值语句的情况下,我们需要为变量指定类型,因为无法从分配给变量的值推断出变量的类型。让我们尝试为str 变量指定其类型,看看编译器会说什么。
var str: 字符串打印(str)
这次,编译器在打印时报告错误,指出“变量‘str’在初始化之前使用”。显然str没有初始化,编译器不会让这样的代码运行。但是我们可能需要一个变量暂时具有空值,但是当我们尝试将代码更改为以下内容时,编译器有话要说。
var str: 字符串=nilprint(str)
这次,编译器说“nil Cannotinitialized type "String"”(nil值不能初始化String类型),即字符串不能被赋值为nil。如果您确实想这样做,可以使用可空类型,如下所示:
var str: 字符串? print(str)
我们可以通过在类型标记后添加问号来将变量声明为可空类型,并且不需要为其指定nil 值。它的值已经是nil了,所以我们可以在需要的时候给这个变量赋值。完整代码如下所示:
var str: String?print(str)str="你好,世界"print(str?uppercaseString)print(str!uppercaseString)
Playground中的执行如下图所示:
图4. 可空类型
你可以使用!强制可空类型恢复为变量的原始类型。当然,如果变量的值为nil,就会出现运行时错误。如果变量值不为nil,则可以顺利恢复。您还可以通过以下方式获取可为空变量的值。
var str: 字符串?="你好,世界"if let newstr=str { print(newstr.uppercaseString)}
提示:出于某种未知的原因,Swift 2.x 引入了异常机制,并且还添加和修改了一系列关键字。最莫名其妙的是do关键字改变了它的含义。异常机制的语法类似,本文暂时不介绍Java和C#中的语法。
容器
许多语言都提供容器类型的数据结构。顾名思义,容器就是承载其他对象的对象,也就是说,它是一个对象的持有者。最常见的容器是数组、字典和集合(就像我们数学书中提到的集合一样,不允许有重复的元素)。当然,有些地方容器也被称为集合框架(这个名字很容易与数学中的集合混淆,所以这里我们称之为对象持有者容器)。在Objective-C Foundation 框架中,最常用的容器是NSArray 和NSDictionary,这两个容器在Swift 中也可用。
大批
与许多其他语言一样,Swift 中的数组也是元素的有序集合。注意,这里所说的顺序并不是指从小到大或者从大到小的顺序,而是指元素是相邻的。他们有自己的序列号(索引)。下面的代码演示了如何创建和使用数组。
var array=[12, 7, 38, 65]print(array[2]) //38array.append(93)print(array) //[12, 7, 38, 65, 93]array.appendContentsOf(100.103)print(array) //[12, 7, 38, 65, 93, 100, 101, 102, 103]array.removeAtIndex(0)print(array) //[7, 38, 65 93, 100 、101、102、103]
上面使用文字语法创建了一个数组对象。如果您愿意,还可以使用以下方法创建数组对象并指定数组元素的类型。
var myArray=Array(count: 5,repeatedValue: 0)myArray[4]=1000print(myArray) //[0,0,0,0,1000]var thyArray=[Int](count: 5,repeatedValue: 100)thyArray[2]=55print(thyArray) //[100, 100, 55, 100, 100]
字典
顾名思义,存储在称为字典的容器中的元素不是单个值,而是键和值的组合。通常称为键值对映射,如《新华字典》。上面每一个可以搜索到的单词都是一个Key,这个单词的解释就是value。
var myDict=[1: "狗", 2: "猫"]print(myDict[2]) //可选("猫")var yourDict: [Int: String]=[1: "飞机", 2: "坦克"]print(yourDict [3]) //无
上面的代码创建了两个字典,其键均为Int 类型,值均为String 类型。需要注意的是,当我们要从字典容器中取出数据时,需要使用key通过下标操作得到key对应的值,但是这个值可能不存在,就像你编的单词一样字典。找不到,所以如果有key对应的value,取出来的value是可空类型的。如果key没有对应的value,则获取nil。
您可以定义一个常量容器。这样的容器只有在定义的时候才能被初始化。以后不能添加元素、删除元素或修改元素的值。
可以通过循环遍历容器中的元素,请看下面的代码。
var myArray=[12, 7, 38, 65]for x in myArray { print(x)} var myDict=[1: "狗", 2: "猫", 31: "飞机", 32: "坦克"] for x in myDict .keys { print("(x) ---(myDict[x])")}
函数和闭包
函数是现代编程语言中最重要的构建块,允许您将执行特定任务的业务逻辑封装到独立的、可重用的工作单元中。对于调用者来说,函数就像一个独立的黑匣子,调用者在使用函数提供的功能时不需要了解其内部实现。 Swift 支持全局函数和方法,它们是与特定类型的类或对象关联的函数。 Swift 还提供了对闭包、匿名函数等的支持,这些在Swift 中被视为一等公民。
我们先看一个例子。您可以创建一个Playground 项目并键入以下代码。
导入Foundationlet a=3.0, b=4.0let c=sqrt(a * a + b * b)print(c)
你可能已经注意到,Swift 中没有内置的计算平方的函数,但我们可以自己编写一个。上面的代码可以重写如下。
导入Foundationfunc square(x:Double) -Double { return x * x}let a=3.0, b=4.0let c=sqrt(square(a) + square(b))print(c)
上面的square 函数接受一个Double 类型参数并返回一个Double 类型值。定义函数的关键字是func,后面跟函数的名称。函数的命名也遵循标识符命名的规则,同时要由名称可知其含义。这些应该是常识,不需要解释。函数具有零个或多个参数以及返回值或无返回值。 Swift 中所有称为函数的东西都是全局的。如果在类或接口中定义了一个函数,并且该函数绑定到某个类型或对象,我们通常将其称为方法。
Swift 中的函数是一等公民。 Swift 中函数的使用与JavaScript 非常相似。您可以将函数分配给变量或常量。当然,你也可以使用一个函数作为另一个函数的参数。代码如下所示:
import Foundationfunc square(x:Double) -Double { return x * x}let f=square //使用类型推断获得变量f 的类型let a=3.0, b=4.0let c=sqrt(f(a) + f( b) ) 打印(c)
你可能会觉得上面的代码其实挺熟悉的,这很好,因为这种写法就相当于C语言中的函数指针。还有一点需要说明的是,上述代码中常量f的类型是通过类型推断得到的。如果想完整给出常量f的类型,代码可以写如下。
var f:(Double) -Double=方形
当然,你也可以像函数指针一样声明一个新的变量类型,然后将一个函数赋给这个新类型的变量。 Swift 中声明类型别名的关键字是typealias。代码如下:
typealias FType=(Double) -Doublevar f:FType=方形
提示:如果你想在Xcode中查看某个函数的定义,可以按住Command键并单击该函数,让代码跳转到函数定义处。
Swift 支持函数重载。所谓重载,就是同名的函数有不同的参数列表,因此可以和平共处。重载也是实现编译时多态性的重要手段。看看下面的代码。
func assertEquals(value: Int, Expected: Int, message: String) { 如果预期!=value { print(message) }} func assertEquals(value: String, Expected: String, message: String) { 如果预期!=value { print(message) }}断言Equals(100,expected: 1000,message:"两个整数不相等!")assertEquals("Hello",expected:"好",message:"两个字符串不相等!")
当然,如果重载函数与上面相同但参数不同但执行相同的代码,那么将来对这些代码的维护将是一场噩梦。编程大师Martin Fowler 在《重构改进现有代码的设计》(《重构:改进现有代码的设计》)中写道。
计》)一书中指出:“代码有很多种坏味道,重复是最坏的一种”。要消除重复代码,可以使用泛型来改写上面的函数,如下所示。 func assertEquals(value: T, expected: T, message: String) { if expected != value { print(message) }}assertEquals(100, expected: 1000, message: "Two integers are not equal!")assertEquals("Hello", expected: "Good", message: "Two strings are not equal!") 上面的代码中T是泛型参数,相当于定义了一种虚拟的类型,冒号后面的Equatable是对泛型的限定(如果不理解可以先不管他,后面会讲到这个知识),这种类型在编译时会根据传入该函数的实际参数的类型来决定,就如上面的代码中,第一次调用assertEquals函数时,T被替换成Int类型;而第二次调用时,T被替换成String类型。 提示:在Swift 2.x中,函数调用传参时从第二个参数开始必须要给出外部参数名,这一点从某种程度上来讲照顾了Objective-C程序员的代码书写习惯。 Swift中,函数的参数可以是inout参数,所谓inout参数是指可以在函数中被修改并影响到调用者的参数(这一点应该是借鉴了C#的语法,C#中方法的参数可以设置为ref参数或out参数)。先看看下面的代码。 func swap(inout x: Int, inout y: Int) { let temp: Int = x; x = y; y = temp;}func bubbleSort(inout x: [Int]) { var swapped:Bool = true for var i = 1; swapped && i<= x.count - 1; i++ { swapped = false for var j = 0; j< x.count - i; j++ { if x[j] >x[j + 1] { swap(&x[j], &x[j + 1]) swapped = true } } }}var x = [34, 12, 85, 7, 96, 63, 40]bubbleSort(&x)print(x) 上面的代码中有两个函数,swap函数实现交换两个参数的值,下面的bubbleSort函数实现冒泡排序,如果没有inout参数,你只能修改函数参数在函数内部的拷贝而不会影响到调用者,但是有了inout参数一切就不同了,调用者在传入参数的时候用在参数前加上&,有C或者C++开发经验的都知道,这种语法叫做传指针(引用)而不是简单的传值。当然,对于inout参数的使用还是应当小心,因为不是所有的时候我们都希望通过函数调用来修改传入的参数并影响调用者,一定要警惕这种形式的参数所带来的副作用。 Swift1.x中,如果要为函数的参数指定内部参数名和外部参数名,可以按照下面的方式来做。 // greeting是第一个参数的外部参数名// g是第一个参数的内部参数名func say(greeting g: String, name n: String, counter c: Int) { for var i = 0; i< c; ++i { print("(g), (n)") }}say(greeting: "Hello", name: "Jack", counter: 3) 提示:Swift 1.x中可以通过在内部参数名前加#让内部参数和外部参数同名,Swift 2.x默认内部参数名就是外部参数名。如果不愿意在调用函数时书写外部参数名,可以在函数的参数列表的内部参数名前添加一个下划线,第一个参数除外。 如果需要使用外部参数名,比较好的做法是让参数的外部名字和内部名字保持一致,在Swift 2.x中可以使用下面的写法。 func say(greeting greeting: String, name: String, counter: Int) { for var i = 0; i< counter; ++i { print("(greeting), (name)") }}say(greeting: "Hello", name: "Jack", counter: 3) 说明:这种语法对于使用Objective-C的开发人员来说一点都不陌生,但是对于使用Java或C#这样编程语言的程序员来说可能会觉得比较别扭。 在类或结构中定义的函数我们通常称之为方法,这一点稍后予以讲解。 Swift既然支持函数式编程,那么不可避免的要支持闭包(closure)。关于闭包一词,你可能会听到各种各样的解释,下面给出其中的一种:“闭包是可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。闭包一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)”。闭包最常见的一种用法就是用闭包表达式实现匿名函数的功能,我们用下面的代码来加以说明。 var animals = ["fish", "cat", "panda", "dog"]func compare(one: String, two: String) ->Bool { return one< two;}animals.sortInPlace(compare)print(animals) 上面的代码实现了对字符串数组的排序,我们将一个包含排序规则的函数作为数组sort方法的参数传入,sort方法就能够完成对数组的排序。 事实上,完全不需要单独定义这样的一个函数,因为这个函数想表达的就是一个临时的排序规则,我们可以用下面的方式将代码修改得更加简短。 var animals = ["fish", "cat", "panda", "dog"]animals.sortInPlace({ (one: String, two: String) ->Bool in return one< two })print(animals) 当然,这样看起来仍然不够简单明了。如果你还记得Swift有强大的类型推断能力,那么上面的代码就可以做出如下的改进。 var animals = ["fish", "cat", "panda", "dog"]animals.sortInPlace({ (one, two) ->Bool in return one< two })print(animals) 你甚至还可以省略掉那个return,代码仍然可以很好的工作。还有什么可以省略吗?答案是肯定的,既然有类型推断,这次我们连返回类型也一并省去。 var animals = ["fish", "cat", "panda", "dog"]animals.sortInPlace({ (one, two) in one< two })print(animals) 写到这我们已经觉得做到极致了,似乎已经没有什么办法可以让代码写得更简单了。不,Swift还能做到更加的简单明了。 var animals = ["fish", "cat", "panda", "dog"]animals.sortInPlace({ $0< $1 })print(animals) OMG!参数名都可以省略掉,$0很明显表示匿名函数的第一个参数,而$1则是匿名函数的第二个参数。对于那种只有一行代码的闭包,这样写不是更简单明了吗。我们不能不感叹,Swift算是把简单即美践行到极致了,但是,事情还没完,继续看。 var animals = ["fish", "cat", "panda", "dog"]animals.sortInPlace(<)print(animals) 最后还有一个很特殊的东西叫尾随闭包(tailing closure)需要提一下,请看下面的代码。 var animals = ["fish", "cat", "panda", "dog"]animals.sortInPlace() { $0< $1 }print(animals) 闭包另一个重要的用途是将其所在的代码块中的常量或变量的生命周期延长,在离开代码块以后,我们还能够访问到这些常量或变量的值。我们可以在一个函数中返回一个闭包表达式,这相当于返回了一个匿名函数,在这个匿名函数中我们还能使用刚才返回这个闭包的函数(有的地方称之为封闭函数)中的局部变量或常量,专业的说法称之为“捕获值”(capturing value)。 typealias StateMachineType = () ->Intfunc makeStateMachine(maxState: Int) ->StateMachineType { var currentState: Int = 0 return { currentState++; if currentState >maxState { currentState = 1 } return currentState }}let myStateMachine = makeStateMachine(3)for i in 1...10 { print(myStateMachine())} 上面的代码中,makeStateMachine函数返回了一个匿名函数(闭包表达式),由于这个闭包的存在,本来是函数局部变量的currentState和参数maxState可以在函数的生命周期结束以后仍然被继续使用。闭包让你免除了对全局变量的使用(因为全局变量总是让你的代码变得糟糕,因为你不知道这个变量什么时候会被哪段代码意外的修改),但是它通过延长局部变量生命周期的方式让你以可控制的方式使用这些值。 最后,我们对闭包做一个小小的总结: 全局函数都是闭包,有名字但是不能捕获任何值。 嵌套函数都是闭包,有名字也能捕获封闭函数内的值。 闭包表达式都是匿名函数,可以根据上下文环境捕获值。 Swift中的闭包较之其他语言更简单更优秀,主要表现在: 可以根据上下文推断参数和返回值的类型。 对于单行闭包表达式,可以省略return,隐式返回。 可以省略掉参数名而使用$0, $1, ... 替换之。 提供了尾随闭包的语法,让代码更自然。 类和结构体 前面我们曾经提到过Swift支持面向对象的编程范式。所谓面向对象的编程理念,就是用对象来组织数据以及操作数据的方法,这也就意味着数据和操作数据的方法在逻辑上是一个整体,从而保证了操作的有效性和数据的完整性。面向对象编程和函数编程是目前最流行的两种编程范式,Swift对两种编程范式都提供了很好的支持。我们先看看面向对象编程。 定义和使用类 在Swift中,除了可以使用Swift已有的数据类型还可以自定义数据类型,类和结构体都是实现自定义数据类型的手段。例如,Swift没有一种类型来描述现实世界中的人,但是我们可以通过类或结构体来定义一种新的类型叫人,而且在人这种新类型中可以包含拥有的属性和方法。我们先看看如何定义和使用类。 类是对象的蓝图和模板。当我们把世界上一类对象共同的特征抽取出来的时候,我们就可以定义一个类。例如,我们把人的共同特征抽象出来的时候,我们就可以定义人类。创建类的过程就是一个抽象的过程,我们需要做两种抽象:数据抽象和行为抽象。所谓数据抽象就是找到对象的静态属性;所谓行为抽象就是找到对象动态属性,也就是对象能够执行的动作。例如:人的静态属性有名字和年龄等,人的动态属性有吃饭、睡觉、行走等。这些静态属性会成为类中的字段,而动态属性会成为类中的方法。 下面的代码定义了人类。 /人类/class Person { var name: String // 姓名 var age: Int // 年龄 /初始化方法/ init(name: String, age: Int) { self.name = name; self.age = age; } /吃饭/ func eat() { print("(name)正在吃饭") } /看片/ func watchJapaneseAV() { if age< 18 { print("(name)不能观看岛国爱情动做片") } else { print("(name)正在观看岛国爱情动作片") } }} 上面的代码用class关键字定义了名为Person的类,其中有name和age两个属性,类型是字符串和整型,分别代表人的姓名和年龄;接下来的代码中用func关键字定义了两个方法,分别代表了人吃饭和看片的行为。除此之外,在Person类中还有一个名为init的代码块,它是类的初始化方法(在C++、Java和C#中通常将其称为构造器或构造方法),承担了创建并初始化对象的职责,当程序中需要创建Person类的对象时,就可以调用该初始化器,初始化器有两个参数,前者是姓名,后者是年龄,在初始化器中我们把这两个参数分别赋值给了类的name和age字段。由于字段名和初始化器的参数名完全一致,为了加以区分,我们使用self关键表示类的字段,而没有self.前缀的则是参数。接下来可以创建人类的对象,也就是一个具体的人,然后让他执行吃饭和行走的行为,代码如下所示: var p = Person(name: "王大锤", age: 20)p.eat()p.watchJapaneseAV() 有C++或Java编程经验的程序员都知道,类中的构造器可以重载,这样的话可以根据需要选择某个构造器来创建对象。Swift中使用初始化器来创建并初始化对象,而且提供了一种叫做便利初始化器(convenience initializer)的语法,便利初始化器会调用初始化器(通常称之为非便利初始化器或指派初始化器[designated initializer])器来创建对象,代码如下所示。 /人类/class Person { var name: String // 姓名 var age: Int // 年龄 /便利初始化器/ convenience init(name: String) { self.init(name: name, age: 20) } /初始化器/ init(name: String, age: Int) { self.name = name self.age = age } /吃饭/ func eat() { print("(name)正在吃饭") } /*看片/ func watchJapaneseAV() { if age< 18 { print("(name)不能观看岛国爱情动做片") } else { print("(name)正在观看岛国爱情动作片") } }}var p1 = Person(name: "骆昊", age: 35)print("姓名: (p1.name)n年龄: (p1.age)")p1.eat()var p2 = Person(name: "王大锤")print("姓名: (p2.name)n年龄: (p2.age)")p2.watchJapaneseAV() 继承 继承是面向对象编程中一个重要的概念,它是从已有的类创建新类的过程。提供继承信息的类被称为父类,而得到继承信息的类被称为子类。下面我们试着用继承从刚才的人类(Person)派生出老师(Teacher)类和学生类(Student)。 import Foundation/老师/class Teacher: Person { var title: String // 职称 /初始化器/ init(name: String, age:Int, title: String) { self.title = title super.init(name: name, age: age) // 调用父类初始化器 } /教学/ func teach(courseName: String) { print("(name)(title)正在教(courseName)") }}/学生/class Student: Person { var grade: String // 年级 /初始化器/ init(name: String, age:Int, grade: String) { self.grade = grade super.init(name: name, age: age)// 调用父类初始化器 } /学习/ func study(courseName: String) ->Int { print("(name)选修了(courseName)") return Int(arc4random() % 101) }}var t = Teacher(name: "骆昊", age: 35, title: "砖家")var s = Student(name: "王大锤", age: 20, grade: "大二")t.eat()t.teach("iOS开发")s.watchJapaneseAV()var score:Int = s.study("Swift")print("(s.name)得到了(score)分") 在上面的例子中,Teacher类和Student类从Person类得到了继承信息,因此它们是子类,Person类是父类。子类通常也称为派生类,而父类通常称为超类或基类。子类和父类之间的关系是“IS-A”关系,我们可以说老师是人,学生是人。通过继承Teacher类和Student类复用了Person类中的代码,即学生和老师作为人的那些属性和行为,因此继承是复用代码的手段之一。在Swift中,继承的语法是定义子类时在子类后面跟一个冒号再跟父类的名字。在上面的例子中,Teacher类和Student类继承了Person的name和age静态属性,还继承到了eat和watchJapaneseAV方法,因此子类不用再重新定义这些属性和方法。当然,子类在继承父类的过程中还可以定义自己特有的属性和方法,比如Teacher中的title属性和Student中的grade属性,此外Teacher类中还有teach方法,学生类中有study方法这些都是子类对父类的扩展。显然,子类拥有比父类更多的能力,也就是说,继承一定是让子类扩展父类的能力而绝不会缩小父类的能力。就好比让猫继承狗之后,猫没有狗看门的行为,子类就要去掉父类的这项功能,这种继承明显是错误的;同理,如果让狗继承猫,狗没有猫爬树的行为,这种继承显然也是错误的。还有一点,子类的初始化器中必须调用父类的初始化器,而且必须调用父类的非便利初始化器。 在Swift中,还可以通过协议(protocol)来扩展类的能力,所谓协议就是定义了遵循该协议的类要遵守的规范。我们可以为刚才的程序添加下面的代码来看看如何使用协议来扩展类。 // 协议protocol MoreInfo { var info: String { get }}// 类扩展extension Person: MoreInfo { var info: String { get { return String("(name): (age)") } }}var t = Teacher(name: "骆昊", age: 35, title: "砖家")var s = Student(name: "王大锤", age: 20, grade: "大二")print(t.info) // 显示老师信息print(s.info) // 显示学生信息 当我们对Person类做出扩展以后,它的子类Student和Teacher也继承到了它扩展的内容。 多态 多态简单的说是同样的同样的方法执行了不同的行为,就好比猫和狗都有发出叫声的行为,但是发出的声音是完全不一样的。多态有两种实现方式:方法的重载(overload)和方法的重写(override)。方法的重载是在一个类中同名的方法有不同的参数列表,而方法重写是在继承过程,子类对父类已有的方法重新做出实现,不同的子类给出不同的实现版本。 函数的重载关于深入iOS开发:实战笔记解析,的介绍到此结束,希望对大家有所帮助。
【深入iOS开发:实战笔记解析】相关文章:
用户评论
想学 iOS 开发,这类笔记真是敲好用的!
有15位网友表示赞同!
收藏了,以后慢慢学习。
有11位网友表示赞同!
开发者们分享经验真好,省了我不少时间
有10位网友表示赞同!
这个笔记涵盖的内容都比较基础吧,刚入门的人看应该可以快速上手。
有20位网友表示赞同!
期待后续作者能更新更多进阶知识
有5位网友表示赞同!
iOS 开发入门资源一直都是挺少见的,这篇文章很宝贵!
有14位网友表示赞同!
学习笔记里代码写得清晰易懂,比官方文档方便多了。
有19位网友表示赞同!
点赞,这样的笔记太少了!
有9位网友表示赞同!
正好我最近想了解 iOS 开发,这个笔记真是及时雨啊!
有7位网友表示赞同!
作为一名iOS开发者,这种贴近实战的笔记很有帮助
有20位网友表示赞同!
学习iOS开发确实需要很多积累,希望这份笔记能给我一些启发。
有19位网友表示赞同!
感觉这份笔记整理得不错,结构清晰易懂。
有15位网友表示赞同!
ios 开发入门不容易,这篇笔记可以帮到你!
有16位网友表示赞同!
分享这样的优秀资源真是太棒了,做技术的学习需要互相帮助
有11位网友表示赞同!
iOS 开发一直想了解一下,现在看这些笔记感觉可做性很强
有10位网友表示赞同!
希望能学到更多iOS开发实用的技巧!
有5位网友表示赞同!
分享这份笔记的人太牛了吧!整理得真仔细。
有12位网友表示赞同!
学习 iOS 开发的路上,有笔记这种宝藏资源真是太好了!
有16位网友表示赞同!