从0开始学Swift笔记整理(二)

这是跟在上一篇博文后续内容:

——函数中参数的传递引用

类是引用类型,其他的数据类型如整型、浮点型、布尔型、字符、字符串、元组、集合、枚举和结构体全部是值类型。

有的时候就是要将一个值类型参数以引用方式传递,这也是可以实现的,Swift提供的inout关键字就可以实现。看下面的一个示例:

func increment(inout value:Double, amount:Double = 1.0) {

value += amount

}

var value : Double = 10.0

increment(&value)

print(value)

increment(&value, amount:100.0)

print(value)

代码increment(&value)是调用函数increment,增长量是默认值,其中&value(在变量前面加&符号,取出value地址)是传递引用方式,它在定义函数时,参数标识与inout是相互对应的。

代码increment(&value, amount:100.0)也是调用函数increment,增长量是100.0。

上述代码输出结果如下:

11.0

111.0

——函数返回值

返回值3种形式:无返回值、单一返回值和多返回值。

无返回值函数

所谓无返回结果,事实上是Void类型,即表示没有数据的类型。

无返回值函数的语法格式有如下3种形式:

func 函数名(参数列表)  {

语句组

}

func 函数名(参数列表) ->() {

语句组

}

func 函数名(参数列表) ->Void {

语句组

}

无返回值函数不需要“return返回值”语句。

多返回值函数

两种方式来实现。

一种是在函数定义的时候,将函数的多个参数声明为引用类型传递,这样当函数调用结束时,这些参数的值就变化了。

另一种是将返回定义为元组类型。

介绍元组类型返回多值的实现。看一个示例:

func position(dt: Double, speed:(x:Int, y:Int)) -> (x:Int, y:Int) {

let posx:Int = speed.x * Int(dt)

let posy:Int = speed.y * Int(dt)

return (posx, posy)

}

let move = position(60.0, speed: (10, -5))

print("物体位移:\(move.x) , \(move.y)")

参数speed:(x:Int, y:Int)是元组类型。

position函数的返回值是(x:Int, y:Int)的元组类型。

代码调用函数,传递的时期间是60.0秒,速度是(10, -5)。

输出结果,结果如下:

物体位移:600 , -300

——闭包

闭包是自包含的匿名函数代码块,可以作为表达式、函数参数和函数返回值,闭包表达式的运算结果是一种函数类型。
Swift中的闭包类似于Objective-C 中的代码块、Java中的匿名内部类。
Swift中的闭包表达式很灵活,其标准语法格式如下:
{ (参数列表) ->返回值类型 in
语句组
}
其中,参数列表与函数中的参数列表形式一样,返回值类型类似于函数中的返回值类型,但不同的是后面有in关键字。

Swift提供了多种闭包简化写法,我来介绍下面几种不同形式:

1、类型推断简化
类型推断是Swift的强项,Swift可以根据上下文环境推断出参数类型和返回值类型。以下代码是标准形式的闭包:

{(a:Int, b:Int) -> Int in
return a + b
}

Swift能推断出参数a和b是Int类型,返回值也是Int类型。简化如下:

{(a, b) in return a + b }    
{a, b
in return a + b }    //参数列表括号也可以省略

2、隐藏return关键字
在闭包内部语句组只有一条语句,如return a + b等,那么这种语句都是返回语句。前面的关键字return可以省略,省略形式如下:
{a, b in a + b }

func calculate(opr :String)-> (Int,Int)-> Int {
var result : (Int,Int)-> Int
     switch (opr) {
          
case "+" :
           result = {a, b
in a + b } //return关键字省略了
          
default:
           result = {a, b
in a - b } //return关键字省略了
    }
    return result
}

省略的前提是闭包中只有一条return语句。

3、省略参数名称
Swift提供了参数名省略功能,我们可以用$0、$1、$2…来指定闭包中参数,$0指代第一个参数,$1指代第二个参数,$2指代第三个参数,以此类推$n+1指代第n个参数。
使用参数名省略功能,在闭包中必须省略参数列表定义,Swift能够推断出这些缩写参数的类型。参数列表省略了,in关键字也需要省略。参数名省略之后如下所示:{$0 + $1}

func calculate(opr :String)-> (Int,Int)-> Int {
var result : (Int,Int)-> Int
  
switch (opr) {
      case "+" :
      result = {$
0 + $1}    //采用了参数名省略
      
default:
      result = {$
0 - $1}    //采用了参数名省略
   }
return result
}
let f1:(Int,Int)-> Int = calculate("+")
print("10 + 5 = \(f1(10,5))")
let f2:(Int,Int)-> Int = calculate("-")
print("10 - 5 = \(f2(10,5))")

4、使用闭包返回值
闭包表达本质上是函数类型,是有返回值的,我们可以直接在表达式中使用闭包的返回值。重新修改add和sub闭包,示例代码如下:

let c1:Int = {(a:Int, b:Int) -> Int in
return a + b
}(
10,5)  
print("10 + 5 = \(c1)")

解释:给c1赋值,后面是一个闭包表达式。但是闭包表达式不能直接赋值给c1,因为c1是Int类型,需要闭包的返回值。这就需要在闭包结尾的大括号后面接一对小括号(10,5),通过小括号(10,5)为闭包传递参数。

——Swift尾随闭包

闭包表达式可以作为函数的参数传递,如果闭包表达式很长,就会影响程序的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。

下面我们来看一个示例代码:

func calculate(opr: String, funN:(Int, Int) -> Int) {

//最后一个参数funN是(Int,Int)-> Int函数类型,funN可以接收闭包表达式

switch (opr) {

case "+" :

print("10 + 5 = \(funN(10,5))")

default:

print("10 - 5 = \(funN(10,5))")

}

}

calculate("+", funN: {(a: Int, b: Int) -> Int in return a + b }) //调用
calculate("+"){(a: Int, b: Int) -> Int in return a + b } //调用,这种形式就是尾随闭包
calculate("+") { $0 + $1 } //调用,这种形式就是尾随闭包

需要注意的是,闭包必须是参数列表的最后一个参数,如果calculate函数采用如下形式定义:

func calculate(funN:(Int, Int) -> Int, opr:String) {

...

}

由于闭包表达式不是最后一个参数,那么调用calculate函数就不能使用尾随闭包写法的。

——枚举(enum)

Swift中的枚举可以定义一组常量、提高程序的可读性;还具有面向对象特性。

使用enum关键词声明枚举类型,具体定义放在一对大括号内,枚举的语法格式如下:

enum 枚举名

{

枚举的定义

}

“枚举名”是该枚举类型的名称。它首先应该是有效的标识符,其次应该遵守面向对象的命名规范,它由一组成员值和一组相关值组成。

成员值

枚举的成员值默认情况下不是整数类型,以下代码是声明枚举示例:

enum WeekDays {

case Monday

case Tuesday

case Wednesday

case Thursday

case Friday

}

在这些成员值前面要加上case关键字,也可以将多个成员值放在同一行,用逗号隔开,如下所示:

enum WeekDays {

case Monday, Tuesday, Wednesday, Thursday, Friday

}

下面我们看一个示例,代码如下:

var day = WeekDays.Friday

day = WeekDays.Wednesday

day = .Monday

使用枚举成员赋值时候,我们可以采用完整的“枚举类型名.成员值”的形式,也可以省略枚举类型采用“.成员值”的形式。这种省略形式能够访问的前提是,Swift编译器能够根据上下文环境推断类型。因为我们已经在第1行和第2行给day变量赋值,所以即使第3行代码采用缩写,Swift编译器能够推断出数据类型是WeekDays。

原始值

出于业务上的需要,要为每个成员提供某种基本数据类型,我们可以为枚举类型提供原始值(raw values)声明,这些原始值类型可以是:字符、字符串、整数和浮点数等。

原始值枚举的语法格式如下:

enum 枚举名: 数据类型

{

case 成员名 = 默认值

...

}

在“枚举名”后面跟“:”和“数据类型”就可以声明原始值枚举的类型,然后在定义case成员的时候需要提供原始值。

以下代码是声明枚举示例:

enum WeekDays: Int {

case Monday = 0

case Tuesday = 1

case Wednesday = 2

case Thursday = 3

case Friday = 4

}

我们声明的WeekDays枚举类型的原始值类型是Int,需要给每个成员赋值,只要是Int类型都可以,但是每个分支不能重复。

相关值

在Swift中除了可以定义一组成员值,还可以定义一组相关值(associated values),它有点类似于C中的联合类型。下面看一个枚举类型的声明:

enum Figure {

case Rectangle(Int, Int)

case Circle(Int)

}

枚举类型Figure(图形)有两个相关值: Rectangle(矩形)和Circle(圆形)。Rectangle和Circle是与Figure有关联的相关值,它们都是元组类型,对于一个特定的Figure实例,只能是其中一个相关值。从这一点来看,枚举类型的相关值类似于C中的联合类型。

——类和结构体定义

Swift中的类和结构体定义的语法是非常相似的。类使用class关键词定义类,使用struct关键词定义结构体,它们的语法格式如下:

class 类名 {

定义类的成员

}

struct 结构体名 {

定义结构体的成员

}

从语法格式上看,Swift中的类和结构体的定义更类似于Java语法,不需要像C++和Objective-C那样把接口部分和实现部分放到不同的文件中。

下面来看一个示例:

class Employee { //定义的类

var no: Int = 0

var name: String = ""

var job: String?

var salary: Double = 0

var dept: Department?

}

struct Department { //定义的结构体

var no: Int = 0

var name: String = ""

}

里面定义了一些属性。

Employee和Department是有关联关系的。

下列语句实例化:

let emp = Employee()

var dept = Department()

Employee()和Department()是调用它们的构造函数实现实例化。

注意:类声明为let常量还是var变量呢?从编程过程讲类一般声明为let常量,由于类是引用数据类型,声明为let常量只是说明不能修改引用,但是引用指向的对象可以被修改。

——可选链

可选链与类图之间是典型的关联关系类图。这些类一般都是实体类,实体类是系统中的人、事、物。Employee通过dept属性与Department关联,Department通过comp属性与Company关联。

下面看示例代码:

class Employee {

var no:Int = 0

var name:String = "Tony"

var job:String?

var salary:Double = 0

var dept:Department = Department()

}

class Department {

var no:Int = 10

var name:String = "SALES"

var comp: Company = Company()

}

class Company {

var no:Int = 1000

var name:String = "EOrient"

}

letemp = Employee()  //Employee实例

print(emp.dept.comp.name)   //emp.dept.comp.name可以引用到Company实例,形成一个引用的链条,但是这个“链条”任何一个环节“断裂”都无法引用到最后的目标(Company例)。

给定一个Employee实例,一定会有一个Department与其关联。但现实是一个新入职员工未必有部门,这种关联关系有可能有值,也有可能没有值,我们需要使用可选类型(Department?)声明dept属性。

修改代码如下:

class Employee {

var no:Int = 0

var name:String = "Tony"

var job:String?

var salary:Double = 0

var dept:Department?//= Department()

}

class Department {

var no:Int = 10

var name:String = "SALES"

var comp:Company?//=

Company()

}

class Company {

var no:Int = 1000

var name:String = "EOrient"

}

let emp = Employee()

print(emp.dept!.comp!.name)//显示拆包

print(emp.dept?.comp?.name)//可选链

其中可选类型的引用,可以使用感叹号(!)进行显示拆包,代码修改如下:

print(emp.dept!.comp!.name)

但是显示拆包有一个弊端,如果可选链中某个环节为nil,将会导致代码运行时错误。我们可以采用更加“温柔”的引用方式,使用问号(?)来代替原来感叹号(!)的位置,如下所示:

print(emp.dept?.comp?.name)

——可选类型

可选类型:

var n1: Int = 10
n1 = nil //编译错误
let str: String = nil //编译错误

Int和String类型不能接受nil的,但程序运行过程中有时被复制给nil是在所难免的,Swift为每一种数据类型提供一种可选类型(optional),即在某个数据类型后面加上问号(?)或感叹号(!),修改前文示例代码:

var n1: Int? = 10
n1 = nil
let str: String! = nil

Int?和String!都是原有类型Int和String可选类型,它们可以接受nil。

可选类型值拆包

在可选类型的问号(?)或感叹号(!)究竟有什么区别呢?这与可选类型的“拆包”(unwrapping)有关,拆包是将可选类型变成普通类型,如果我们直接打印非空的可选类型值,代码如下:

var n1: Int? = 10
print(n1)

输出的结果是Optional(10),而非10。所以试图计算表达式n1 + 100会发生编译错误,代码如下:

var n1: Int? = 10
print(n1 + 100) //发生编译错误

需要对可选类型值进行“拆包”是必要地。

“拆包”分为显示拆包和隐性拆包。

使用问号(?)声明的可选类型,在拆包时需要使用感叹号(!),这种拆包方式称为“显式拆包”;

使用感叹号(!)声明的可选类型,在拆包时可以不使用感叹号(!),这种表示方式称为“隐式拆包”。

看看下面代码:

var n1: Int? = 10
print(n1! + 100) //显式拆包
var n2: Int! = 100
print(n2 + 200) //隐式拆包

总结使用问号(?)和感叹号(!)

在使用可选类型和可选链时,多次使用了问号(?)和感叹号(!),但是它们的含义是不同的,下面我来详细说明一下。

1. 可选类型中的问号(?)

声明这个类型是可选类型,访问这种类型的变量或常量时要使用感叹号(!),下列代码是显示拆包:

let result1: Double? = divide(100, 200)
print(result1!)

2. 可选类型中的感叹号(!)

声明这个类型也是可选类型,但是访问这种类型的变量或常量时可以不使用感叹号(!),下列代码是隐式拆包:

let result3: Double! = divide(100, 200)
print(result3)

3. 可选链中的感叹号(!)

多个实例具有关联关系,当从一个实例引用另外实例的方法、属性和下标等成员时就会形成引用链,由于这个“链条”某些环节可能有值,也可能没有值,因此需要采用如下方式访问:

emp.dept!.comp!.name

4. 可选链中的问号(?)

在可选链中使用感叹号(!)访问时,一旦“链条”某些环节没有值,程序就会发生异常,我们把感叹号(!)改为问号(?),代码如下所示:

emp.dept?.comp?.name

这样某些环节没有值的时候返回nil,程序不会发生异常。

——访问级别

Swift提供了3种不同访问级别,对应的访问修饰符为:public、internal和private。这些访问修饰符可以修饰类、结构体、枚举等面向对象的类型,还可以修饰变量、常量、下标、元组、函数、属性等内容。

· public。可以访问自己模块中的任何public实体。如果使用import语句引入其他模块,我们可以访问其他模块中的public实体。

· internal。只能访问自己模块的任何internal实体,不能访问其他模块中的internal实体。internal可以省略,换句话说,默认访问限定是internal。

· private。只能在当前源文件中使用的实体,称为私有实体。使用private修饰,可以用作隐藏某些功能的实现细节。

使用访问修饰符的示例代码如下:

public class PublicClass {}
internal class InternalClass {}
private class PrivateClass {}
public var intPublicVariable = 0
let intInternalConstant = 0
private func intPrivateFunction() {}

使用最佳访问级别:

由于中Swift中访问限定符能够修饰的实体很多,使用起来比较繁琐,下面我们给出一些最佳实践。

1. 统一性原则

原则1:如果一个类型(类、结构体、枚举)定义为internal或private,那么类型声明的变量或常量不能使用public访问级别。因为public的变量或常量可以被任何人访问,而internal或private的类型不可以。

原则2:函数的访问级别不能高于它的参数和返回类型(类、结构体、枚举)的访问级别。假设函数声明为public级别,而参数或者返回类型声明为internal或private,就会出现函数可以被任何人访问,而它的参数和返回类型不可以访问的矛盾情况。

2. 设计原则

如果我们编写的是应用程序,应用程序包中的所有Swift文件和其中定义的实体,都是供本应用使用的,而不是提供其他模块使用,那么我们就不用设置访问级别了,即使用默认的访问级别。

如果我们开发的是框架,框架编译的文件不能独立运行,因此它天生就是给别人使用的,这种情况下我们要详细设计其中的Swift文件和实体的访问级别,让别人使用的可以设定为public,不想让别人看到的可以设定为internal或private。

3. 元组类型的访问级别

元组类型的访问级别遵循元组中字段最低级的访问级别,例如下面的代码:

private class Employee {

var no: Int = 0

var name: String = ""

var job: String?

var salary: Double = 0

var dept: Department?

}

struct Department {

var no: Int = 0

var name: String = ""

}

private let emp = Employee()
var dept = Department()
private var student1 = (dept, emp)

4. 枚举类型的访问级别

枚举中成员的访问级别继承自该枚举,因此我们不能为枚举中的成员指定访问级别。示例代码如下:

public enum WeekDays {

case Monday

case Tuesday

case Wednesday

case Thursday

case Friday

}

由于WeekDays枚举类型是public访问级别,因而它的成员也是public级别。

——选择类结构体的关系

类和结构体非常相似,很多情况下没有区别。类和结构体异同:
类和结构体都有如下功能
· 定义存储属性
· 定义方法
· 定义下标
· 定义构造函数
· 定义扩展
· 实现协议
只有类才有的功能
· 能够继承另外一个类
· 能够核对运行期对象的类型
· 析构对象释放资源
· 引用计数允许一个实例有多个引用
选择的原则:
结构体是值类型,每一个实例没有独一无二的标识,下面两个数组实例本质上没有区别,他们可以互相替换。
var studentList1: [String] =  ["张三","李四","王五"]
var studentList2: [String] =  ["张三","李四","王五"]
但是我们提到类时候,它是引用类型,每个实例都有独一无二的标识。我们看看下面员工Employee类代码:
class Employee { 
    var no = 0  
    var name = "" 
    var job = ""  
    var salary = 0.0
}
var emp1 = Employee()
emp1.no = 100
emp1.name = "Tom"
emp1.job = "SALES"
emp1.salary = 9000
var emp2 = Employee()
emp2.no = 100
emp2.name = "Tom"
emp2.job = "SALES"
emp2.salary = 9000
emp1和emp2两个员工实例即便内容完全相同,但是这不能说明他们就是同一个员工,只是相似而已。每一个员工实例的背后都有独一无二的标识。
我们再来看看部门Department结构体。
struct Department {
    var no: Int = 0
    var name: String = ""
}
var dept1 = Department()
dept1.no = 20
dept1.name = "Research"
var dept2 = Department()
dept2.no = 20
dept2.name = "Research"
Department为什么被设计成为结构体而不是类呢,那要看我们对于两个不同部门的理解是什么,如果具有相同的部门编号(no)和部门名称(name),我们就认为他们是两个相同的部门,那么就可以把Department定义为结构体,这一点与员工Employee不同。

这是我在学Swift整理的基础笔记,希望给更多刚学IOS开发者带来帮助,在这里博主非常感谢大家的支持!

更多的请到参考我下一篇博文。之后还在持续更新中。。。

上一篇:假如想要建设一个能承受500万PV/每天的网站,服务器每秒要处理多少个请求才能应对?


下一篇:从零开始学Electron笔记(五)