Swift基础语法

前言

  最近开始看下《Swift4从零到精通iOS开发》,学习下Swift的语法.也相当于做一个简单的读书笔记.

  虽然在毕业前也做过一丢丢的Swift语法学习,但是那个时候还没有接触到任何的iOS开发,当时学起来也是晦涩难懂,所以之前的学习就当作空白归零,现在重新开始新的Swift语法学习.

量值与基本数据类型

特殊的基本数据类型

  • 元组
       元组是Swift中特有的数据类型,允许一些不相关的数据类型进行自由组合成为新的集合类型.如下定义
    1
    2
    3
    4
    5
    /// note Swift中对于string类型前面不需要加@
    var pen:(name:String,price:Int) = ("钢笔",2)
    /// 进行元组定义后,可以使用通过参数名称来取得各个参数
    var name = pen.name; //钢笔
    var price = pen.price; //2

  在创建元组时,可以不指定元组中的参数名称,此时元组会自动为每个参数分配下标,从0开始

1
2
3
var newpen:(String,Int) = ("钢笔",2)
var newpenName = newpen.0; //钢笔
var newpenPrice = newpen.1; // 2

  元组分解,元组创建后,可以通过制定变量为分解.

1
2
3
4
5
6
7
var newpen:(String,Int) = ("钢笔",2)
///分解元组,分组成员分解为两个变量
var (newpenName,newpenPrice) = newpen
print(newpenName,newpenPrice)
/// 匿名分解
var (newpenName2,_) = newpen;
print(newpenName2);

  在上面代码中,使用_来表示匿名的概念,因此只分解了第一个参数

  • 可选值类型
      optional类型是Swift特有的一种类型,在实现Swift类型安全上,使用Optional对普通类型进行包装,实现对空值的监控.在Swift中,如下代码会在编译时报错,
    1
    2
    3
    4
    5
    //Variable 'obj' used before being initialized
    var obj:String
    if obj == nil {
    obj = "111";
    }

  在Swift中如果需要基本类型为nil的话,应该将其包装为Optional类型.

1
2
3
4
5
6
7
var obj:String?
/// 在普通数据类型后加?包装为Optional类型.
/// Optional类型不会独立存在,总是附着在某个具体的数据类型上.具体数据类型可以是基本数据类型/结构体/类等.
/// Optional只有两种值 1、如果附着类型的对应变量有值,那么其为具体值的包装 2、如果没有具体值,那么为nil
if obj == nil {
obj = "111";
}

  Optional是对普通类型的一种包装,在使用时也需要拆包操作.拆包使用操作符”!”

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj:String?
if obj != nil {
obj!;
}
/// 一般开发中,更常用的拆包操作
var obj:String?
/// if let 结构将创建一个临时变量接受拆包后的数据
if let tmp = obj {
print(tmp)
} else {
obj = "111";
print(obj!)
}

字符,字符串和集合类型

字符串类型

  在Swift中,String类型是一个结构体,结构体中定义了属性和方法.String中提供了很多的重载构造方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
        var str:String = "123"; //使用字符串初始化
str = ""; // 使用空字符串
str = String(); // ""
str = String("123"); // "123"
str = String(123); /// 数字转字符串"123"
str = String("a"); /// 字符转字符串"a"
str = String(describing: (1,1,true)); /// 元组转字符串 "(1,1,true)"
str = String(false);
str = String(format: "12%@","3"); /// 格式化字符串 "123"
```

#### 字符串组合
  Swift中重载了+号运算符,可以将两个字符串连接起来.
var str1 = "hello";
var str2 = "world";
/// 注意重载运算符+两边不能有空格
str1 = str1+" "+str2;
print(str1);
1
  可以使用插值的方式对字符串进行组合拼接.这种方式**在开发中经常使用**
var str3 = "hello\(123)"
var str4 = "hello \(str2)"
print(str3,str4); // hello123 , hello world
1
2
#### 字符串类型中的常用方法
&ensp;&ensp;Swift中可以使用 >/</==比较两个字符串,其比较方式是对每一个字符依次比较其对应AscII码大小.
var sstr = "hello-swift"
var startIndex = sstr.startIndex; ///起始索引,不是一个integer
var endIndex = sstr.endIndex; /// 结束索引,不是一个integer,注意,此处返回结束位置的下一个位置
var lenth = sstr.count; /// 字符串长度 11

var char = sstr[sstr.index(after: startIndex)]; /// 第二个字符 e
var char2 = sstr[sstr.index(before: endIndex)]; /// 最后一个字符 t
print(char,char2); ///e t
var range = sstr.range(of: "hello") ///子串所在的位置
sstr.append("!") /// hello-Swift!
print(sstr)
sstr.insert("1", at: sstr.endIndex) /// hello-Swift!1
print(sstr)
sstr.insert(contentsOf: ["1","1","1"], at: sstr.endIndex) /// hello-Swift!1111
sstr.remove(at: sstr.index(before: sstr.endIndex)) /// hello-Swift!111
print(sstr)
sstr.hasPrefix("hell")  ///是否有前缀 yes
sstr.hasSuffix("222")  ///是否有后缀  no
sstr.uppercased()  ///转大写
sstr.lowercased()  ///转小写
1
2
3
#### 集合类型
- Array类型
&ensp;&ensp;申明

///申明空数组
var arr1:[Int] = []
var arr2:Array = Array()
arr1 = [1,2,3]; // [1,2,3]
arr2 = Array(arrayLiteral: 1,2,3) [1,2,3]

1
&ensp;&ensp;数组相加 Swift中对数组的+运算符也进行了重载,但是要保证相加的数组元素类型相同
var arr3 = [1,2,3]+[4,5,6] //[1,2,3,4,5,6]
1
&ensp;&ensp;数组中的常用方法
arr3.count; /// 个数
arr3.isEmpty /// 是否空
arr3.first; /// 第一个元素
arr3.last; ///最后一个元素
arr3.append(7); ///[1,2,3,4,5,6,7]
arr3.removeLast() /// [1,2,3,4,5,6]
arr3.removeLast(2) /// [1,2,3,4]
arr3.max() /// 最大值
arr3.min() /// 最小值
1
2
 - Set集合
&ensp;&ensp; set和C++中的set比较类型,对比即可,Swift中的set可以进行集合的运算.
var set1:Set<Int> = [1,2,3,4]
var set2:Set<Int> = [3,4,5,6] 
var setInter = set1.intersection(set2)  //[4, 3] 交集
var setEx = set1.symmetricDifference(set2) // [6, 5, 1, 2] 交集的补集
var setUni = set1.union(set2) /// 并集 [4, 5, 6, 1, 3, 2]
var setSub = set1.subtracting(set2) /// 差集 [1,2]
print(setInter,setEx, setUni,setSub)
1
2
3

#### Swift两种特殊运算符
- 空合并运算符 空合并运算符是针对Optional类型设计的运算符,比如如下判断代码
    var q:Int? = 8
    var value:Int
    if q != nil {
        /// 不为空,拆包
        value = q!
    } else {
        /// 为空,赋值0
        value = 0
    }
/// 使用空合并运算符
var q:Int? = 8
var value:Int
value = q ?? 0  /// 8
1
2
3
&ensp;&ensp;空合并运算符会自行判断是否为空,并自行拆包.**注意:??两边需要有空格**

- 区间运算符 在Swift中使用了区间运算符来快捷的表示范围.实例代码如下
var srange = 0...10
var srange2 = 0..<10

// var range3 = 0<..10 ///报错,没有左开区间
print(srange,srange2)

1
2
3

#### 分支选择结构
- Swift中的swicth结构有一定的优化,其中不需要加break在执行分支后进行跳出,case条件的匹配不仅仅局限于Int类型.可以进行任意类型的匹配.
var ssstr:String = "111"
switch ssstr {
case "111":
    print(sstr) ///自动break
case "333":
    print("333")
default:
    print("222")
}
1
- fallthrough:fallthrough可以使switch语句继续向下执行,不直接跳出
var ssstr:String = "111"
switch ssstr {
case "111":
    print(sstr) ///自动break
    fallthrough
case "333":
    print("333")
    fallthrough
default:
    print("222") /// log 111 333 222
}
1
- guard else 守护语句:作用是只在某个条件成立时才执行相应代码,类似if的效果
func foo(params:Int) {
    if params>0 {
        print(params)
    } else {
        return
    }
}

/// guard
func foo2(params:Int) {
/// params <= 0 直接返回
guard params>0 else {
return
}
print(params)
}

1
2
3
4
5
### 函数

#### 函数的基本使用
&ensp;&ensp; Swift中的函数定义需要在最前面增加一个**func**,不过在xcode的语法提示下,申明一个函数变得比较简单,下面是两个特殊点
- 不定数量函数参数:可以使用...实现不定参数个数的函数.需要注意不定参数的参数类型需要保持一致.
/// params1不定参数,但是类型一致
func foo3(params1:Int ...,params2:String) -> Int {
    var sum:Int = 0;
    for cout in params1 {
        sum += cout
    }
    print("foo3",sum)
    return sum
}
1
- Swift中如果是值传递,不能修改参数在函数内部的值.在Swift中类是引用类型,基本数据类型(枚举,结构体,float...)是值类型,如果需要修改值类型的参数,需要添加**inout**关键字,同时引用传递参数
func foo4(params:Int) -> Int {
    params = params+1;  ///不能修改params
    return params
}
/// 正确
func foo4(params:inout Int) -> Int {
    params = params+1;
    return params
}
1
2
3

#### 闭包
- 闭包可以和OC中的block进行类比,但是Swift中的闭包语法可以进行一定的简化.
func foo4(params:Int) -> Int {
    return params*params
}

let myClosure = {(params:Int)->Int in
    return params*params
}
foo4(3) /// 9
myClosure(3) /// 9
/// 1、闭包返回值类型可以通过定义自动推断

let myClosure2 = {(params:Int) in
    return params*params
}
myClosure2(3) /// 9
1
- 闭包作为函数参数

/// sortClosure 作为参数传递,进行数组排序
func mySort(array:inout Array, sortClosure:(Int,Int)->Bool) -> Array {
for indexI in array.indices {
if indexI == array.count-1 {
break
}
for indexJ in 0 … ((array.count - 1) - indexI - 1) {
if sortClosure(array[indexJ] as! Int, array[indexJ+1] as! Int) {

            } else {
                array.swapAt(indexJ, indexJ+1)
            }
        }
    }
    return array
}
/// 调用
mySort(array: &tmpArray, sortClosure: {(index:Int,index2:Int) -> Bool in
    return (arrays[index] as!Int) <= (arrays[index2] as!Int)

})

1
- 闭包的写法优化

///省略返回类型,编译器自动通过闭包返回类型推断
mySort(array: &tmpArray, sortClosure: {(index:Int,index2:Int) in
return (arrays[index] as!Int) <= (arrays[index2] as!Int)
})
/// 如果闭包只有一行代码,同时闭包作为函数的参数,闭包的return关键字可以省略
mySort(array: &tmpArray, sortClosure: {(index:Int,index2:Int) in
(arrays[index] as!Int) <= (arrays[index2] as!Int)
})
/// 当闭包围坐函数的参数时,闭包的参数列表会自动创建一组参数,参数名以$0,$1这样的结构类推,因此
mySort(array: &tmpArray, sortClosure: {
(arrays[$0] as! Int) <= (arrays[$1] as!Int)
})

1
2
- 后置闭包
&ensp;&ensp;当函数中的最后一个参数位闭包参数时,可以在调用函数时,将闭包结构脱离函数参数列表,追加在函数尾部.
///后置闭包写法
mySort(array: &tmpArray) {
    (arrays[$0] as! Int) <= (arrays[$1] as!Int)
}
1
&ensp;&ensp;当函数之后一个参数,并且参数时比好类型时,可以用后置闭包的写法省略函数的参数

/// 定义
func foo5(myClosure:(Int,Int)->Bool){

}

/// 调用
foo5 {
$0<=$1
}

1
2
3
4
5
6
7
8
9
- 逃逸闭包和非逃逸闭包
&ensp;&ensp;逃逸闭包指函数内的闭包在函数执行结束后在函数外依然可以执行.非逃逸闭包纸当函数的生命周期结束后,闭包也将被销毁.非逃逸闭包也不可以作为返回值返回.逃逸闭包一般用在异步操作中,比如接口调用之后对于返回结果的回调.

&ensp;&ensp;闭包类型是Swift中比较特殊的语法,需要多加练习.

### 运算符重载和枚举

#### 运算符重载
- 函数重载,是指参数列表不同,返回值不同的同名函数,OC中没有函数的重载,Swift中可以进行函数的重载
func adds(params1:Int,params2:Int)->Int {

}
func adds(params1:String,params2:Int)->String {

}
1
2

- 运算符重载,Swift从2.x版本后剔除了++运算符,但是通过重载运算符可以自定义相关的方法.

/// 前缀++
prefix operator ++
prefix func ++(params:Int) -> Int {
var ret = params
ret = ret+1
return ret
}
/// 中缀运算符重载
infix operator ++
func ++(params:Int,params2:Int) -> Int {
return (params * params2)
}
/// 后缀运算符重载
postfix operator ++
postfix func ++(params:Int) -> Int {
return params+1
}

1
2
#### 枚举的创建和应用
&ensp;&ensp;Swift中枚举的创建支持声明一个原始类型,如果定义为int类型时,如果设置第一个为1,后面的枚举都会自增1.

/// 声明字符枚举
enum charEnum:Character {
case a = “a”
case b = “b”
case c = “c”
}

1
2

&ensp;&ensp;Swift中枚举较为灵活,可以通过自定义枚举实现匹配

enum shape {
case circle(center:(Double,Double),radius:(Double))
case rectangle(center:(Double,Double),width:(Double),height:(Double))
case triangle(point1:(Double,Double),point2:(Double,Double),point3:(Double,Double))
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
然后使用switch语句时根据样式进行匹配.

### 类和结构体
#### 类和结构体的区别
- 类中需要定一个初始化方法,进行内部成员的初始化,结构体不需要,结构体会自动生成一个初始化方法在内部
- 结构是值类型,进行赋值是会进行一份内存的拷贝,修改其中一个,另一个不会影响.类是引用类型,赋值后两份指向同一份内存.
- 类可以继承,结构体不能继承.
- Swift中结构体内部可以定义方法.

#### 类的继承
- Swift中类可以被继承,实现子类自身所需要的特点.
- override关键字表示子类中重写父类中的方法.在重写的方法中可以通过super关键字调用父类的实现,然后继续子类的逻辑.
- final关键字可以修改类的方法,属性,类本身.被修饰后,不可以被子类继承或重写.

#### 使用场景
- 结构体:如果成员比较简单,不需要继承,所描述的数据类型需要以复制的形式进行.则使用结构体来定义,否则使用类来定义.
- Swift中array,dictionary,string,set等类型都是使用struct来定义的.因此进行赋值时都会拷贝一份新的数据.

### 构造方法的设计和使用
&ensp;&ensp;Swift中的构造方法与OC中的有区别,OC中的构造方法需要返回一个(instanceType)类型,Swift中无返回类型,Swift为了实现类型安全的特性,给构造方法增加了一些原则.Swift中的构造放包括指定构造放和便利构造方法,可失败构造方法和必要构造方法.想通的是在构造子类时需要先构造一个父类.

&ensp;&ensp;Swift中可以进行属性监听,但是监听在给属性设置初始值和构造方法时豆不会出发,只有在构造完成之后,属性监听才会生效.

class MyClass {
var count:Int = 0 {
willSet {
print(“willSet:count”)
}
didSet {
print(“didSet:count”)
}
}
init(params:Int) {
count = params
}
}
var objClass = MyClass(params: 5)
objClass.count = 3; ///只有在初始化后赋值才会触发属性监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

&ensp;&ensp;对于结构体,可以不用实现其构造方法,编译器默认生成一个构造方法,将属性作为参数.**对于值类型结构,比如结构体,如果自己定义了一个构造方法,那么系统生成的构造方法将会失效**

#### 指定构造方法和便利构造方法
&ensp;&ensp;指定构造方法(Designnated)不需要任何关键字修饰,便利构造方法需要使用(Convenience)修饰.便利构造方法方便开发者使用.但是也需要遵循一些原则.
- 子类的指定构造方法中必须调用父类的指定构造方法
- 便利构造方法中必须调用当前类的其他构造方法
- 便利构造方法归根结底要调用某个指定构造方法

&ensp;&ensp;可以这样理解这三个原则,1、子类构造方法需要调用父类的构造方法.2、便利构造方法是为了方便开发这调用,但是便利构造方法最终依赖于指定构造方法.

#### 构造方法的继承关系
- 在继承关系中,如果子类没有覆写或者重写任何指定构造方法,则默认子类会继承父类所有的指定构造方法.
- 如果子类提供了父类的指定构造方法,则子类默认继承父类的便利构造方法.

&ensp;&ensp;第一个原则说明子类如果自定义了指定构造方法,或者覆写了父类的某个指定构造方法,那么子类不在继承父类所有的指定构造方法.第二个原则说明由于所有的便利构造方法最终都会调用指定构造方法,因此无论子类中定义的便利构造方法与父类是否相同,都是子类独立的构造方法.

&ensp;&ensp;这里注意下覆写(override)和重载(overload)的概念,覆写是指子类对父类的方法进行重新定义.重载是指方法名相同,参数不同或者返回值不同的方法.

#### 构造方法的安全行检查
&ensp;&ensp;Swift在类的构造方法中会进行4项安全性检查.
- 在子类的构造方法中,必须完成当前类所有存储属性的构造,才能调用父类的指定构造方法,这样可以保证构造完成从父类继承下来的存储属性前,本身定义的存储属性也构造完成.
- 子类中如果要自定父类中的存储属性的值,需要在调用父类的构造方法之后再设置,可以保证在重新赋值时父类的属性已经构造完成.
- 如果便利构造方法中要重新设置某些存储属性的值,那么需要在调用指定构造方法之后设置,可以保证便利构造方法中的值不会被指定构造方法中的值覆盖.
- 子类在调用父类构造方法前,不能使用self引用属性,可以保证使用self调用实例本身时,实例已经构造完成.

class subCheck:check {
var subProperty:Int
init() {
/// 原则1调用父类构造方法前,先完成自身属性赋值
subProperty = 1;
super.init(param: 0);
/// 原则2 如果子类对父类属性重新赋值,需要在父类初始化方法之后,保证父类已经存在
property = 2;
/// 原则3完成父类构造方法之后,才能使用self关键字
self.subProperty = 3;
}
convenience init(param1:Int,param2:Int) {
self.init();
/// 原则4 便利构造方法中要修改属性的值需要在指定构造方法之后,防止值被覆盖
subProperty = 3;
property = param2;
}
}

1
2
3

#### 可失败构造方法和必要构造方法
&ensp;&ensp;Swift为了处理某些可能空的值,引入了optional可选值类型,对于类的构造方法,如果传递的参数不符合要求时,可以让构造方法失败,此时需要使用可失败构造方法的语法.其次,可以设置某些构造方法为必要构造方法,如果类中的某些构造方法被指定为必要构造方法,则其子类必须实现这个方法.必要构造方法使用required关键字修饰.

class reCheck {
var property:Int
/// 可失败构造方法
init?(params:Int) {
guard params>10 else {
return nil
}
property = params;
}
/// 此构造方法必须子类实现
required init(param1:Int,param2:Int) {
property = param2;
}
deinit {
print(“该类被销毁”);
}

}
```

  与构造方法相对应的是析构方法.使用deinit标识,当把实例置为nil时,实例会被释放