# README

[![](https://api.gitsponsors.com/api/badge/img?id=142772933)](https://api.gitsponsors.com/api/badge/link?p=ba+b75TK+cKDBuBEQC/J+DfRh9wflCckeu1Zn0v3b7X8cL93CKdfWQFCpEUXxXtwnT1OkffhmAurT5nSXZYbe0x48QZ0IW9Q/ztWWoUuUKv3fbsTx++OHPXBLXRToSE7gyfA37hrAENpv2YFPTgUoQ==)

#### 学习笔记

* 开发语言学习
  * Python
    * [0.概述](/kai-fa-yu-yan-xue-xi/python/0.-gai-shu)
    * [1.基本数据类型](/kai-fa-yu-yan-xue-xi/python/1.-ji-ben-shu-ju-lei-xing)
    * [2.变量](/kai-fa-yu-yan-xue-xi/python/2.-bian-liang)
    * [3.函数](/kai-fa-yu-yan-xue-xi/python/3.-han-shu)
    * [4.运算符](/kai-fa-yu-yan-xue-xi/python/4.-yun-suan-fu)
    * [5.字符串](/kai-fa-yu-yan-xue-xi/python/5.-zi-fu-chuan)
    * [6.列表](/kai-fa-yu-yan-xue-xi/python/6.-lie-biao)
    * [7.元组](/kai-fa-yu-yan-xue-xi/python/7.-yuan-zu)
    * [8.集合](/kai-fa-yu-yan-xue-xi/python/8.-ji-he)
    * [9.字典](/kai-fa-yu-yan-xue-xi/python/9.-zi-dian)
    * [10.流程控制](/kai-fa-yu-yan-xue-xi/python/10.-liu-cheng-kong-zhi)
    * [12.函数](/kai-fa-yu-yan-xue-xi/python/12.-han-shu)
    * [13.模块](/kai-fa-yu-yan-xue-xi/python/13.-mo-kuai)
    * [14.输入输出格式化](/kai-fa-yu-yan-xue-xi/python/14.-shu-ru-shu-chu-ge-shi-hua)
    * [15.面向对象](/kai-fa-yu-yan-xue-xi/python/15.-mian-xiang-dui-xiang)
  * Swift
    * 1.基础部分
      * [常量和变量以及基本类型\_1](/kai-fa-yu-yan-xue-xi/swift/1.-ji-chu-bu-fen/1.-chang-liang-he-bian-liang-yi-ji-ji-ben-lei-xing-1)
      * [常量和变量以及基本类型\_2](/kai-fa-yu-yan-xue-xi/swift/1.-ji-chu-bu-fen/2.-chang-liang-he-bian-liang-yi-ji-ji-ben-lei-xing-2)
    * 2.基本运算符
      * [基本运算符](/kai-fa-yu-yan-xue-xi/swift/2.-ji-ben-yun-suan-fu/1.-ji-ben-yun-suan-fu)
    * 3.字符串和字符
      * [字符串](/kai-fa-yu-yan-xue-xi/swift/3.-zi-fu-chuan-he-zi-fu/1.-zi-fu-chuan)
      * [字符串操作](/kai-fa-yu-yan-xue-xi/swift/3.-zi-fu-chuan-he-zi-fu/2.-zi-fu-chuan-cao-zuo)
    * 4.集合类型
      * [概述](/kai-fa-yu-yan-xue-xi/swift/4.-ji-he-lei-xing/1.-gai-shu)
      * [Array](/kai-fa-yu-yan-xue-xi/swift/4.-ji-he-lei-xing/2.array)
      * [Set](/kai-fa-yu-yan-xue-xi/swift/4.-ji-he-lei-xing/3.set)
      * [Dictionary](/kai-fa-yu-yan-xue-xi/swift/4.-ji-he-lei-xing/4.dictionary)
    * 6.函数
      * [函数](/kai-fa-yu-yan-xue-xi/swift/6.-han-shu/1.-han-shu)
      * [函数返回值](/kai-fa-yu-yan-xue-xi/swift/6.-han-shu/2.-han-shu-fan-hui-zhi)
      * [函数类型](/kai-fa-yu-yan-xue-xi/swift/6.-han-shu/3.-han-shu-lei-xing)
    * 7.闭包
      * [闭包](/kai-fa-yu-yan-xue-xi/swift/7.-bi-bao/1.-bi-bao)
      * [闭包表达式](/kai-fa-yu-yan-xue-xi/swift/7.-bi-bao/2.-bi-bao-biao-da-shi)
      * [尾随闭包](/kai-fa-yu-yan-xue-xi/swift/7.-bi-bao/3.-wei-sui-bi-bao)
      * [捕获变量或常量](/kai-fa-yu-yan-xue-xi/swift/7.-bi-bao/4.-bu-huo-bian-liang-huo-chang-liang)
      * [闭包是引用类型](/kai-fa-yu-yan-xue-xi/swift/7.-bi-bao/5.-bi-bao-shi-yin-yong-lei-xing)
      * [逃逸闭包](/kai-fa-yu-yan-xue-xi/swift/7.-bi-bao/6.-tao-yi-bi-bao)
      * [自动闭包](/kai-fa-yu-yan-xue-xi/swift/7.-bi-bao/7.-zi-dong-bi-bao)
    * 8.枚举
      * [枚举](/kai-fa-yu-yan-xue-xi/swift/8.-mei-ju/1.-mei-ju)
      * [枚举与switch语句](/kai-fa-yu-yan-xue-xi/swift/8.-mei-ju/2.-mei-ju-yu-switch-yu-ju)
    * 9.类和结构体
      * [类和结构体](/kai-fa-yu-yan-xue-xi/swift/9.-lei-he-jie-gou-ti/1.-lei-he-jie-gou-ti)
      * 属性
        * [属性](/kai-fa-yu-yan-xue-xi/swift/9.-lei-he-jie-gou-ti/shu-xing/2.1-shu-xing)
        * [属性观察器](/kai-fa-yu-yan-xue-xi/swift/9.-lei-he-jie-gou-ti/shu-xing/2.2-shu-xing-guan-cha-qi)
        * [属性包装器](/kai-fa-yu-yan-xue-xi/swift/9.-lei-he-jie-gou-ti/shu-xing/2.3-shu-xing-bao-zhuang-qi)
        * [全局变量和局部变量](/kai-fa-yu-yan-xue-xi/swift/9.-lei-he-jie-gou-ti/shu-xing/2.4-quan-ju-bian-liang-he-ju-bu-bian-liang)
        * [类属性](/kai-fa-yu-yan-xue-xi/swift/9.-lei-he-jie-gou-ti/shu-xing/2.5-lei-shu-xing)
      * [方法](https://github.com/ExistOrLive/DocumentForLearning/blob/master/开发语言学习/Swift/6.函数/3.方法.md)
  * Shell
    * [Shell变量和基本类型](/kai-fa-yu-yan-xue-xi/shell/shell-bian-liang-he-ji-ben-lei-xing)
    * [Shell函数](/kai-fa-yu-yan-xue-xi/shell/shell-han-shu)
    * [Shell基本运算符](/kai-fa-yu-yan-xue-xi/shell/shell-ji-ben-yun-suan-fu)
    * [Shell脚本的参数](/kai-fa-yu-yan-xue-xi/shell/shell-jiao-ben-de-can-shu)
    * [Shell流程控制](/kai-fa-yu-yan-xue-xi/shell/shell-liu-cheng-kong-zhi)
    * [Shell输入输出](/kai-fa-yu-yan-xue-xi/shell/shell-shu-ru-shu-chu)
    * [Shell文件包含](/kai-fa-yu-yan-xue-xi/shell/shell-wen-jian-bao-han)
* iOS
  * UI
    * UIKit
      * UIView
        * [UIView](/ios/ui/uikit/uiview/uiview)
        * [drawRect](/ios/ui/uikit/uiview/drawrect)
      * UITableView
        * [UITableView](/ios/ui/uikit/uitableview/uitableview)
        * [UITableView的加载过程](/ios/ui/uikit/uitableview/uitableview-de-jia-zai-guo-cheng)
* 学习开发工具
  * 静态分析
    * [OCLint](/xue-xi-kai-fa-gong-ju/jing-tai-fen-xi/oclint)
    * [infer](/xue-xi-kai-fa-gong-ju/jing-tai-fen-xi/infer)
    * [SwiftLint](/xue-xi-kai-fa-gong-ju/jing-tai-fen-xi/swiftlint)
  * iOS构建工具
    * fastlane
      * [fastlane](/xue-xi-kai-fa-gong-ju/ios-gou-jian-gong-ju/fastlane/fastlane)
      * [fastlane的安装](/xue-xi-kai-fa-gong-ju/ios-gou-jian-gong-ju/fastlane/fastlane-de-an-zhuang)
      * [fastlane一键打包](/xue-xi-kai-fa-gong-ju/ios-gou-jian-gong-ju/fastlane/fastlane-yi-jian-da-bao)
      * [fastlane证书管理](/xue-xi-kai-fa-gong-ju/ios-gou-jian-gong-ju/fastlane/fastlane-zheng-shu-guan-li)
  * Cocoapods
    * [安装pod库-podfile](/xue-xi-kai-fa-gong-ju/cocoapods/an-zhuang-pod-ku-podfile)
    * [创建pod库-podspec](/xue-xi-kai-fa-gong-ju/cocoapods/chuang-jian-pod-ku-podspec)
* 开源库
  * Tool
    * [Swiftlint源码学习](/kai-yuan-ku/tool/swiftlint-yuan-ma-xue-xi)
    * [利用Swiftlint自定义规则实现自动化code review](/kai-yuan-ku/tool/li-yong-swiftlint-zi-ding-yi-gui-ze-shi-xian-zi-dong-hua-code-review)


# ReadMe


# 开发语言学习


# Python


# 0.概述

#### 1. 引入模块

\#Swift\
\#基本语法\
\#语言

在 python 用 **import** 或者 **from...import** 来导入相应的模块。\
将整个模块(somemodule)导入，格式为： **import somemodule**\
从某个模块中导入某个函数,格式为： **from somemodule import somefunction**\
从某个模块中导入多个函数,格式为： **from somemodule import firstfunc, secondfunc, thirdfunc**\
将某个模块中的全部函数导入，格式为： \*\*from somemodule import \*\*\*

```python
import sys
print('================Python import mode==========================')
print ('命令行参数为:')
for i in sys.argv:
    print (i)
print ('\n python 路径为',sys.path)

from sys import argv,path  #  导入特定的成员
 
print('================python from import===================================')
print('path:',path) # 因为已经导入path成员，所以此处引用时不需要加sys.path
```

####

#### 2. builtin 模块

builtin 是内置模块，不需要显式地 import ；

builtin 中定义了 python 的基本能力，包括 基本的数据类型 (Number,String,Tuple等) 以及 常用函数 (print, abs 等)\
​

```python
import builtins      # 不需要显式import

if __name__ == '__main__':
    builtins.print(11)      # print 定义在 builtin 中

```

#### 3. 内置函数

```python
def issubclass(x, A_tuple)

def isinstance(x, A_tuple)  // 是否

def type(obj)    
```


# 1.基本数据类型

> Python 中的变量不需要声明。每个变量在使用前都必须赋值，变量赋值以后该变量才会被创建。在 Python 中，变量就是变量，它没有类型，我们所说的"类型"是变量所指的对象

## 标准数据类型

* Number(数字)
* String(字符串)
* List(列表)
* Tuple(元组)
* Set(集合)
* Dictionary(字典)

Number，String，Tuple为不可变的数据类型

List，Dictionary，Set 为可变的数据类型

## Number

> Python3支持int，float，bool，complex（Python只有int，没有long；只有float，没有double）

> 在 Python2 中是没有布尔型的，它用数字 0 表示 False，用 1 表示 True。到 Python3 中，把 True 和 False 定义成关键字了，但它们的值还是 1 和 0，它们可以和数字相加。

```python

5 + 4  # 加法

4.3 - 2 # 减法

3 * 7  # 乘法

2 / 4  # 除法，得到一个浮点数
0.5

2 // 4 # 除法，得到一个整数
0

17 % 3 # 取余 
2

2 ** 5 # 乘方
32

```

> 在混合计算时，Python会把整型转换成为浮点数

## String

> Python中的字符串用单引号 ' 或双引号 " 括起来，同时使用反斜杠 \ 转义特殊字符。

> 反斜杠可以用来转义，使用r可以让反斜杠不发生转义

> Python中没有字符类型，所有的字符都是长度为1的字符串

> python的索引值有两个方向，索引值以 0 为开始值，-1 为从末尾的开始位置

> 字符串截取语法`变量[头下标:尾下标]`

```python
str = 'Runoob'
 
print (str)          # 输出字符串
print (str[0:-1])    # 输出第一个到倒数第二个的所有字符
print (str[0])       # 输出字符串第一个字符
print (str[2:5])     # 输出从第三个开始到第五个的字符
print (str[2:])      # 输出从第三个开始的后的所有字符
print (str * 2)      # 输出字符串两次
print (str + "TEST") # 连接字符串
```

### Tip

* 反斜杠可以用来转义，使用r可以让反斜杠不发生转义。`r"asb\nb"`,其中r会让\n的转义不发生作用
* 字符串可以用+运算符连接在一起，用\*运算符重复。
* Python中的字符串有两种索引方式，从左往右以0开始，从右往左以-1开始。
* Python中的字符串不能改变。与 C 字符串不同的是，Python 字符串不能被改变。向一个索引位置赋值，比如word\[0] = 'm'会导致错误

## List

```python

#!/usr/bin/python3
 
list = [ 'abcd', 786 , 2.23, 'runoob', 70.2 ]
tinylist = [123, 'runoob']
 
print (list)            # 输出完整列表
print (list[0])         # 输出列表第一个元素
print (list[1:3])       # 从第二个开始输出到第三个元素
print (list[2:])        # 输出从第三个元素开始的所有元素
print (tinylist * 2)    # 输出两次列表
print (list + tinylist) # 连接列表

```

### Tip

* List写在方括号之间，元素用逗号隔开。
* 和字符串一样，list可以被索引和切片。
* List可以使用+操作符进行拼接。
* List中的元素是可以改变的。

## Tuple

> 元组（tuple）与列表类似，不同之处在于元组的元素不能修改。元组写在小括号 () 里，元素之间用逗号隔开。元组中的元素类型也可以不相同

```python

tuple = ( 'abcd', 786 , 2.23, 'runoob', 70.2  )
tinytuple = (123, 'runoob')
 
print (tuple)             # 输出完整元组
print (tuple[0])          # 输出元组的第一个元素
print (tuple[1:3])        # 输出从第二个元素开始到第三个元素
print (tuple[2:])         # 输出从第三个元素开始的所有元素
print (tinytuple * 2)     # 输出两次元组
print (tuple + tinytuple) # 连接元组

```

> 其实，可以把字符串看作一种特殊的元组。

```python

tup1 = ()           # 空元组
tup2 = (20,)        # 只有一个元素的元组

```

### Tip

* 与字符串一样，元组的元素不能修改。
* 元组也可以被索引和切片，方法一样。
* 注意构造包含 0 或 1 个元素的元组的特殊语法规则。
* 元组也可以使用+操作符进行拼接。

## Set

> 集合（set）是由一个或数个形态各异的大小整体组成的，构成集合的事物或对象称作元素或是成员。

> 可以使用大括号 { } 或者 set() 函数创建集合，注意：创建一个空集合必须用 set() 而不是 { }，因为 { } 是用来创建一个空字典

```python
student = {'Tom', 'Jim', 'Mary', 'Tom', 'Jack', 'Rose'}

set1 = set() #  创建空集合
dic = {}     #  这是一个空的字典
 
print(student)   # 输出集合，重复的元素被自动去掉
 
# 成员测试
if 'Rose' in student :
    print('Rose 在集合中')
else :
    print('Rose 不在集合中')
 
 
# set可以进行集合运算
a = set('abracadabra')
b = set('alacazam')
 
print(a)
 
print(a - b)     # a 和 b 的差集
 
print(a | b)     # a 和 b 的并集
 
print(a & b)     # a 和 b 的交集
 
print(a ^ b)     # a 和 b 中不同时存在的元素

```

### Tip

* set中的元素必须为不可变的对象

## Dictionary

> 字典（dictionary）是Python中另一个非常有用的内置数据类型。

> 列表是有序的对象集合，字典是无序的对象集合。两者之间的区别在于：字典当中的元素是通过键来存取的，而不是通过偏移存取。

> 字典是一种映射类型，字典用 { } 标识，它是一个无序的 键(key) : 值(value) 的集合。

> 键(key)必须使用不可变类型。在同一个字典中，键(key)必须是唯一的。

```python

dict = {}
dict['one'] = "1 - 菜鸟教程"
dict[2]     = "2 - 菜鸟工具"
 
tinydict = {'name': 'runoob','code':1, 'site': 'www.runoob.com'}
 
 
print (dict['one'])       # 输出键为 'one' 的值
print (dict[2])           # 输出键为 2 的值
print (tinydict)          # 输出完整的字典
print (tinydict.keys())   # 输出所有键
print (tinydict.values()) # 输出所有值
```


# 2.变量

```python
a = "dasdsa"
```

> 如上定义了变量a，并赋值。

* python变量的声明及定义，不需要指明变量的类型（当然python中变量是没有类型的），也不需要其他关键字表明定义一个变量（如swift 中的var）。
* 但是变量定义后必须赋值，不然没有任何意义，而且会报错，如下：

![](/files/-MjGncSDV-a3qCCwqB-d)

## 变量作用域

> python 的变量作用域由变量在哪里定义的决定，

* L （Local） 局部作用域
* E （Enclosing） 闭包函数外的函数中
* G （Global） 全局作用域
* B （Built-in） 内置作用域（内置函数所在模块的范围）

```python
g_count = 0  # 全局作用域
def outer():
    o_count = 1  # 闭包函数外的函数中
    def inner():
        i_count = 2  # 局部作用域
```

> 内置作用域是通过一个名为 builtin 的标准模块来实现的，但是这个变量名自身并没有放入内置作用域内，所以必须导入这个文件才能够使用它。

> Python 中只有模块（module），类（class）以及函数（def、lambda）才会引入新的作用域，其它的代码块（如 if/elif/else/、try/except、for/while等）是不会引入新的作用域的，也就是说这些语句内定义的变量，外部也可以访问

```python
if True:
   a = "123"
else
   b = "1234"
print(a)          # a 定义了且可以访问； 只有模块，类，函数可以引入新的作用域
print(b)          # b 没有定义，代码没有执行到

```

## 全局变量和局部变量

> 定义在函数内部的变量拥有一个局部作用域，定义在函数外的拥有全局作用域。

> 局部变量只能在其被声明的函数内部访问，而全局变量可以在整个程序范围内访问

```python

a = 11            # 全局变量
def func():
    print(a)      # 可以直接访问全局变量
func()

```

```python

a = 11            # 全局变量
def func():
    a = 12        # 定义局部变量     
    print(a)      # 这里a为局部变量
func()

```

```python

a = 11            # 全局变量
def func():   
    a = a + 1     # 这里会报错； 后面的a 是局部变量，但是没有被定义
    print(a)      
    
func()

```

![](/files/5EptuKQt07UjgxXH8TZS)

> 当内部作用域想修改外部作用域的变量时，就要用到global和nonlocal关键字了

```python

# 修改全局变量

a = 11            # 全局变量
def func():
    global a      # 声明这里的a 为全局变量a     
    a = a + 1
    print(a)      # 可以直接访问全局变量
func()

print(a)          # 这里a已经被修改



def func1():
    b = 11
    def func2():
        nonlocal b
        b = b + 1

```


# 3.函数

## 函数定义

* 函数代码块以 def 关键词开头，后接函数标识符名称和圆括号 ()。
* 任何传入参数和自变量必须放在圆括号中间，圆括号之间可以用于定义参数。
* 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
* 函数内容以冒号起始，并且缩进。
* return \[表达式] 结束函数，选择性地返回一个值给调用方。不带表达式的return相当于返回 None。

```python

def test(str):    # def 函数名(参数) ：
    print(str)    #     函数体
    return

```

## 参数传递

> 由于python的所有类型都是对象类型（包括int，float），变量中保存的都是对象的地址。因此函数的参数传递都是指针传递。

## 参数

* 必需参数
* 关键字参数
* 默认参数
* 不定长参数

### 必须参数

```python

def test(str1,num1,str2):
    num2 = num1 + 1
    str3 = str1 + str2
    print(num2,str3)

test("123",1,"1234")

```

> 如上 str1，num1，str2都是必须参数，不可以少，且顺序不可以变

### 关键字参数

```python

# 仍是上面定义的test方法

test(str2 = "dadsa",str1 = "2131", num1 = 11)

```

> 可以通过关键字表明是传递的是哪个参数，这样可以不按顺序传入

### 默认参数

> 调用函数时，如果没有传递参数，则会使用默认参数；默认参数的后面必须都是默认参数

```python

def test(str1,num1 = 0,str2 = "123"):
    num2 = num1 + 1
    str3 = str1 + str2
    print(num2,str3)


test("DASD")

```

> 如上num1，和 str2 有默认的参数，但是不可以num1为默认参数，而str2不是

### 变长参数

> 有些函数的参数的个数是不确定的，如，`def print(self, *args, sep=' ', end='\n', file=None):`这里就会需要变长参数

```python

def functionname([formal_args,] *var_args_tuple ):

def functionname([formal_args,] *var_args_dic ):

```

> 变长参数有两种形式，一种是


# 4.运算符

#### 1. 算术运算

`/` 运算符返回结果为 浮点数 ， `//` 返回结果为 整数\
​

`**`运算符用于 乘方 运算\
​

```python
>>> 5 + 4  # 加法
9
>>> 4.3 - 2 # 减法
2.3
>>> 3 * 7  # 乘法
21
>>> 2 / 4  # 除法，得到一个浮点数
0.5
>>> 2 // 4 # 除法，得到一个整数
0
>>> 17 % 3 # 取余
2
>>> 2 ** 5 # 乘方
32
```

####

#### 2. 比较运算符

略\
​

#### 3. 位运算符

`~`, `^`, `&`, `|` ，`>>`, `<<` 等 运算符

#### 4. 逻辑运算符

python 中没有 `&&`, `||` , `!` 逻辑运算符

`and` 对应 `&&or` 对应 `||not` 对应 `!`\
​

#### 5. 成员运算符

`in` `not in` 运算符 检查某个元素是否在集合中 (arrray, set, string)\
​

```python
a = 10 
b = 11
array = [1,2,10]

print(a in array)

print(b not in array)

```


# 5.字符串

* python中单引号和双引号使用完全相同。
* 使用三引号('''或""")可以指定一个多行字符串。
* 反斜杠可以用来转义，使用r可以让反斜杠不发生转义。。 如 r"this is a line with \n" 则\n会显示，并不是换行。
* 按字面意义级联字符串，如"this " "is " "string"会被自动转换为this is string。
* 字符串可以用 + 运算符连接在一起，用 \* 运算符重复。
* Python 中的字符串有两种索引方式，从左往右以 0 开始，从右往左以 -1 开始。
* Python中的字符串不能改变.
* Python 没有单独的字符类型，一个字符就是长度为 1 的字符串。
* 字符串的截取的语法格式如下：变量\[头下标:尾下标:步长]

## python 字符串的常用函数

```python

title()   // 首字母大写

upper()   // 字母大写

lower()   // 字母小写

rstrip()  // 删除尾部空白

lstrip()   // 删除头部空白

strip()   // 删除头尾空白
```


# 6.列表

* 列表是Python中的基本数据结构，是可以修改的。
* 列表可以通过索引访问元素，正向索引从 0 开始，反向索引从 -1 开始；
* 列表都可以进行的操作包括索引，切片，加，乘，检查成员。
* 列表内置了许多常用的方法，包括获取列表长度，列表最大最小的元素，
* 列表中的元素可以是不同的类型

## 1. 访问列表中的元素

列表中的元素通过索引访问，正向索引从 0 开始，反向索引从 -1 开始

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2021-11-22%20%E4%B8%8A%E5%8D%8812.27.53.png)

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2021-11-22%20%E4%B8%8A%E5%8D%8812.28.10.png)

```python
ls = [1,2,3,4]
print(ls[0])   # 1
print(ls[-1])  # 4
```

## 2. 列表切片

```python
ls = [1,2,3,4]

print(ls[0:-1])   # [1,2,3]

```

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2021-11-22%20%E4%B8%8A%E5%8D%8812.32.01.png)

## 3. 修改列表

```python
ls = [1,2,3,4]

# 通过索引修改元素
ls[0] = 11 
print(ls)      # [11,2,3,4] 

# 在列表后插入元素 
ls.append(13)
print(ls)      # [11,2,3,4,13]

# 删除元素
del ls[0]
print(ls)      # [2,3,4,13]

# 在头部插入元素 
ls.insert(0,15)
print(ls)      # [15,2,3,4,13]

```

## 4. 遍历

```python
ls = [1,2,3,4]

for item in ls:
    print(item)

```

## 4. 常用方法

```swift

len(list)     # 列表长度

max(list)     # 最大值

min(list)     # 最小值

list(tuple)   # 将元组转换为列表


# 在列表末尾添加新的对象
list.append(obj)

# 统计某个元素在列表中出现的次数
list.count(obj)

# 在列表末添加另一个列表的元素
list.extend(list1)

# 插入元素
list.insert(index,obj)

# 移除某个元素
list.remove(obj)

# 移除索引指定的一个元素（默认最后一个元素），并且返回该元素的值
list.pop(index=-1)

# 反序
list.reverse()

# 排序
list.sort(key=None,reverse=False)

# 清空列表
list.clear()

# 复制列表
list.copy()
```


# 7.元组

元组可以理解为不可修改的列表。元组使用 () 定义，而非 \[].

```py
tup = (1,2,3)

# 当元组中只包含一个元素，应该加，.否则圆括号会被当作运算符使用
tup = (1)
print(type(tup))  # <class 'int'>

tup = (1,)
print(type(tup))  # <class 'tuple'>
```

## 元组操作

元组除了不可以修改，可以像列表一样，通过索引访问，截取，遍历

```py
tup = (1,2,3,4)

print(tup[0])

print(tup[0:-1])

for item in tup:
    print(item)
```

删除元组：

```py
# 元组不可以修改，但是可以通过del删除整个元组
tup = (1,2,3,4)
del tup
print(tup)

'''
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'tup' is not defined
'''
```

## 元组的函数

```py
len(tup)

max(tup)

min(tup)

# 将可迭代系列转换为元组
tuple(iterable)
```


# 8.集合

**集合（set）​是一个无序的不重复元素序列。**\
**可以使用大括号 { } 或者 set() 函数创建集合，注意：创建一个空集合必须用 set()** 而不是 **{ }**，因为 **{ }** 是用来创建一个空字典\
​

```python
# 空集合
set1 = set()

# 将列表和元组转换为set
set2 = set((1,3,4))
set3 = set([1,23,3])

```

### 集合的操作

集合可以进行**交差并补**操作\
​

```python

a = set('abracadabra')
b = set('alacazam')

print(a | b)           # 并集

print(a & b)           # 交集

print(a - b)           # 差集

print(a ^ b)           # 不同时包含于a和b的元素
```

### 集合方法和函数

```python
set.add(obj)          # 添加元素
set.update(iterable)  

set.remove(obj)       # 删除元素，如果obj不存在则报错
set.discard(obj)      # 删除元素，obj不存在不报错
set.pop()             # 随机移除一个元素

set.clear()           # 清空集合

set.difference(iterable)   # 差集
set.difference_update(iterable)  # 删除iterable中包含的元素

set.intersection(iterable)   # 交集
set.intersection_update(iterable)  # 删除iterable中不包含的元素

isdisjoint()
issubset()
issuperset()
```


# 9.字典

```python
dict = {'name': 'runoob', 'likes': 123, 'url': 'www.runoob.com'}
```

Python中的字典是一种可变的容器，可以用于保存任意类型的对象。\
​

key 必须是不可变类型(如 number，str), value 可以是任意类型\
​

### 字典的操作

```python
# 创建空字典
dic = {} 

dic = {'Name': 'Runoob', 'Age': 7, 'Class': 'First'}

# 通过key修改和访问字典
dic['Name'] = 'Runoob1'
print(dic['Age'])

# 删除和清空字典
del dic['Name']        # 删除某个key
dic.remove()           # 清空字典
del dic                # 删除字典




```

**Tip**\
​

key必须是不可变的，因此可以使用 number，str 和 tuple 作为 key，但是 list，dictionary 等可变类型不可以作为key\
​

​

### 字典的函数和方法

```python
len(dic)                      # 字典的长度

str(dic)                      # 将字典转换为字符串

type(dic)                     # 获取类型



dic.clear()                   # 清空

dic.copy()                    # 浅拷贝

dic.fromkeys(iterable,value)     # 根据iterable传进来的值作为 key，以及 默认value 创建新字典


dic.get(key,default = None)   # 获取Key对应的值，key不存在返回默认值
dic[key]                      # 获取Key对应的值，key不存在报错

dic.update(newDic)            # 以newDic来更新 dic

dic.setDefault(key,defaultValue)     # 如果key对应的value不存在，则会设置为defaultValue

dic.items()
dic.keys()
dic.values()

dic.pop(key)
del dic[key]                  # 删除某个key
```

​

​


# 10.流程控制

### 1. 条件语句 if/else

```python
if condition:
    #####
elif condition:
    #####
else: 
    ##### 
```

在 Python 中，没有 switch/case 语句\
​

### 2. 循环语句 while/for

```python
# while 语句

while condition:
    #### condition 满足执行
else:
    #### 循环结束执行
    
      
# for 语句

for <variable> in <sequence>:
    #### 
else:
    #### 循环结束执行

```

### 3. Range函数

**Python** 内置 range 函数可以创建一组数列(不是list)\
​

```python
range1 = range(5)           # 0,1,2,3,4

range2 = range(1,5)         # 1,2,3,4

range3 = range(1,10,2)      # 1,3,5,7,9         2 为 步长


li = list(range(1,11))      # 使用range创建 list
```

​

### 4. break/continue

break 语句用于跳出循环体\
​

continue 语句用于结束当前这次循环，继续下一次循环\
​

跳出循环体之后，else 语句将不会执行

### 5. pass

Python 的 **pass** 是空语句，是为了保持程序结构的完整性。**pass**​ 不做任何事情，一般用做占位语句

```python
for letter in 'Runoob': 
   if letter == 'o':
      pass
      print ('执行 pass 块')
   print ('当前字母 :', letter)
 
print ("Good bye!")


'''
当前字母 : R
当前字母 : u
当前字母 : n
执行 pass 块
当前字母 : o
执行 pass 块
当前字母 : o
当前字母 : b
Good bye!
'''
```


# 12.函数

## 1. 函数的定义

```python
def 函数名（参数列表）:
    函数体
```

Python中的函数以 `def` 关键字定义，后接 **函数名**，**参数列表**在 `()` 中，`:` 之后接**函数体**\
​

#### Tip

**python**中的函数不显示指定返回值类型，根据 **return语句** 的返回值自动判断。 如果没有 **return** 语句，则返回 None\
​

## 2. 参数传递

python 中的一切都是对象，变量中保存的是对象的地址。

因此函数中的参数传递方式就是引用传递(指针传递)。但是需要注意的是 python 中的对象分为 **可变类型** 和 **不可变类型**。

* **不可变类型**：作为参数传递到函数中后，不能修改对象的值，只能够修改参数变量中保存的对象地址. 这样的表现则像是值传递(尽管事实上并不是)

  不可变类型有 numbers，str，tuple，

```python

def change(a):
    print(id(a))   # 指向的是同一个对象
    a=10
    print(id(a))   # 一个新对象
 
a=1
print(id(a))
change(a)

'''
4344209672
4344209672
4344209960
'''

```

* **可变类型** : 可变类型的表现就完全是正常的引用传递

## 3. 参数类型

python中的参数分为：

* 必需参数
* 关键字参数
* 默认参数
* 不定长参数

#### 必需参数

**必需参数**必须是按照声明时的顺序，数量进行传递，否则会出现语法错误

```python
def min(a,b):
    if a > b :
        return b
    else :
        return a

print(min(11)) # 报错，缺少一个参数
```

![](https://pic.existorlive.cn/202112030147517.png)

#### 关键字参数

参数在传递时以**参数名**作为**关键字**，这样参数就不是必须以声明时的顺序传递

```python
print(min(b=11,a=2))    # a,b的顺序可以颠倒
```

#### 默认参数

参数在声明时可以指定默认参数

```python
#可写函数说明
def printinfo( name, age = 35 ):
   "打印任何传入的字符串"
   print ("名字: ", name)
   print ("年龄: ", age)
   return
 
#调用printinfo函数
printinfo( age=50, name="runoob" )
print ("------------------------")
printinfo( name="runoob" )
```

注意一点，同 C++/Swift 一样，默认参数后的参数也必须是默认参数

#### 不定长参数

你可能需要一个函数能处理比当初声明时更多的参数。

```python
def functionname([formal_args,] *var_args_tuple ):
   "函数_文档字符串"
   function_suite
   return [expression]

def functionname([formal_args,] **var_args_dict ):
   "函数_文档字符串"
   function_suite
   return [expression]
```

\[ \*参数名 ], 在函数内使用时是一个元组

\[ \*\*参数名 ] 在函数内使用时是一个字典

```python
# 可写函数说明
def printinfo( arg1, *vartuple ):
   "打印任何传入的参数"
   print ("输出: ")
   print (arg1)
   for var in vartuple:
      print (var)
   return
 
# 调用printinfo 函数
printinfo( 10 )
printinfo( 70, 60, 50 )

# 可写函数说明
def printinfo( arg1, **vardict ):
   "打印任何传入的参数"
   print ("输出: ")
   print (arg1)
   print (vardict)
 
# 调用printinfo 函数
printinfo(1, a=2,b=3)
```

## 4. 匿名函数/lambda表达式

python中使用 **lambda** 关键字创建匿名函数

```python
lambda [arg1 [,arg2,.....argn]]:expression
```

**lambda** 关键字后直接跟 **参数列表** 和 **函数体**，不需要函数名

```python
# 可写函数说明
sum = lambda arg1, arg2: arg1 + arg2
 
# 调用sum函数
print ("相加后的值为 : ", sum( 10, 20 ))
print ("相加后的值为 : ", sum( 20, 20 ))
```

* lambda 只是一个表达式，函数体比 def 简单很多。
* lambda的主体是一个表达式，而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
* **lambda 函数拥有自己的命名空间，且不能访问自己参数列表之外或全局命名空间里的参数**。
* 虽然lambda函数看起来只能写一行，却不等同于C或C++的内联函数，后者的目的是调用小函数时不占用栈内存从而增加运行效率。


# 13.模块

**模块** 是一个包含所有你定义的函数和变量的文件，其后缀名是`.py`。模块可以被别的程序引入，以使用该模块中的函数等功能。

## 1. 引入模块 import

`import` 关键字用于引入模块

```py
import requests 
```

**Tip**: 不论 import 调用多少回， 同一个模块只会被引入一次

`from ... import ...` 语句用于从模块中引入部分代码，而不引入整个模块

```py
# 从 fibo 模块中引入 fib，fib2 函数 
from fibo import fib, fib2
fib(500)
```

### 搜索路径

当使用`import`关键字来引入模块时，会按照搜索路径指定的目录逐个去查询是否有匹配的模块名。

**搜索路径** 是在Python编译或安装的时候确定的，安装新的库应该也会修改。

搜索路径被存储在sys模块中的path变量。

![](https://pic.existorlive.cn/202112230047970.png)

搜索路径一般包括**当前执行脚本目录**，**python解释器的安装目录**

## 2. 深入模块

模块除了方法定义，**还可以包括可执行的代码。这些代码一般用来初始化这个模块。这些代码只有在第一次被导入时才会被执行**。

每个模块有各自独立的**符号表**，在模块内部为所有的函数当作全局符号表来使用。

模块是可以导入其他模块的，被导入的模块的名称将被放入当前操作的模块的符号表中。另外还可以使用 import 直接把模块内（函数，变量的）名称导入到当前操作模块。

```py
# 导入模块中所有的名字
import requests 

# 导入模块内的 fib，fib2
from fibo import fib, fib2

# 导入模块内的所有名字
from fibo import *
```

### 2.1 **name** 属性

一个模块被另一个程序第一次引入时，其主程序将运行。如果我们想在模块被引入时，模块中的某一程序块不执行，我们可以用`__name__`属性来使该程序块仅在该模块自身运行时执行。

```py
#!/usr/bin/python3
# Filename: using_name.py

if __name__ == '__main__':
   print('程序自身在运行')
else:
   print('我来自另一模块')
```

> 每个模块都有一个\_\_name\_\_属性，当其值是'**main**'时，表明该模块自身在运行，否则是被引入。

### 2.2 dir() 函数

内置函数 `dir()` 可以输出模块内定义的所有名称

```py
import sys
dir(sys)
```

![](https://pic.existorlive.cn/202112230106834.png)

## 3. 包

包是一种管理 Python 模块命名空间的形式，采用"点模块名称"。 一个包中可以包含多个模块。\
比如一个模块的名称是 A.B， 那么他表示一个包 A中的子模块 B 。

包的目录下必须包含一个 `__init_.py` 文件，

![](https://pic.existorlive.cn/202112230114238.png)

如图，`concurrent`包中有一个子包`futures`, `futures`包中包含`process`,`thread`等模块

## 总结

* 每个模块都有独立的符号表，引入模块就是将其他模块的名称引入当前模块的符号表

```py
# 导入模块中所有的名字
import requests 

# 导入模块内的 fib，fib2
from fibo import fib, fib2
```

* 包是管理模块的目录，目录下必须有`__init__.py` 文件
*


# 14.输入输出格式化

## 1. 标准输入 (`input`)

`input` 函数接收命令行输入

![](https://pic.existorlive.cn/202112240034845.png)

## 2. 标准输出 (`print`)

`print` 函数用于将字符串输出到命令行中

### 2.1 str()/repr()

* `str()` ： 函数返回一个用户易读的表达形式。
* `repr()`: 产生一个解释器易读的表达形式

### 2.2 rjust()/ljust()/center()

用**空格**将字符串补全至目标长度；

* rjust(num) ： 右对齐
* ljust(num) ： 左对齐
* center(num) ： 局中

![](https://pic.existorlive.cn/202112240100826.png)

### 2.3 zfill()

在数字左边填充 0

![](https://pic.existorlive.cn/202112240102695.png)


# 15.面向对象

### 1. 类的定义

**python**中的类通过 `class` 关键字来定义，类的成员有**属性**和**方法**​

```python
# 示例
class ClassName: 
    <statement-1>
    .
    .
    .
    <statement-N> 
```

#### 1.1 属性

类的属性可以显式地定义在类中, 同变量一样，属性的定义必须初始化

```python
class A:
    a = 14
    b = 12
    
print(A().a)
# 14
```

属性还可以隐式地通过构造方法来定义

```python
class A:
    # 在构造方法中隐式的定义属性：
    def __init__(self):
        self.a = 13
        self.b = 12
print(A().a)
# 13
```

#### 1.2 构造方法 \_**init**(self)

Python的构造方法是名为 `__init__` 的方法，第一个参数为 `self`, 指向当前对象。

```python
class A:
    def __init__(self,a,b):
        self.a = a
        self.b = b
print(A(11,12).a)
# 11
```

#### 1.3 类的方法

类的方法不同于函数，有一个默认参数 `self`

### 2. 继承

子类可以继承父类的属性和方法，支持多继承。

```swift
class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>
```

方法的搜索规则：

需要注意圆括号中父类的顺序，若是父类中有相同的方法名，而在子类使用时未指定，python从左至右搜索 即方法在子类中未找到时，从左到右查找父类中是否包含方法。

#### 2.1 重写方法

* 在子类中定义与父类相同的方法，就会在子类中重写该方法。
* 在子类中调用父类方法，需要显示指定父类的名字（可能有多个父类）

```swift
#类定义
class people:
    #定义基本属性
    name = ''
    age = 0
    #定义私有属性,私有属性在类外部无法直接进行访问
    __weight = 0
    #定义构造方法
    def __init__(self,n,a,w):
        self.name = n
        self.age = a
        self.__weight = w
    def speak(self):
        print("%s 说: 我 %d 岁。" %(self.name,self.age))
 
#单继承示例
class student(people):
    grade = ''
    def __init__(self,n,a,w,g):
        #调用父类的构函
        people.__init__(self,n,a,w)
        self.grade = g
    #覆写父类的方法
    def speak(self):
        print("%s 说: 我 %d 岁了，我在读 %d 年级"%(self.name,self.age,self.grade))
 
#另一个类，多重继承之前的准备
class speaker():
    topic = ''
    name = ''
    def __init__(self,n,t):
        self.name = n
        self.topic = t
    def speak(self):
        print("我叫 %s，我是一个演说家，我演讲的主题是 %s"%(self.name,self.topic))
 
#多重继承
class sample(speaker,student):
    a =''
    def __init__(self,n,a,w,g,t):
        student.__init__(self,n,a,w,g)
        speaker.__init__(self,n,t)
 
test = sample("Tim",25,80,4,"Python")
test.speak()   #方法名同，默认调用的是在括号中参数位置排前父类的方法
```

强制调用父类的方法：

```swift
s = student("Tim",12,180)

super(student,s).speak()
# Tim 说: 我 12 岁
```

### 3. 访问控制

#### 3.1 私有成员

* 类的私有属性 : `__private_attrs`：两个下划线开头，声明该属性为私有，不能在类的外部被使用或直接访问。在类内部的方法中使用时 `self.__private_attrs`。
* 类的私有方法 : `__private_method`：两个下划线开头，声明该方法为私有方法，只能在类的内部调用 ，不能在类的外部调用。`self.__private_methods`。

### 4. 专有方法

类的专有方法：

* `__init__` : 构造函数，在生成对象时调用
* `__del__` : 析构函数，释放对象时使用
* `__repr__` : 打印，转换
* `__setitem__` : 按照索引赋值
* `__getitem__`: 按照索引获取值
* `__len__` : 获得长度
* `__cmp__` : 比较运算
* `__call__` : 函数调用
* `__add__` : 加运算
* `__sub__` : 减运算
* `__mul__` : 乘运算
* `__truediv__` : 除运算
* `__mod__` : 求余运算
* `__pow__` : 乘方

```python
class object:
    __doc__: str | None
    __dict__: dict[str, Any]
    __module__: str
    __annotations__: dict[str, Any]
    @property
    def __class__(self: Self) -> type[Self]: ...
    # Ignore errors about type mismatch between property getter and setter
    @__class__.setter
    def __class__(self, __type: type[object]) -> None: ...  # type: ignore # noqa: F811
    def __init__(self) -> None: ...
    def __new__(cls: type[Self]) -> Self: ...
    # N.B. `object.__setattr__` and `object.__delattr__` are heavily special-cased by type checkers.
    # Overriding them in subclasses has different semantics, even if the override has an identical signature.
    def __setattr__(self, __name: str, __value: Any) -> None: ...
    def __delattr__(self, __name: str) -> None: ...
    def __eq__(self, __o: object) -> bool: ...
    def __ne__(self, __o: object) -> bool: ...
    def __str__(self) -> str: ...  # noqa Y029
    def __repr__(self) -> str: ...  # noqa Y029
    def __hash__(self) -> int: ...
    def __format__(self, __format_spec: str) -> str: ...
    def __getattribute__(self, __name: str) -> Any: ...
    def __sizeof__(self) -> int: ...
    # return type of pickle methods is rather hard to express in the current type system
    # see #6661 and https://docs.python.org/3/library/pickle.html#object.__reduce__
    def __reduce__(self) -> str | tuple[Any, ...]: ...
    if sys.version_info >= (3, 8):
        def __reduce_ex__(self, __protocol: SupportsIndex) -> str | tuple[Any, ...]: ...
    else:
        def __reduce_ex__(self, __protocol: int) -> str | tuple[Any, ...]: ...

    def __dir__(self) -> Iterable[str]: ...
    def __init_subclass__(cls) -> None: ...

```

​

​


# Swift


# 1.基础部分


# 常量和变量以及基本类型\_1

\#Swift\
\#开发语言学习

## 1. 常量和变量

常量使用`let`声明，变量使用`var`声明；

声明时可以不用指定类型(类型注解)，会根据初始化的值来推断类型。

```swift

let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

```

如果常量或者变量在声明时不初始化，则需要指定类型注解。

```swift
let a : Int

var b : String

```

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8A%E5%8D%885.52.10.png)

常量或者变量可以在声明后，再赋予初值

```swift

let a : Int
var b : String

a = 12
b = "Hello World"

```

常量一旦赋予了初值，就不可以再修改

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8A%E5%8D%885.57.49.png)

***

可选类型的变量可以不初始化默认值为nil；非可选类型的变量和常量只有在初始化后才能够使用；

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-05%20%E4%B8%8A%E5%8D%8812.39.28.png)

## 2. 整数类型

Swift提供了8，16，32和64位的有符号和无符号的整数类型。

`Int8,Int16,Int32,Int64,UInt8,UInt16,UInt32,UInt64`

### 2.1 整数范围

可以通过访问不同类型的`max`和`min`属性获取对应类型的最大值和最小值。

```swift
let minValue = UInt8.min  // minValue 为 0，是 UInt8 类型
let maxValue = UInt8.max  // maxValue 为 255，是 UInt8 类型
```

### 2.2 Int 和 UInt

Swift 提供了一个特殊的整数类型 Int，长度与当前平台的原生字长相同：

* 在32位平台上，Int 和 Int32 长度相同。
* 在64位平台上，Int 和 Int64 长度相同。

> 除非你需要特定长度的整数，一般来说使用 Int 就够了。这可以**提高代码一致性和可复用性**

Swift 也提供了一个特殊的无符号类型 UInt，长度与当前平台的原生字长相同：

* 在32位平台上，UInt 和 UInt32 长度相同。
* 在64位平台上，UInt 和 UInt64 长度相同。

> 尽量不要使用 UInt，除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况，最好使用 Int，即使你要存储的值已知是非负的。**统一使用 Int 可以提高代码的可复用性，避免不同类型数字之间的转换**，并且匹配数字的类型推断

***

## 3. 浮点数类型

Swift 提供了两种有符号浮点数类型：

* Double 表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。
* Float 表示32位浮点数。精度要求不高的话可以使用此类型。

> Double 精确度很高，至少有 15 位小数，而 Float 只有 6 位小数。选择哪个类型取决于你的代码需要处理的值的范围，在两种类型都匹配的情况下，将**优先选择 Double**。

## 4. 数值型字面量

整数字面量可以被写作：

* 一个十进制数，没有前缀
* 一个二进制数，前缀是 0b
* 一个八进制数，前缀是 0o
* 一个十六进制数，前缀是 0x

```swift
let decimalInteger = 17
let binaryInteger = 0b10001       // 二进制的17
let octalInteger = 0o21           // 八进制的17
let hexadecimalInteger = 0x11     // 十六进制的17
```

浮点字面量可以是十进制（没有前缀）或者是十六进制（前缀是 0x ）。小数点两边必须有至少一个十进制数字（或者是十六进制的数字）。十进制浮点数也可以有一个可选的指数（exponent)，通过大写或者小写的 e 来指定；十六进制浮点数必须有一个指数，通过大写或者小写的 p 来指定。

如果一个十进制数的指数为 exp，那这个数相当于基数和10^exp 的乘积：

* 1.25e2 表示 1.25 × 10^2，等于 125.0。
* 1.25e-2 表示 1.25 × 10^-2，等于 0.0125。

如果一个十六进制数的指数为 exp，那这个数相当于基数和2^exp 的乘积：

* 0xFp2 表示 15 × 2^2，等于 60.0。
* 0xFp-2 表示 15 × 2^-2，等于 3.75。

```swift

let decimalDouble = 12.1875

let exponentDouble = 1.21875e1

let hexadecimalDouble = 0xC.3p0  // ( 12 + 3 / 16 ) * 2 ^ 0 = 12.1875
```

***

## 5. Bool类型

Swift 有一个基本的布尔（Boolean）类型，叫做 Bool。布尔值指逻辑上的值，因为它们只能是真或者假。Swift 有两个布尔常量，true 和 false：

```swift
let orangesAreOrange = true
let turnipsAreDelicious = false
```

***

## 6. 数值类型转换

Swift对于数值类型非常重要的一点是：**Swift中不同的数值类型在结合使用时，不会出现隐式的转换。**

而C/C++/Objc语言中，基本的数值类型在结合前，会隐式转换为相同的类型

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8B%E5%8D%8812.43.34.png)

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8B%E5%8D%8812.44.39.png)

在不同的数值类型结合使用时，需要显式转换为相同的类型

```c++

let a : Int8 = 11

let b : Double = 14

let c = a + Double(b)

```

**Swift中，当数值类型在计算中超出了自己的范围，将会报错**

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8B%E5%8D%8812.49.20.png)

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8B%E5%8D%8812.49.50.png)

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8B%E5%8D%8812.50.12.png)

***

## 7. 类型安全和类型推断

Swift 是一个 **类型安全**（type safe） 的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个 String，你绝对不可能不小心传进去一个 Int。

由于 Swift 是类型安全的，所以它会在编译你的代码时进行 类型检查（type checks），并把不匹配的类型标记为错误。这可以让你在开发的时候尽早发现并修复错误。

**类型推断**：

Swift在声明变量或常量时，可以不指定变量或常量的类型，由初始化的值推断出变量或者常量的类型。

```swift
let a = 123        // a是Int型，整型字面值推断为Int

let b = 12.3       // b是Double型，浮点数字面值推断为Double

```

***

## 8. typealias 类型别名

```swift
typealias AudioSample = UInt16   // 声明AudioSample为UInt16的别名
```


# 常量和变量以及基本类型\_2

## 1. 元组类型

**元组**（tuples）把多个值组合成一个复合值。元组内的值可以是任意类型，并不要求是相同类型。

```swift
let http404Error = (404, "Not Found")
// http404Error 的类型是 (Int, String)，值是 (404, "Not Found")


// 可以在声明时指定其中元素的名字
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// 输出“The status code is 404”
print("The status message is \(statusMessage)")
// 输出“The status message is Not Found”


// 你可以在定义元组的时候给单个元素命名：

let http200Status = (statusCode: 200, description: "OK")

```

作为函数返回值时，元组非常有用。相比于objc，使用元组可以返回多个返回值

> 当遇到一些相关值的简单分组时，元组是很有用的。元组不适合用来创建复杂的数据结构。如果你的数据结构比较复杂，不要使用元组，用类或结构体去建模。

## 2. 可选类型

使用**可选类型**（optionals）来处理值可能缺失的情况。可选类型表示两种可能： 或者有值， 你可以解析可选类型访问这个值， 或者根本没有值(nil)。

```c++

let a : Int? = 12  // Int? 可选类型，可以赋为nil，表示没有值

a = nil

```

> Int类型和Int?可选类型并不是同一种类型了

### 2.1 nil

在Objc中, nil代表的是一个空指针，表示某个变量不指向任何对象

在Swift中, nil 不是指针——它是一个确定的值，用来表示值缺失

### 2.2 强制解析

可选类型的对象往往不能直接使用，需要转化为对应的非可选类型。

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-02%20%E4%B8%8B%E5%8D%881.57.21.png)

这里**在对象名字后加一个！将可选类型强制解析为对应的非可选类型**

```swift
let a : Int? = 11

if a != nil {
    print(a! + 1)
}

```

> 使用 ! 来获取一个不存在的可选值会导致运行时错误。使用 ! 来强制解析值之前，一定要确定可选包含一个非 nil 的值。

### 2.3 可选绑定

使用**可选绑定**（optional binding）来判断可选类型是否包含值，如果包含就把值赋给一个临时常量或者变量。

通过`while`，`if`和`guard`判断可选类型是否有值

```swift

let a : Int? = 11

// if 语句的句中（body）中才能获取到值
if let b = a {
    print(b + 1)
}


// guard 语句外且在语句后才能获取到值
guard let b = a else {
    return
}
print(b+1)
```

### 2.4 隐式解析可选类型

有时候在程序架构中，第一次被赋值之后，可以确定一个可选类型总会有值。在这种情况下，每次都要判断和解析可选值是非常低效的，因为可以确定它总会有值。

当确定一个可选类型有值后，可以将可选类型赋予给**隐式解析可选类型**。在使用**隐式解析可选类型**的对象时，会自动解析为对应的非可选类型，而不需要手动强制解析。

```swift
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感叹号来获取值

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString  // 不需要感叹号 可以自动的隐式解析
```

> **隐式解析可选类型** 必须要保证有值，否则在隐式解析时会报错。

> **隐式解析可选类型** 仍然是可选类型，只是可以自动解析

***

## 3. 错误处理

```swift

// 函数
func makeASandwich() throws {
    // 这个函数有可能抛出错误
}

do {
    try makeASandwich()
    eatASandwich()
} catch SandwichError.outOfCleanDishes {
    washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

```

## 4. 断言和先决条件

**断言**和**先决条件**是在运行时所做的检查。你可以用他们来检查在执行后续代码之前是否一个必要的条件已经被满足了。

如果**断言**或者**先决条件**中的布尔条件评估的结果为 true（真），则代码像往常一样继续执行。**如果布尔条件评估结果为 false（假），程序的当前状态是无效的，则代码执行结束，应用程序中止**。

> 断言和先决条件并不是用来处理可以恢复的或者可预期的错误。因为一个断言失败表明了程序正处于一个无效的状态，没有办法去捕获一个失败的断言。

**断言仅在调试环境运行，而先决条件则在调试环境和生产环境中运行**

### 4.1 断言

Swift标准库提供了断言函数 `assert(_:_:file:line:)` 和 `assertionFailure(_:file:line:)`

```swift

let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// 因为 age < 0，所以断言会触发


if age > 10 {
    print("You can ride the roller-coaster or the ferris wheel.")
} else if age > 0 {
    print("You can ride the ferris wheel.")
} else {
    // 代码已经检查了条件,直接触发断言失败
    assertionFailure("A person's age can't be less than zero.")
}

```

### 4.2 先决条件

先决条件同样提供了`precondition(_:_:file:line:)` 和`·preconditionFailure(_:file:line:)`

```swift
// 在一个下标的实现里...
precondition(index > 0, "Index must be greater than zero.")
```


# 2.基本运算符


# 基本运算符

## 1. 赋值运算符

与 C 语言和 Objective-C 不同，**Swift 的赋值操作并不返回任何值**。所以下面语句是无效的：

```swift

if x = y { // 此句错误，因为 x = y 并不返回任何值
    
}

let b = a += 2  // 赋值运算符没有返回值， a += 2  不会有返回值

```

## 2. 算术运算符

与 C 语言和 Objective-C 不同的是，**Swift 默认情况下不允许在数值运算中出现溢出情况**。

```swift

let a : Int = Int.max + 1 ;   // 出现溢出

```

加法运算符也可用于 String 的拼接：

```swift
"hello, " + "world"  // 等于 "hello, world"
```

### 3. 比较运算符

Swift 提供恒等（===）和不恒等（!==）这两个比较符来判断两个对象是否引用同一个对象实例。

Swift支持对元组的比较, 当然每个元素都要支持比较运算符，对每个元素按照字典顺序比较

```swift

(1, "zebra") < (2, "apple")   // true，因为 1 小于 2
(3, "apple") < (3, "bird")    // true，因为 3 等于 3，但是 apple 小于 bird
(4, "dog") == (4, "dog")      // true，因为 4 等于 4，dog 等于 dog

("blue", false) < ("purple", true) // 错误，因为 < 不能比较布尔类型

```

> Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时，你需要自己实现比较运算符。

## 4. 空合运算符

空合运算符（a ?? b）将对可选类型 a 进行空判断，如果 a 包含一个值就进行解包，否则就返回一个默认值 b。表达式 a 必须是 Optional 类型。默认值 b 的类型必须要和 a 存储值的类型保持一致。

```swift

let defaultColorName = "red"
var userDefinedColorName: String?   //默认值为 nil

var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 的值为空，所以 colorNameToUse 的值为 "red"

```

## 5. 区间运算符

Swift 提供了几种方便表达一个区间的值的区间运算符。

### 5.1

**闭区间运算符a...b**定义一个包含从 a 到 b（包括 a 和 b）的所有值的区间。a 的值不能超过 b。

```swift
for index in 1...5 {
    print("\(index) * 5 = \(index * 5)")
}
// 1 * 5 = 5
// 2 * 5 = 10
// 3 * 5 = 15
// 4 * 5 = 20
// 5 * 5 = 25
```

### 5.2 半开区间运算符

**半开区间运算符a..\<b**定义一个从 a 到 b 但不包括 b 的区间。

半开区间的实用性在于当你使用一个从 0 开始的列表（如数组）时，非常方便地从0数到列表的长度。

### 5.3 单侧区间

闭区间操作符有另一个表达形式，可以表达往一侧无限延伸的区间

可以在数组下标中使用

```swift

for name in names[2...] {
    print(name)
}
// Brian
// Jack

for name in names[...2] {
    print(name)
}
// Anna
// Alex
// Brian
```

单侧区间不止可以在下标里使用，也可以在别的情境下使用。然而，由于这种区间无限延伸的特性，请保证你在循环里有一个结束循环的分支


# 3.字符串和字符


# 字符串

`Swift`的`String`和`Character`类型提供了一种快速且兼容Unicode的方式来处理代码中的文本内容。

> `Swift` 的 `String` 类型与 `Foundation NSString` 类进行了无缝桥接。`Foundation` 还对 `String` 进行扩展使其可以访问 `NSString` 类型中定义的方法。这意味着调用那些 `NSString` 的方法，你无需进行任何类型转换。

## 1. 字符串字面值

```swift
let someString = "Hello World"

```

## 2.字符串字面量的特殊字符

* 转义字符

  ```
     \0(空字符)、\\(反斜线)、\t(水平制表符)、\n(换行符)、\r(回车符)、\"(双引号)、\'(单引号)
  ```
* Unicode 标量

  ```
    写成 \u{n}(u 为小写)，其中 n 为任意一到八位十六进制数且可用的 Unicode 位码。
  ```

```swift
let dollarSign = "\u{24}"             // $，Unicode 标量 U+0024
let blackHeart = "\u{2665}"           // ♥，Unicode 标量 U+2665
let sparklingHeart = "\u{1F496}"      // 💖，Unicode 标量 U+1F496

```

## 3. 多行字符串

`Swift`可以使用`“”“`包含多行字符串，直接回车表示换行，不用表示换行，在多行字符串中可以直接使用`"`

```swift

let str = """
      hdakshdajshdkajs\n
\u{1234}
            dasdjaskjdklasjdkals
   dsahjdkhasjkdhaskj
"""
/* 
  str值为：

      hdakshdajshdkajs

ሴ
            dasdjaskjdklasjdkals
   dsahjdkhasjkdhaskj
*/

```

## 4. 扩展字符串分隔符

将字符串放在引号`"`中并用数字符号`#`括起来。字符串中的特殊字符将会被直接包含而非转义后的效果。

```swift
let str = #"""
      hdakshdajshdkajs\n
\u{1234}
            dasdjaskjdklasjdkals
   dsahjdkhasjkdhaskj
"""#

/**

str值为：

      hdakshdajshdkajs\n
\u{1234}
            dasdjaskjdklasjdkals
   dsahjdkhasjkdhaskj

**/


let str2 = #"""
Here are three more double quotes: """
"""#

/**

str2值为：

Here are three more double quotes: """

**/

```

## 6. 初始化空的字符串

```swift
var str = ""      // 可变字符串

let str1 = String()   // 常量字符串
```

## 7. 字符串是值类型

在 `Swift` 中 `String` 类型是值类型。如果你创建了一个新的字符串，那么当其进行常量、变量赋值操作，或在函数/方法中传递时，会进行值拷贝。在前述任一情况下，都会对已有字符串值创建新副本，并对该新副本而非原始字符串进行传递或赋值操作。

在实际编译时，Swift 编译器会优化字符串的使用，使实际的复制只发生在绝对必要的情况下，这意味着你将字符串作为值类型的同时可以获得极高的性能。

## 8. 字符类型

```swift
let c : Character = "c"

let cArray : [Character] = ["c","a","t"]

let catStr = String(cArray)

for c in catStr {
    print(c)
}

```


# 字符串操作

## 1. 连接字符串

`Swift`


# 4.集合类型


# 概述

在Swift中提供了三种基本的集合类型。

* Array ： 有序的可重复的
* Set ： 无序的不可重复的
* Dictionary ： 无序的不可重复的字典

![](https://docs.swift.org/swift-book/_images/CollectionTypes_intro_2x.png)

## 集合类型的可变性

如果集合类型被赋予给常量，集合类型则不可更改，不能增加删除元素，也不能修改元素；

如果集合类型赋予给变量，则可以修改。


# Array

`Array` 是有序的，支持随机读写的集合类型

```swift
@frozen struct Array<Element>
```

![](http://pic.existorlive.cn//202206062006677.png)

## 1. 基本操作

### 1.1 初始化，构造

```swift

// 创建空的Array, 类型推断

let array1 = [Int]()

var array2 = Array<Int>()


// 创建空的Array, 指定类型

var array3 : [Int] = []

// 通过列表初始化

var array4 = [1,2,3,4,5]

// 调用init方法初始化

var array5 = Array(repeating:0, count:3)

```

### 1.2 增加元素

`+`，`append(_:)` 可以在数组尾部添加元素

`insert(_:,at:)` 在索引指定的位置插入元素

```swift

array1.append(11)
array1 += [13,12]

array1.insert(13,at:0)

```

### 1.3 删除元素

`remove(at:)` 删除索引指定位置的元素

`removeLast()` 删除数组中的最后一项

```swift
// 
array1.remove(at:5)

// Can't remove last element from an empty collection: 
array1.removeLast()
```

### 1.4 访问数组

使用下标法访问`Array`中的元素

```swift
array1[0]

array1[1] = 12

// 下标法可以使用区间运算符
array1[1...3] = [2,3,4]
```

### 1.5 遍历数组

可以直接使用`for-in`循环遍历数组

```swift
for value in array1 {
    print("\(value)")
}
```

如果同时需要每个数据项的值和索引值，可以使用 `enumerated()` 方法来进行数组遍历。`enumerated()` 返回一个由索引值和数据值组成的元组数组。

```swift

for (index, value) in shoppingList.enumerated() {
    print("Item \(String(index + 1)): \(value)")
}

```

### 1.6 数组越界问题

> 如果你试图通过越界索引来执行访问或者修改数据的操作，会引发一个运行时错误。此时可以使用索引值和数组的 count 属性进行比较来在使用该索引之前检验其是否有效。除了当 count 等于 0 时（说明这是个空数组），最大索引值一直是 count - 1，因为数组都是零起索引。

## 2. 数组自动扩容

每个数组都会预留一定数量的内存空间去保存元素。当在数组中添加元素且超出预留的数量；数组会申请一块更大的内存空间，并将元素复制到新的内存空间中。

新的内存空间是旧空间的数倍大小。指数级的增长策略意味着多次append操作的平均性能是实时的。触发内存充分配的append 操作有一定的性能消耗，但是数组的容量的逐渐增大，内存分配将会发生的更少。

如果预先知道需要存储的元素数量，可以在添加元素之前调用`reserveCapacity(_:)` 避免内存重分配。`capacity` 和 `count` 属性可以用于计算可用容量。

对于大部分元素类型，存储区域是一个连续的内存空间。对于元素类型是 class 或者 @objc protocol 的数组，存储区域可以是一个连续的内存空间或者NSArray的对象。

## 3. 修改Array 的拷贝

Array 是struct类型，也就是值类型。Array对象的每一个拷贝之间都是独立的，修改一个拷贝并不会影响其他拷贝。

如果 Array 的元素类型是值类型，那不仅是Array对象的每个拷贝是独立的，Array对象的每个拷贝对应的元素也是独立的，修改某个拷贝的元素不会影响其他拷贝的对应元素。

```swift
var numbers = [1, 2, 3, 4, 5]
var numbersCopy = numbers
numbers[0] = 100
print(numbers)
// Prints "[100, 2, 3, 4, 5]"
print(numbersCopy)
// Prints "[1, 2, 3, 4, 5]"
```

如果 Array 的元素类型是引用类型，尽管Array对象的每个拷贝是独立的，Array对象的每个拷贝对应的元素是浅拷贝，指向同一个引用类型对象。

```swift
// An integer type with reference semantics
class IntegerReference {
    var value = 10
}
var firstIntegers = [IntegerReference(), IntegerReference()]
var secondIntegers = firstIntegers

// Modifications to an instance are visible from either array
firstIntegers[0].value = 100
print(secondIntegers[0].value)
// Prints "100"

// Replacements, additions, and removals are still visible
// only in the modified array
firstIntegers[0] = IntegerReference()
print(firstIntegers[0].value)
// Prints "10"
print(secondIntegers[0].value)
// Prints "100"
```

标准库中的许多集合包括Array都使用了 **写时拷贝** 优化。 Array 的多个拷贝共享同一块存储空间，直到某一个拷贝被修改。

当Array的某个拷贝首次被修改时，会复制出一块新的独属于该拷贝的存储空间，然后再修改；之后再修改就不再有申请新的存储空间的开销了。

```swift
var numbers = [1, 2, 3, 4, 5]
var firstCopy = numbers
var secondCopy = numbers

// The storage for 'numbers' is copied here
numbers[0] = 100
numbers[1] = 200
numbers[2] = 300
// 'numbers' is [100, 200, 300, 4, 5]
// 'firstCopy' and 'secondCopy' are [1, 2, 3, 4, 5]

```

## 4. Array 和 NSArray 之间桥接

当你需要 Array 和 NSArray 对象之间相关转换，可以使用类型声明运算符(`as`)来实现桥接。桥接的前提是元素的类型必须是`class`或者`@objc protocol`或者`Foundation类型`。

有以下的示例：

`colors` 是 `Array<String>`的数组，元素类型为 `String`, `String` 是一个Foundation类型，可以桥接；

`moreColors` 是 `Array<String?>`, 元素类型为 `String?`, `String?` 本质上是枚举类型，不是`class`或者`@objc protocol`或者`Foundation类型`中任意一种，因此不可以桥接。

```swift


let colors = ["periwinkle", "rose", "moss"]
let moreColors: [String?] = ["ochre", "pine"]

let url = URL(fileURLWithPath: "names.plist")
(colors as NSArray).write(to: url, atomically: true)
// true

(moreColors as NSArray).write(to: url, atomically: true)
// error: cannot convert value of type '[String?]' to type 'NSArray'
```

#### 4.1 `class` 或者 `@objc protocol` 的桥接性能

当元素类型为 `class` 或者 `@objc protocol`, `Array` 桥接为 `NSArray`的时间复杂度和空间复杂度都是 O(1)

当元素类型为 `class` 或者 `@objc protocol`, `NSArray` 桥接为 `Array` 首先调用 `copy(with:)` 方法获得一个可变的拷贝数组，然后执行额外的Swift保存操作，将会消耗O(1)的时间复杂度。如果是 `NSMutableArray` 对象桥接为`Array` ，`copy(with:)`通常会在O(1)时间内返回同一个数组对象，否则copy的性能将是不确定的。如果`copy(with:)`返回同一个数组对象，`NSArray` 和 `Array` 共享同一块支持写实拷贝的存储空间。

#### 4.2 Foundation 中非class类型的桥接性能

当元素类型为Foundation中的非class类型, `Array` 桥接为 `NSArray`的时间复杂度和空间复杂度都是 O(n)

当元素类型是Foundation中的非class类型，`NSArray` 桥接为 `Array`会在 O(n) 的时间复杂度内将元素拷贝到一块连续存储中。


# Set

## 1. 集合类型的hash值

集合类型的元素和字典类型的key必须提供计算hash值和比较相等性的方法。即实现`Swift`标准库的`Hashable`协议。

`Swift`的基本类型包括`String`,`Int`,`Double`等都实现了`Hashable`协议

```swift
public protocol Hashable : Equatable {
    var hashValue: Int { get }


    func hash(into hasher: inout Hasher)
}

```

## 2. 初始化，构造一个集合

```swift

// Set 没有简化的初始化版本
var set1 = Set<Character>()


//  使用数组字面值创建集合
var set2 : Set<Int> = [1,2,3]

```

## 3. 插入元素

`insert(newMember:)`插入新的元素

```swift
set1.insert(11)
```

## 4. 删除元素

`remove(member:)`删除某个元素；如果集合包含元素，则删除并返回，如果不包含则返回nil

`removeAll()` 删除所有元素

```swift
array1.remove(4)

array1.removeAll()
```

## 5.遍历集合

`for-in` 循环中遍历一个集合中的所有值

```swift
for value in array1 {

}

```

## 6. 集合操作

![](https://docs.swift.org/swift-book/_images/setVennDiagram_2x.png)

* 使用 `intersection(_:)` 方法根据两个集合的交集创建一个新的集合。
* 使用 `symmetricDifference(_:)` 方法根据两个集合不相交的值创建一个新的集合。
* 使用 `union(_:)` 方法根据两个集合的所有值创建一个新的集合。
* 使用 `subtracting(_:)` 方法根据不在另一个集合中的值创建一个新的集合
* 使用“是否相等”运算符（==）来判断两个集合包含的值是否全部相同。
* 使用 `isSubset(of:)` 方法来判断一个集合中的所有值是否也被包含在另外一个集合中。
* 使用 `isSuperset(of:)` 方法来判断一个集合是否包含另一个集合中所有的值。
* 使用 `isStrictSubset(of:)` 或者 `isStrictSuperset(of:)` 方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。
* 使用 `isDisjoint(with:)` 方法来判断两个集合是否不含有相同的值（是否没有交集）


# Dictionary

元素为key-value对的集合类型

```swift
@frozen struct Dictionary<Key, Value> where Key : Hashable
```

Dictionary 是一种哈希表类型，提供对其记录的快速访问。表中的记录用 key 来标识，key 是一种 Hashable 类型。通过 key 可以获取到对应的 value，value 可以是任意类型。

## 1. 初始化，构造字典

```swift

// 初始化一个空的字典
var dic1 : [String:Int] = [:]

var dic2 = [String:Int]()

// 使用字面值初始化一个字典

var dic3 = ["He":1,"das":2]

```


# 6.函数


# 函数

## 1. 函数定义

```swift

func greet(person: String) -> String {
    let greeting = "Hello, " + person + "!"
    return greeting
}


```

`Swift` 函数定义 使用关键字`func`，后接函数名，参数列表，以及尾置返回值类型。

返回值类型为`Void`时，可省略返回值

## 2. 函数参数标签和参数名字

每个函数参数都有一个`参数标签（argument label）`以及一个`参数名称（parameter name）`。`参数标签`在调用函数的时候使用；调用的时候需要将函数的参数标签写在对应的参数前面。`参数名称`在函数的实现中使用。

默认情况下，函数参数使用参数名称来作为它们的参数标签。

```swift
// firstParameterName 既是参数名，也是参数标签
func someFunction(firstParameterName: Int, secondParameterName: Int) {
    // 在函数体内，firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值
}

// 调用时 参数标签：参数
someFunction(firstParameterName: 1, secondParameterName: 2)

```

### 2.1 指定参数标签

可以在参数名称前指定它的参数标签，中间以空格分隔：

```swift

// from 为参数标签 hometown 为参数名
func greet(person: String, from hometown: String) -> String {
    return "Hello \(person)!  Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// 打印“Hello Bill!  Glad you could visit from Cupertino.”

```

### 2.2 忽略参数标签

如果你不希望为某个参数添加一个标签，可以使用一个下划线（\_）来代替一个明确的参数标签。

```swift
// firstParameterName 忽略参数标签
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
     // 在函数体内，firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值
}

// 调用时firstParameterName 不用带参数标签
someFunction(1, secondParameterName: 2)
```

## 3. 默认参数

你可以在函数体中通过给参数赋值来为任意一个参数定义默认值（Deafult Value）。当默认值被定义后，调用这个函数时可以忽略这个参数。

不同于C++，由于参数标签的存在，不需要将没有默认行参的参数放在前面

```swift

// parameterWithDefault 设置默认参数 12
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
    // 如果你在调用时候不传第二个参数，parameterWithDefault 会值为 12 传入到函数体中。
}
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault = 6
someFunction(parameterWithoutDefault: 4) // parameterWithDefault = 12
```

## 4. 可变参数

一个`可变参数（variadic parameter）`可以接受零个或多个值。函数调用时，你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入（...）的方式来定义可变参数。

**可变参数的传入值在函数体中变为此类型的一个数组。**

例如，一个叫做 numbers 的 Double... 型可变参数，在函数体内可以当做一个叫 numbers 的 \[Double] 型的数组常量。

```swift
func arithmeticMean(_ numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
```

感觉类似于C++的`initializer_list`,需要可变参数类型一致，在使用时作为数组使用

## 5. 输入输出参数

**函数参数默认是常量**。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。如果你想要一个函数可以修改参数的值，并且想要在这些修改在函数调用结束后仍然存在，那么就应该把这个参数定义为**输入输出参数**（In-Out Parameters）。

定义一个输入输出参数时，在参数定义前加 `inout` 关键字。

你只能传递变量给输入输出参数。你不能传入常量或者字面量，因为这些量是不能被修改的。当传入的参数作为输入输出参数时，需要在参数名前加 `&` 符，表示这个值可以被函数修改。

> 输入输出参数不能有默认值，而且可变参数不能用 inout 标记。

```swift
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)

print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")

```

> inout 参数 可以理解为 C/C++ 中参数的引用传递，能够修改参数引用的值。


# 函数返回值


# 函数类型


# 7.闭包


# 闭包

**闭包**是自包含的函数代码块，可以在代码中被传递和使用。

Swift 中的闭包与 C 和 Objective-C 中的 **代码块(Block)** 以及其他一些编程语言中的 **匿名函数(Lambda)** 比较相似。

**闭包**可以捕获和存储其所在上下文中任意常量和变量的**引用**。被称为**包裹常量和变量**。

Swift 会为你管理在捕获过程中涉及到的所有内存操作。

闭包采用如下三种形式之一：

* **全局函数**是一个有名字但不会捕获任何值的闭包
* **嵌套函数**是一个有名字并可以捕获其封闭函数域内值的闭包
* **闭包表达式**是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包

***


# 闭包表达式

**闭包表达式** 是一种构建**内联闭包**的方式，是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包。

```swift
{[capture list](paramList) -> returnType in 
    // closure code
}
```

闭包表达式由**捕获列表**，**参数列表**，**返回值**，以及**闭包函数体**组成。

通常**捕获列表**可以参略，会根据函数体中使用变量或常量自动捕获；

**Example**

```swift

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

// sorted需要的闭包类型 (String,String) -> Bool
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

```

## 1. 闭包中的类型推断

根据闭包需要的类型，可以在闭包定义时省略参数类型和返回类型的定义，根据闭包参数的类型来推断

```swift
// 省略参数类型和返回类型，根据(String,String) -> Bool来推断
reversedNames = names.sorted(by: { s1, s2 in
    return s1 > s2
})
```

## 2. 单表达式闭包的隐式返回

单行表达式闭包可以通过省略 `return` 关键字来隐式返回单行表达式的结果

```swift
// 省略return，隐式返回
reversedNames = names.sorted(by: { s1, s2 in
    s1 > s2
})
```

## 3. 参数名称缩写

Swift 自动为内联闭包提供了参数名称缩写功能，你可以直接通过 `$0`，`$1`，`$2` 来顺序调用闭包的参数.

如果你在闭包表达式中使用参数名称缩写，你可以在闭包定义中省略参数列表，并且对应参数名称缩写的类型会通过函数类型进行推断。in 关键字也同样可以被省略

```swift
reversedNames = names.sorted(by: { $0 > $1 } )
```

## 4. 运算符方法

实际上还有一种更简短的方式来编写上面例子中的闭包表达式

```swift
reversedNames = names.sorted(by: >)
```

## 5. 总结

> 虽然在定义闭包表达式时，有许多简易写法，但是为了保证代码可读性，尽量还是要保证必要的代码存在。

## 不同语言中闭包实现的对比

**闭包** 是能够捕获定义上下文中的变量或常量引用的可调用对象。可以在代码中传递和调用。

不同语言中也有相似的实现：

* C++中lambda表达式
  * lambda表达式支持值捕获和引用捕获
  * 值捕获默认不可以修改，但是将`lambda`声明为`mutable`支持修改；引用捕获能够修改取决于是否为常量
  * C++中lambda表达式是值类型
  * 底层实现是重载调用运算符的匿名类的匿名对象；捕获的变量是匿名类的数据成员；函数实现就是重载调用运算符函数
* OC中的Block
  * Block表达式支持值捕获，和`__block`捕获
  * 值捕获的值不可以修改；`__block`捕获可以修改
  * Block是引用类型
  * 底层实现是匿名OC类的匿名对象，函数实现保存在匿名类中的成员函数指针中；捕获的变量是匿名类的属性
* Swift中的闭包
  * 闭包中都是引用捕获
  * 捕获的值能够修改取决于是否为常量
  * 闭包是引用类型


# 尾随闭包

如果你需要将一个很长的闭包表达式作为**最后一个参数传递给函数**，将这个闭包替换成为尾随闭包的形式很有用。

> 只有闭包表达式作为函数的最后一个参数，才可以写为尾随的形式。

```swift

// 未使用尾随闭包
reversedNames = names.sorted(by: { s1, s2 in
    s1 > s2
})

// 使用尾随闭包，参略(),参数标签
reversedNames = names.sorted { s1, s2 in
    s1 > s2
}

```


# 捕获变量或常量

闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在，闭包仍然可以在闭包函数体内引用和修改这些值

## 1. 捕获的方式是引用捕获

闭包是以**引用的方式捕获常量和变量**。捕获的变量可以在闭包中修改，并且会影响原本的变量。

```swift
var a = 1

// 引用捕获a
let function = {() in
    a += 1
}

function()
function()
print(a)        // a = 3
```

> 为了优化，如果一个值不会被闭包改变，或者在闭包创建后不会改变，Swift 可能会改为捕获并保存一份对值的拷贝。

## 2. 闭包会管理捕获变量或常量的生命周期

Swift 负责被捕获变量的所有内存管理工作.即使捕获的常量和变量的原作用域已经不存在，闭包仍然可以在闭包函数体内引用和修改这些值。

```swift

var function : (() -> Void )!


if true{
    var a = 1
    function = {() in
    a += 1
    }
}

// a 看似生命周期结束，但闭包管理了a的生命周期，仍然可以访问
function()
function()

```


# 闭包是引用类型

无论你将函数或闭包赋值给一个常量还是变量，你实际上都是将常量或变量的值设置为对应函数或闭包的引用。


# 逃逸闭包

当一个闭包作为参数传到一个函数中，但是**这个闭包在函数返回之后才被执行**，我们称**该闭包从函数中逃逸**。

当你定义接受闭包作为参数的函数时，你可以在参数名之前标注 `@escaping`，用来指明这个闭包是允许“逃逸”出这个函数的。

```swift
var completionHandlers: [() -> Void] = []

// @escaping 将在该函数返回后才调用
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}
```


# 自动闭包

**自动闭包**是一种自动创建的闭包，用于包装传递给函数作为参数的表达式。

这种闭包**不接受任何参数**，当它被调用的时候，会返回被包装在其中的表达式的值。

在声明时，需要`@autoclosure`指定自动闭包

```swift
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
```

> 过度使用 autoclosures 会让你的代码变得难以理解


# 8.枚举


# 枚举

**枚举为一组相关的值定义了一个共同的类型**

在`C`语言中，枚举只能为一组整型值分配相关联的名称。

Swift 中的枚举更加灵活，不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值（称为**原始值**），则该值的类型可以是字符串、字符，或是一个整型值或浮点数。

枚举成员可以指定任意类型的**关联值**存储到枚举成员中。你可以在一个枚举中定义一组相关的枚举成员，每一个枚举成员都可以有适当类型的关联值。

在Swift中，枚举是一种值类型。支持属性，方法，构造函数，还可以实现协议。

## 1. 定义枚举

使用`enum`关键字定义枚举

```swift

// 定义枚举可以不绑定任何值
enum CompassPoint {
    case north
    case south
    case east
    case west
}

// 可以定义在一行，使用逗号分隔
enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

// 使用enum

var directionToHead = CompassPoint.west      // 可以类型推断

var directionToHead : CompassPoint = .west   // 指定类型解释，可以省略枚举名

```

> 与`C`和 `Objective-C` 不同，`Swift` 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的 CompassPoint 例子中，north，south，east 和 west 不会被隐式地赋值为 0，1，2 和 3。相反，这些枚举成员本身就是完备的值，这些值的类型是已经明确定义好的 CompassPoint 类型。

## 2. 遍历枚举

枚举实现`CaseIterable`协议，协议提供了`allCases`属性，返回所有的枚举值

```swift

enum Beverage: CaseIterable {
    case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")


for tmpcase in Beverage.allCases {
    print(tmpcase)
}

```

## 3. 关联值

你可以定义 `Swift` 枚举来存储任意类型的关联值，如果需要的话，每个枚举成员的关联值类型可以各不相同。枚举的这种特性跟其他语言中的`可识别联合（discriminated unions）`，`标签联合（tagged unions）`，或者`变体（variants）`相似。

如下定义了枚举`HttpResponse`,

枚举值`success`绑定`Int`和`Data`型的数据，代表状态码和返回的数据

枚举值`failed`绑定`Int`和`Error`型的数据，代表状态码和错误信息

```swift

enum HttpResponse{
    case success(Int,Data)
    case fail(Int,Error)
}


// 
var respoonse = HttpResponse.success(200,Data())

response = .fail(400,Error())

```

## 4. 原始值

Swift 可以为每个枚举值设置一个固定的默认值，称为原始值。

原始值必须类型相同，可以为**字符串**、**字符**，或是一个**整型值**或**浮点数**。

```swift

enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

// 访问原始值
let c = ASCIIControlCharacter.tab.raw

```

### 4.1 原始值的隐式赋值

* 整型原始值隐式赋值

```swift

// 指定第一个枚举值的原始值，其他原始值以递增1 的方式，隐式赋值；第一个原始值为指定，默认为0
enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
```

* 字符串原始值隐式赋值

```swift
// 每个枚举成员的隐式原始值为该枚举成员的名称。
enum CompassPoint: String {
    case north, south, east, west
}
```

### 4.2 以原始值初始化枚举实例

如果在定义枚举类型的时候使用了原始值，那么将会自动获得一个初始化方法，这个方法接收一个叫做 `rawValue` 的参数。

参数类型即为原始值类型，**返回值则是枚举成员或 nil。**

```swift

//  返回值则是枚举成员或 nil
let possiblePlanet = Planet(rawValue: 7)

// 
```

### 4.3 原始值特性

> 枚举的原始值必须指定类型，同一个枚举类型的不同枚举值都是相同的类型

> 枚举值的原始值在定义时指定，且不可以修改；如果整型或字符串原始值未指定，一般会隐式的赋值

> 原始值构造器是一个可失败构造器，因为并不是每一个原始值都有与之对应的枚举成员。

## 5. 原始值和关联值的区别

* 原始值是在定义枚举时被预先填充的值。对于一个特定的枚举成员，它的原始值始终不变。
* 关联值是创建一个基于枚举成员的常量或变量时才设置的值，枚举成员的关联值可以变化。

## 6. 递归枚举

**递归枚举**是一种枚举类型，它有一个或多个枚举成员**使用该枚举类型的实例作为关联值**。

在`enum`前使用`indirect`指定枚举类型中一个或多个枚举成员使用该枚举类型的实例作为关联值。

在`case`前使用`indirect`指定当前枚举值使用枚举类型的实例作为关联值

```swift

enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

```

## 7. C/C++/Swift枚举对比

C/C++中的枚举 ：

* 是为一组名字绑定整型值（且只能绑定整型值，不能绑定浮点值，字符串或者其他类型）
* 本质上每个枚举值还是整型，是简单的基本算术类型

Swift的枚举：

* 是一种类类型，可以有属性，方法以及构造器，简而言之具有封装性
* (原始值) 可以为每个枚举值绑定一个固定的值（这个值可以是整型，浮点型和字符串）
* (关联值) 可以为每个枚举值绑定一组数据(每个枚举值的数据类型可以不一样)


# 枚举与switch语句

`swift`中的枚举不只可以关联整型数，还可以通过关联值，原始值关联更复杂的类型。

`switch`也同样不只是支持整型数，也支持更复杂的枚举

## 1. switch 和 关联值

```swift
enum HttpResponse{
    case success(Int,Data)
    case fail(Int,Error)
}


switch response{
    // 可以将关联值绑定新的变量或常量
    case let .success(statusCode,data):
          print(statusCode)
    case let .fail(statusCode,error):
          print(statusCode)
}

```

##

```swift
if case let HttpResponse.success(statusCode,data) = response {

}
```


# 9.类和结构体


# 类和结构体

`Swift`支持面向对象编程，支持自定义类型。`Swift`中的自定义类型被分为`结构体Struct`和`类Class`。

`Struct`是值类型，例如`Int`，`Float`，`Array`，`Set`，`String`，`Dictionary`等类型都是Struct类型；

`Class`是引用类型，例如 闭包表达式，`UIKit`框架的类型

## 1. 结构体和类的对比

### 1.1 相同点

* 定义储存属性和计算属性
* 定义方法用于提供功能
* 定义下标操作用于通过下标语法访问它们的值
* 定义构造器用于设置初始值
* 通过扩展以增加默认实现之外的功能
* 遵循协议以提供某种标准功能

### 1.2 不同点

* 结构体是值类型；类是引用类型
* 继承允许一个类继承另一个类的特征(继承性)
* 类型转换允许在运行时检查和解释一个类实例的类型（动态性）
* 析构器允许一个类实例释放任何其所被分配的资源 (析构器)
* 引用计数允许对一个类的多次引用 (引用计数)

> 类支持的附加功能是增加复杂性为代价的。作为一般准则优先使用Struct，必要时使用Class。 更多的比较参考[在结构体和类之间选择](https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes)

## 2. 定义Class和Struct

```swift
struct SomeStructure {
    // 在这里定义结构体
}
class SomeClass {
    // 在这里定义类
}

```

## 3. 结构体和枚举是值类型

值类型是这样一种类型，当它被赋值给一个变量、常量或者被传递给一个函数的时候，其值(即每一个成员)会被拷贝。

Swift 中所有的基本类型：整数（integer）、浮点数（floating-point number）、布尔值（boolean）、字符串（string)、数组（array）和字典（dictionary），都是值类型，其底层也是使用结构体实现的。

> 标准库定义的集合，如数组，集合，字典和字符串，都会采用**写时拷贝**机制进行优化。

![](https://docs.swift.org/swift-book/_images/sharedStateStruct_2x.png)

## 4. 类是引用类型

引用类型在赋值是不会拷贝对象的值，而是传递对象的引用（指针）。

![](https://docs.swift.org/swift-book/_images/sharedStateClass_2x.png)

### 4.1 恒等运算符

为了判定两个常量或者变量是否引用同一个类实例，Swift提出了恒等运算符

```swift

// 相同

===

// 不相同

!==


```


# 属性


# 属性

Swift中的属性分为`存储属性`和`计算属性`。

存储属性会将常量和变量存储为实例的一部分。

计算属性则是返回一个计算后的值，而非存储的值。

计算属性可以用于类，结构体和枚举；存储属性只能用于类和结构体。

## 1. 存储属性

`存储属性`定义在`class`和`struct`之中，是对象的数据成员的一部分，需要占据内存空间。

`class`和`struct`必须要提供`初始化所有存储属性`的构造器。`struct`会自动生成一个构造器；`class`则必须手动实现。

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-30%20%E4%B8%8A%E5%8D%8812.51.40.png)

```swift

struct FixedLengthRangeStruct {
    var firstValue: Int           // 变量
    let length: Int               // 常量
}

// struct 会自动生成为每个存储属性初始化的构造器
let obj1 : FixedLengthRangeStruct = FixedLengthRangeStruct(firstValue:12,length:12)

class FixedLengthRangeClass {
    var firstValue: Int           // 变量
    let length: Int               // 常量   

    init(){
        firstValue = 13
        length = 13
    }
}

let obj2 = FixedLengthRangeClass()  


```

### 1.1 定义存储属性时提供初始值

当为常量属性提供初始值时，构造器中不可以再重新赋值。

当为变量属性提供初始值时，构造器中仍可以再重新赋值

```swift

struct FixedLengthRangeStruct {
    var firstValue: Int 
    let length: Int = 13
    var strArray : [String] = []
}

// struct 会自动生成为每个存储属性初始化的构造器
let obj1 : FixedLengthRangeStruct = FixedLengthRangeStruct(firstValue:12)

class FixedLengthRangeClass {
    var firstValue: Int           // 变量
    let length: Int               // 常量   
    var strArray : [String] = []

    init(){
        firstValue = 13
        length = 13
        // 定义时提供初始值的属性不需要在构造器中再提供初始值
    }
}

let obj2 = FixedLengthRangeClass()  


```

### 1.2 延时加载存储属性

`延时加载存储属性`是指当第一次被访问的时候才会计算其初始值的属性。

```swift
struct FixedLengthRangeStruct {
    lazy var firstValue: Int = Int(15)
    let length: Int = 13
}
```

> 延时加载属性必须声明为变量

> 延时加载属性必须提供初始化器

> 延迟加载属性不能保证线程安全

`延时加载存储属性`在对象初始化之后,才会初始化。因此不可以声明为常量。同时必须在声明时提供一个初始化器。

`延时加载存储属性`适用于一些需要较大开销才能够获得属性。因此将属性的初始化时机推迟到使用时。

### 1.3 常量结构体实例的存储属性

类或者结构体实例中的常量成员在初始化之后不可以再修改。

> 常量的结构体实例中的属性,无论是常量还是变量都不可以修改

> 常量的类实例中的变量属性可以修改

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-30%20%E4%B8%8A%E5%8D%882.02.55.png)

```swift

// 结构体
struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}

let obj1 : FixedLengthRange = FixedLengthRange(firstValue:12,length:13)
obj1.firstValue = 15    // 报错

// 类
class FixedLengthRange {
    var firstValue: Int
    let length: Int
    init(){
        firstValue = 15
        length = 13
    }
}

let obj2 : FixedLengthRange = FixedLengthRange()
obj2.firstValue = 15    // 可以重新赋值

```

## 2. 计算属性

`计算属性`不同于`存储属性`，不需要存储值，不会在对象实例中占据内存。通过提供setter，getter方法，来间接获取和设置其他属性或变量的值。

`类`，`结构体`以及`枚举`都可以定义`计算属性`。

计算属性必须使用`var`声明

```swift
struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}
```

### 2.1 简化Setter和Getter声明

```swift
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            // 单行表达式可以参略return 
            Point(x: origin.x + (size.width / 2), y: origin.y + (size.height / 2))
        }
        set{
            // setter可以不用提供新值的参数名，可以使用默认名称newValue
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

```

### 2.2 只读计算属性

只有 `getter` 没有 `setter` 的计算属性叫只读计算属性。

```swift

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    // 只读计算属性可以省略get{}
    var volume: Double {
        return width * height * depth
    }
}
```

## 3. 存储属性和计算属性

* 存储属性
  * 存储属性需要存储值，占据对象的内存
  * 存储属性可以定义为常量和变量
  * 只有类和结构体才能够定义存储属性
  * 存储属性需要提供初始化器或者在构造器中初始化
  *
* 计算属性
  * 计算属性不需要存储值，但是需要提供setter和getter方法
  * 计算属性只能使用`var`声明
  * 类，结构体和枚举都可以定义计算属性
  *


# 属性观察器

`属性观察器`监控和响应属性值的变化，每次属性被设置值的时候都会调用属性观察器，即使新值和当前值相同的时候也不例外。

你可以在定义类为`存储属性`添加`属性观察器`;也可以为继承父类的`存储属性`和`计算属性`添加`属性观察器`。

```swift

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("将 totalSteps 的值设置为 \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("增加了 \(totalSteps - oldValue) 步")
            }
        }
    }
}

```

在`属性观察器`中可以使用默认参数`newValue`和`oldValue`，访问新值和原本的值

## newValue不可以修改

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-30%20%E4%B8%8A%E5%8D%885.45.17.png)

`newValue`是不可以修改的。

## 在属性观察器中访问属性不会调用到属性观察器

```swift

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("将 totalSteps 的值设置为 \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
               totalSteps = oldValue    // 可以修改新赋的值
            }
        }
    }
}

```

## 存储属性的初始化不会触发属性观察器

存储属性在定义时提供初始值或者在构造器中初始化不会触发属性观察器


# 属性包装器

属性包装器在管理属性如何存储和定义属性的代码之间添加了一个分隔层。

`属性包装器只能应用于变量`

**定义属性包装器**

需要使用`@PropertyWrapper`定义一个包装器

```swift
@propertyWrapper
struct TwelveOrLess {
    private var number: Int    
    init() { self.number = 0 }
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

```

**使用属性包装器**

在定义属性时，使用属性包装器

```swift

// 使用@TwelveOrLess修饰属性 

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
print(rectangle.height)
// 打印 "0"

rectangle.height = 10
print(rectangle.height)
// 打印 "10"

rectangle.height = 24
print(rectangle.height)
// 打印 "12"

```

`TwelveOrLess`定义了一个包装器，包装器中`number`用于保存属性的值(定义成private，阻止其他类访问)。

`wrappedvalue`计算属性用于对`number`的读写，同时在读写时添加额外的操作。

`@TwelveOrLess`修饰需要包装器的属性，对于属性的读写就是对`TwelveOrLess`中`wrappedvalues`的读写。

## 设置被包装属性的初始值

被包装属性在初始化时，会调用包装器的构造器。如果需要为包装器提供更复杂的操作，则需要添加更多的构造器。

* 如果没有为被包装属性提供初始值，则会调用包装器的默认构造器。
* 当为被包装属性提供初始值，但是需要在定义包装器时添加一个构造器。

```swift

@propertyWrapper
struct SmallNumber {
    private var maximum: Int
    private var number: Int

    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, maximum) }
    }

    init() {
        maximum = 12
        number = 0
    }
    init(wrappedValue: Int) {
        maximum = 12
        number = min(wrappedValue, maximum)
    }
    init(wrappedValue: Int, maximum: Int) {
        self.maximum = maximum
        number = min(wrappedValue, maximum)
    }
}


struct ZeroRectangle {

    // 未提供任何初始值，调用包装器的默认构造器
    @SmallNumber var height: Int

    // 提供初始值，调用init(wrappedValue: Int)构造器
    @SmallNumber var width: Int = 13

    // 可以显式调用某个构造器
    @SmallNumber(wrappedValue:13, maximum: 10) var zValue : Int
}

```

## 3. 注意点

* 被包装后的属性事实上已经不是原本的类型了，而是一个包装器结构体类型的属性
* 属性包装器本身就是一个结构体，需要定义一个`wrappedValue`属性(对于被包装属性的读写就是对`wrappedValue`属性的读写)。
* 被包装属性的初始化将会调用属性包装器的构造器。
* 属性包装器只能应用于变量

> 属性包装器是一个语法糖，被属性包装器修饰的属性实际上类型是属性包装器的结构体类型；对被包装属性的读写就转换为对包装器实例中`wrappedValue`属性的读写;而在包装器的结构体中，可以提供更多复杂的逻辑。


# 全局变量和局部变量

一般来说，全局变量和局部变量都是存储型的变量。

## 计算属性

全局变量和局部变量也可以定义为计算型的变量。

```swift

var realObj1 : String =  "dadas"

// 全局变量和局部变量可以定义为计算属性
var obj1 : String {
    set{
        realObj1 = newValue
    }
    get{
        realObj1
    }
}


```

## 属性观察器

```swift

var obj1 : String {
    willSet{
        obj1 = newValue
    }
    didSet{
        obj1
    }
}

```

**Tip**

* 全局的常量或变量都是延迟计算的，跟 `延时加载存储属性` 相似，不同的地方在于，全局的常量或变量不需要标记 `lazy` 修饰符。
* 局部范围的常量和变量从不延迟计算。


# 类属性

`实例属性`属于一个特定类型的实例；`类属性`属于整个类型，在多个该类型的实例间共享。

`类属性`既可以定义为`存储属性`也可以定义为`计算属性`

`存储的类属性`必须在定义时提供初始化器。但是`类型属性`在首次访问时才初始化，同时`类型属性`的初始化是线程安全的。

## 定义类属性

使用关键字 `static` 来定义类型属性。在为类定义计算型类型属性时，可以改用关键字 `class` 来支持子类对父类的实现进行重写.

> 类，结构体和枚举都可以定义存储型类属性

```swift

class SomeStructure {
    static var storedTypeProperty = "Some value."
    class var computedTypeProperty: Int {
        return 1
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 6
    }
}
class SomeClass : SomeStructure{
    class override var computedTypeProperty: Int {
        return 107
    }
}

```

`类型属性`可以被子类继承，子类不可以定义与父类中名字相同的`类型属性`。\
当使用`class`定义的类型计算属性，可以被子类重写。

## 2. 类型属性可以定义属性观察器


# 方法


# Shell


# Shell变量和基本类型

### 1. 变量

#### 1.1 定义变量

```shell
your_name="Tony"
```

**Tip**: 定义的变量，赋值运算符和值之间不能有空格\
​

**变量名命名规则**

* 命名只能使用英文字母，数字和下划线，首个字符不能以数字开头。
* 中间不能有空格，可以使用下划线 **\_**。
* 不能使用标点符号。
* 不能使用sh里的关键字（可用help命令查看保留关键字）

#### 1.2 使用变量

变量在使用时，需要在变量名之前加上 `$`

```shell
echo  $your_name
echo  ${your_name}
```

####

**只读变量**

关键字`readonly` 可以将变量设置为只读

```shell
readonly your_name 

# 报错  your_name: readonly variable
your_name="Stack"
```

![截屏2022-01-12 18.39.46.png](https://cdn.nlark.com/yuque/0/2022/png/22724999/1641984005951-bd23dd22-b086-4fe3-90bc-77eb7349cdcc.png#clientId=u14b73753-ecbd-4\&from=drop\&id=u585aa6bc\&margin=%5Bobject%20Object%5D\&name=%E6%88%AA%E5%B1%8F2022-01-12%2018.39.46.png\&originHeight=210\&originWidth=908\&originalType=binary\&ratio=1\&size=21015\&status=done\&style=none\&taskId=uf2214fcd-01aa-49bd-b2c6-79805c71db7)

**删除变量**

```shell
# 删除变量
unset your_name

echo $your_name
```

####

#### 1.3 变量类型

运行shell时，会同时存在三种变量：

* **1) 局部变量** 局部变量在脚本或命令中定义，仅在当前shell实例中有效，其他shell启动的程序不能访问局部变量。
* **2) 环境变量** 所有的程序，包括shell启动的程序，都能访问环境变量，有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。

```shell
# env 命令可以打印当前shell的所有环境变量
env 

# 常用环境变量
PATH    # 命令的搜索路径
SHELL   # 当前使用的shell

```

​

* **3) shell变量** shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量，有一部分是局部变量，这些变量保证了shell的正常运行

​

​

### 2. 字符串

shell脚本中的字符串可以用单引号，可以用双引号，也可以不用引号。\
​

* 单引号： 单引号之间的字符串都会原样输出，不会发生转义，不能插入变量
* 双引号： 双引号里可以有变量，可以出现转义字符

```shell
a=11
b='1231\"abc$a'

echo $b          # 1231\"abc$a  原样输出

b="1231\"abc$a" 

echo $b          # 1231"abc11   转义和字符

```

![截屏2022-01-12 19.56.33.png](https://cdn.nlark.com/yuque/0/2022/png/22724999/1641988600098-4527b91f-86ba-43f8-8c7d-933186778ac5.png#clientId=u14b73753-ecbd-4\&from=drop\&id=u56f2156f\&margin=%5Bobject%20Object%5D\&name=%E6%88%AA%E5%B1%8F2022-01-12%2019.56.33.png\&originHeight=388\&originWidth=986\&originalType=binary\&ratio=1\&size=168632\&status=done\&style=none\&taskId=ucafda472-2aff-4e0e-b53f-20fce300daa)

#### 拼接字符串

shell中的字符串可以不用任何运算符直接拼接在一起

```shell
your_name="runoob"
# 使用双引号拼接
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting  $greeting_1        # hello, runoob ! hello, runoob !

# 使用单引号拼接
greeting_2='hello, '$your_name' !'
greeting_3='hello, ${your_name} !'
echo $greeting_2  $greeting_3      # hello, runoob ! hello, ${your_name} !
```

####

#### 获取字符串长度

```shell
string="abcd"
echo ${#string} #输出 4
```

####

#### 获取字串

```shell
# 从第二个位置截取4个字符
string="runoob is a great site"
echo ${string:1:4} # 输出 unoo
```

### 3. 数组

shell仅支持一维数组，不支持多维数组。 数组通过 **圆括号** 和 **空格** 定义\
数组的定义没有类型的限制，不同类型的元素可以定义在同一个数组中

```shell
array=(1 2 3 4 5 6)

array1=(
1
2
3
4)

array2=(1 2 3 "Hello" "world" 15 "15")
```

####

#### 3.1 访问元素

使用下标法访问元素，左边从 1 开始，右边从 -1 开始；\
​

```shell
echo ${array[1]}      # 1

echo ${array[-1]}     # 6

echo ${array[100]}    # 越界访问不会报错

echo ${#array}        #  6  获取数组的长度

echo ${array[@]}      # 1 2 3 4 5 6 输出所有元素
```


# Shell函数

shell脚本可以定义函数，供其他脚本调。

### 1. 函数的定义

shell脚本使用`function`关键字和 `{}` 来定义函数。\
​

```shell
function func1() {

echo "this is func1"

}
```

* 函数的定义可以使用`function`关键字，也可以不用
* 函数不会显式的定义参数，只用一个占位用的 `()`
* 函数体定义在`{}`之间
* 函数的返回值可以通过 `return` 显式返回，如果不使用 `return` , 将使用最后一条命令的运行结果作为返回值， 返回的范围在\[0,255]

### 2. 函数的参数

在函数体中，使用 `$n` 这样的模式访问参数\
​

​

### 3. 调用函数

调用函数就像执行一条命令一样\
​

```shell
# 调用函数func1
func1 

# 调用函数并传递参数 
func1 1 2 2 3
```

​

​


# Shell基本运算符

### 1. 算数运算符

原生的shell不支持简单的数学运算，需要通过其他命令来实现。如`awk` 和 `expr`

```shell
val=`expr 2 + 2`
echo "两数之和为 : $val"
```

* 表达式和运算符之间要有空格，例如 2+2 是不对的，必须写成 2 + 2，这与我们熟悉的大多数编程语言不一样。
* 完整的表达式要被 包含，注意这个字符不是常用的单引号，在 Esc 键下边。

![截屏2022-01-13 20.14.56.png](https://cdn.nlark.com/yuque/0/2022/png/22724999/1642076204396-dec7d478-0baa-4f52-a556-9a7996dcc28e.png#clientId=u1a8f8726-6fd6-4\&from=drop\&id=u53d2a40f\&margin=%5Bobject%20Object%5D\&name=%E6%88%AA%E5%B1%8F2022-01-13%2020.14.56.png\&originHeight=726\&originWidth=1284\&originalType=binary\&ratio=1\&size=121598\&status=done\&style=none\&taskId=u551e4468-8646-4eb2-9010-643c6235b0f)

* 乘号(\*) 是shell中的关键字， 前边必须加反斜杠()才能实现乘法运算
* `[ ]` 用于bool判断

​

### 2. 数值关系运算符

关系运算符只支持数字，不支持字符串，除非字符串的值是数字\
​

![截屏2022-01-13 20.30.11.png](https://cdn.nlark.com/yuque/0/2022/png/22724999/1642077034657-5f09c3dc-60ac-4022-b392-1b45494e2610.png#clientId=u1a8f8726-6fd6-4\&from=drop\&id=u6b0ddf58\&margin=%5Bobject%20Object%5D\&name=%E6%88%AA%E5%B1%8F2022-01-13%2020.30.11.png\&originHeight=550\&originWidth=1272\&originalType=binary\&ratio=1\&size=124246\&status=done\&style=none\&taskId=u04e9240c-a338-4be0-9cb7-e248e75c076)

```shell
a=1
b=2

if [ $a -eq $b ]
then 
     echo true
else
     echo false 
fi 
```

### 3. Bool运算符

![截屏2022-01-13 20.44.51.png](https://cdn.nlark.com/yuque/0/2022/png/22724999/1642077920415-2cdc676d-c4ad-4f1b-b043-8cca64efa864.png#clientId=u1a8f8726-6fd6-4\&from=drop\&id=u534da862\&margin=%5Bobject%20Object%5D\&name=%E6%88%AA%E5%B1%8F2022-01-13%2020.44.51.png\&originHeight=306\&originWidth=1272\&originalType=binary\&ratio=1\&size=67006\&status=done\&style=none\&taskId=uc5212d65-94f2-4cf4-b3d3-77e6966f92a)

```shell
a=10
b=20

if [ $a != $b ]
then
   echo "$a != $b : a 不等于 b"
else
   echo "$a == $b: a 等于 b"
fi
if [ $a -lt 100 -a $b -gt 15 ]
then
   echo "$a 小于 100 且 $b 大于 15 : 返回 true"
else
   echo "$a 小于 100 且 $b 大于 15 : 返回 false"
fi
if [ $a -lt 100 -o $b -gt 100 ]
then
   echo "$a 小于 100 或 $b 大于 100 : 返回 true"
else
   echo "$a 小于 100 或 $b 大于 100 : 返回 false"
fi
if [ $a -lt 5 -o $b -gt 100 ]
then
   echo "$a 小于 5 或 $b 大于 100 : 返回 true"
else
   echo "$a 小于 5 或 $b 大于 100 : 返回 false"
fi


if ! [ a -eq 10 ]
then 
    echo true 
else 
    echo false
fi
```

### 4. 字符串运算符

![截屏2022-01-13 21.12.29.png](https://cdn.nlark.com/yuque/0/2022/png/22724999/1642079562231-f9623401-4223-403e-bb12-2c487ac95617.png#clientId=u1a8f8726-6fd6-4\&from=drop\&id=uab73315d\&margin=%5Bobject%20Object%5D\&name=%E6%88%AA%E5%B1%8F2022-01-13%2021.12.29.png\&originHeight=468\&originWidth=1272\&originalType=binary\&ratio=1\&size=96510\&status=done\&style=none\&taskId=u0d0eb9fa-1a15-4a84-a38a-a2fa03d96c5)

```shell
a="abc"
b="efg"

if [ $a = $b ]
then
   echo "$a = $b : a 等于 b"
else
   echo "$a = $b: a 不等于 b"
fi
if [ $a != $b ]
then
   echo "$a != $b : a 不等于 b"
else
   echo "$a != $b: a 等于 b"
fi
if [ -z $a ]
then
   echo "-z $a : 字符串长度为 0"
else
   echo "-z $a : 字符串长度不为 0"
fi
if [ -n "$a" ]
then
   echo "-n $a : 字符串长度不为 0"
else
   echo "-n $a : 字符串长度为 0"
fi
if [ $a ]
then
   echo "$a : 字符串不为空"
else
   echo "$a : 字符串为空"
fi
```

### 5. 文件运算符

![截屏2022-01-13 21.16.26.png](https://cdn.nlark.com/yuque/0/2022/png/22724999/1642079818013-3419affa-618f-4274-bf40-534104ad2660.png#clientId=u1a8f8726-6fd6-4\&from=drop\&id=uc5c0d6ec\&margin=%5Bobject%20Object%5D\&name=%E6%88%AA%E5%B1%8F2022-01-13%2021.16.26.png\&originHeight=1134\&originWidth=1682\&originalType=binary\&ratio=1\&size=275577\&status=done\&style=none\&taskId=ucf8b0ec5-90cc-4f35-a163-286d5a56c76)

```shell
file="/var/www/runoob/test.sh"
if [ -r $file ]
then
   echo "文件可读"
else
   echo "文件不可读"
fi
if [ -w $file ]
then
   echo "文件可写"
else
   echo "文件不可写"
fi
if [ -x $file ]
then
   echo "文件可执行"
else
   echo "文件不可执行"
fi
if [ -f $file ]
then
   echo "文件为普通文件"
else
   echo "文件为特殊文件"
fi
if [ -d $file ]
then
   echo "文件是个目录"
else
   echo "文件不是个目录"
fi
if [ -s $file ]
then
   echo "文件不为空"
else
   echo "文件为空"
fi
if [ -e $file ]
then
   echo "文件存在"
else
   echo "文件不存在"
fi
```


# Shell脚本的参数

shell脚本在shell中执行时，可以传递0个或多个参数\
​

```shell
sh script.sh param1 param2 ...
```

* ​

​

在脚本中，通过 `$n` 的方式获取脚本的参数

```shell
#!/bin/zsh

echo "Shell 传递参数实例！";
echo "执行的文件名：$0";
echo "第一个参数为：$1";
echo "第二个参数为：$2";
echo "第三个参数为：$3";
```

![截屏2022-01-13 18.17.03.png](https://cdn.nlark.com/yuque/0/2022/png/22724999/1642069039157-782838b1-356e-441a-bc09-e9f3782e7803.png#clientId=uc2a163a2-2560-4\&from=drop\&id=u602d0c2c\&margin=%5Bobject%20Object%5D\&name=%E6%88%AA%E5%B1%8F2022-01-13%2018.17.03.png\&originHeight=340\&originWidth=984\&originalType=binary\&ratio=1\&size=97967\&status=done\&style=none\&taskId=u1d6507d8-13bd-472a-8f08-c3e90808c88)

* ​`$0` : 执行的文件路径
* `$1` \~ `$n` : 脚本的参数
* `$#` : 参数的数量
* `$$` : 脚本运行的当前进程ID号
* `$!` : 后台运行的最后一个进程的ID号
* `$-` : 显示Shell使用的当前选项
* `$?` ： 上一条命令或脚本的退出状态（0标识没有错误，其他任何值标识有错误）

```shell
# 退出返回1 错误
exit 1
# 退出返回0 没有错误
exit 0
```

* `$*` : 以一个单字符串显示所有向脚本传递的参数。\
  如"$\*"用「"」括起来的情况、以"$1 $2 … $n"的形式输出所有参数。
* `$@` : 与$\*相同，但是使用时加引号，并在引号中返回每个参数。\
  如"$@"用「"」括起来的情况、以"$1" "$2" … "$n" 的形式输出所有参数。


# Shell流程控制

### 1. if 语句

`if` `then` `elif` `else` `fi` 等关键字构成基本的 if 语句

```shell
if [ condition ]
then 
    # code
elif [ condition ]
then 
    # code
else 
    # code
fi 
```

### 2. for 循环语句

`for` `in` `do` `done`

```shell
for item in itemArray
do 

    # code
done
```

### 3. while循环

```shell
while [ condition ]
do 

    # code

done
```

**循环读取键盘信息​**

```shell
while read param
do 
    echo "输入的参数为$param"
done
```

### 4. 无限循环

```shell
while :
do 

   #code
done 


while true
do 

   #code
done 


for (( ; ; ))
do
   #code
done
```

### 5. case...esac

`*`相当于 `default`

```swift
 # 模式
case 值 in
模式1)  #code
;;
模式2)  #code 
;;
模式3｜模式4｜模式5) #code
;;
*)  #code 
;;
esac


# 例子
echo "请输入："
read param

case $param in
1) echo "第一种模式"
;;
2) echo "第二种模式"
;;
3|4|5|6) echo "第3/4/5/6种模式"
;;
*) echo "其他模式"
;;
esac
```

### 6. break/continue


# Shell输入输出

### 1. echo

`echo` 输出变量，并自动换行

### 2. printf

格式化输出`printf format-string [arguments...]`\
​

```shell
 
printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg  
printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
printf "%-10s %-8s %-4.2f\n" 杨过 男 48.6543
printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.9876

:<<'
姓名     性别   体重kg
郭靖     男      66.12
杨过     男      48.65
郭芙     女      47.99
'
```

### 3. read

```shell
# 从标准输入读取，赋值给name
read name 
```


# Shell文件包含

### 和其他语言一样，Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。

### 1. source/. 引入其他脚本

```shell
. filename   # 注意点号(.)和文件名中间有一空格

或

source filename
```

### 2. source filename 与 sh filename 及./filename执行脚本的区别

* 当shell脚本具有可执行权限时，用sh filename与./filename执行脚本是没有区别得。./filename是因为当前目录没有在PATH中，所有”.”是用来表示当前目录的。
* sh filename 重新建立一个子shell，在子shell中执行脚本里面的语句，该子shell继承父shell的环境变量，但子shell新建的、改变的变量不会被带回父shell。
* source filename：这个命令其实只是简单地读取脚本里面的语句依次在当前shell里面执行，没有建立新的子shell。那么脚本里面所有新建、改变变量的语句都会保存在当前shell里面。

```shell
# test1.sh
#/bin/zsh

hello=11
```

![截屏2022-01-17 15.33.15.png](https://cdn.nlark.com/yuque/0/2022/png/22724999/1642404809109-10f8ea94-5e08-432f-b130-5104b92c4952.png#clientId=u11dd1342-cc5c-4\&from=drop\&id=ua97710fc\&margin=%5Bobject%20Object%5D\&name=%E6%88%AA%E5%B1%8F2022-01-17%2015.33.15.png\&originHeight=254\&originWidth=1356\&originalType=binary\&ratio=1\&size=159067\&status=done\&style=none\&taskId=u29767955-ff87-4a4a-aed3-542ddcf0968)


# iOS


# UI


# UIKit


# UIView


# UIView

> An object that manages the content for a rectangular area on the screen.\
> 管理屏幕上的一个矩形区域内容的对象

> **UIView** 是一个具体（concrete）的类，可以用于创建实例，用于显示固定的背景颜色；也可以派生它绘制更复杂（sophisticated）的内容。

## UIView 的主要功能

* **绘制和动画(*****Drawing and animation*****)**

  使用UIKit和Core Graphics在矩形区域中绘制内容\*（Views draw content in their rectangular area using UIKit or Core Graphics.）\*

  一些View属性可以以动画形式更新 *(Some view properties can be animated to new values.)*
* **布局和子视图管理（Layout and subview management)**

  视图可能会包含0个或多个子视图\*（Views may contain zero or more subviews.）\*

  视图可以调节尺寸和安置它们的子视图 *（Views can adjust the size and position of their subviews.）*

  使用Auto Layout来定义调整视图大小和重新定位视图的规则，以响应视图层次结构中的更改 *（Use Auto Layout to define the rules for resizing and repositioning your views in response to changes in the view hierarchy.）*
* **事件处理（Event handling）**

  UIView 是 UIResponder 的子类，能够响应触摸和其他事件 *（A view is a subclass of UIResponder and can respond to touches and other types of events.）*

  UIView能够安装手势识别来处理一些通用的手势\*（Views can install gesture recognizers to handle common gestures.）\*

> UIView 可以嵌入（nested）其他UIView中创建视图层级（hierarchies）；父视图（superView）可以包含多个子视图（subView），但是子视图只能有一个父视图 ；默认情况下，当子视图的可见区域超过父视图的范围，不会裁剪子视图的内容

```
// 可以修改clipsToBounds 来修改默认选项
@interface UIView(UIViewRendering)
@property(nonatomic) BOOL clipsToBounds; // When YES, content and subviews are clipped to the bounds of the view. Default is NO.
@end
```

## 视图的几何机构（UIViewGeometry）

> 每个视图的几何结构（geometry）都是通过`frame`和`bounds`属性来定义的。`frame` 定义了视图在父视图坐标系（coordinate）中的原点（origin）和大小（dimensions）。`bounds`属性 用于定义视图的内部维度（internal demensions）,并且经常用于自定义的绘制代码（used exclusively in custom draw code）`center` 属性 提供了一个更加方便的方法重新定位视图而不用直接修改frame和bounds

```
@interface UIView(UIViewGeometry) // 管理视图的大小和位置即视图的几何图形

// animatable. do not use frame if view is transformed since it will not correctly reflect the actual location of the view. use bounds + center instead.
@property(nonatomic) CGRect            frame;

// use bounds/center and not frame if non-identity transform. if bounds dimension is odd, center may be have fractional part
@property(nonatomic) CGRect            bounds;      // default bounds is zero origin, frame size. animatable
@property(nonatomic) CGPoint           center;      // center is center of frame. animatable

@end
```

## 视图的层次结构（UIViewHierarchy）

> 创建一个UIView，需要指定它的初始大小和相对于父视图的位置

```
CGRect  viewRect = CGRectMake(10, 10, 100, 100);
UIView* myView = [[UIView alloc] initWithFrame:viewRect];
```

> 通过`addSubView:` 方法可以向父视图中添加子视图，且添加在其他兄弟视图（siblings）的最上面

```
@interface UIView(UIViewHierarchy)  // 视图的层次机构

- (void)removeFromSuperview;
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index;
- (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2;

- (void)addSubview:(UIView *)view;
- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview;
- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview;

@end

```

> 创建了视图之后，可以通过设置视图&#x7684;***Auto Layout***&#x89C4;则来控制视图是如何响应其他相关层次结构的变化

## 视图的绘制周期

> 当视图第一次显示或者视图的部分内容变得可见时，系统会要求视图绘制它的内容。

> 对于包含`UIKit`和`Core Graphics`内容的视图，系统会调用视图的`drawRect:`方法\
> 当视图的内容发生变化时，你有责任去通知系统，视图需要重新绘制。\
> 需要调用`setNeedsDisplay`或者`setNeedDiaplayInRect:`。这两个方法会通知系统在下一个绘制周期中更新视图。

#### tips

> 如果使用OpenGL ES去绘制视图，就需要使用`GLKView`类替代`UIView`

## 视图的运行时交互模型（The Runtime Interaction Model For Views）

1. 用户触摸屏幕
2. 硬件将触摸事件（touch event）报告给UIKit framework
3. UIKit Framework 将触摸事件打包成一个UIEvent对象，派发给合适的UIView
4. 事件处理代码会相应这次事件，你的代码一般会
   * 改变视图的属性 （frame，bounds，alpha等）
   * 调用`setNeedsLayout`方法标记视图需要布局更新（layout update）
   * 调用`setNeedDiaplay`或者`setNeedDisplayInRect:`方法标记视图需要重新绘制
   * 通知UIViewVController更新数据源
5. 如果视图的几何结构发生变化，UIKit会遵循以下几个方法去重新绘制视图
   * 如果你配置了AutoResizing规则，UIKit会根据这些规则区调整视图
   * 如果重写了`layoutSubView`，UIKit会调用它
6. 如果视图的任何部分被标记需要重新绘制，UIKit会调用drawRect:
7. 更新的视图会被发送到图形硬件显示
8. 图形硬件将内容先是在屏幕上![The Runtime Interaction Model For Views](/files/-MjGnSP75b5aOdOUJGFp)


# drawRect

> Draws the receiver's image within the passed-in rectangle\
> 在传入的矩形区域中绘制接收者的图像

```
   - (void) drawRect:(CGRect)rect;
```

## Parameters

> **rect**\
> The portion of the view’s bounds that needs to be updated. The first time your view is drawn, this rectangle is typically the entire visible bounds of your view. However, during subsequent drawing operations, the rectangle may specify only part of your view.

> 视图边界需要更新的部分。\
> 你的视图第一次绘制的时候，矩形区域特指视图的整个可视区域。\
> 但是当随后的（subsequent）绘制操作，矩形区域只是视图的部分区域。

## Discussion

* `drawRect:`方法的默认实现什么都不做。使用UIKit 和 CoreGraphics技术的子类需要重写这个方法，并实现相应的绘制代码。但是如果仅仅是想显示特定的背景颜色或者使用底层对象（underlying layer object），没必要重写该方法
* 当这个方法调用的时候，UIKit已经配置好合适的绘制环境，你可以简单的调用任何绘制的方法和函数。尤其是，UIKit创建和配置了一个graphics context 并调整它的形状让它的原点与矩形区域的原点匹配。可以通过调用`UIGraphicsGetCurrentContext`函数获取Graphics context的引用，但是不能够建立强引用，因为它可能在两次的`drawRect:`调用之间发生改变
* 必须把绘制的内容限制在rect指定的矩形区域中
* 如果直接继承UIView，`drawRect`实现方法不需要调用父类的`drawRect`，因为UIView的`drawRect`什么都没有做；但是间接继承，必须调用父类的`drawRect`
* 在代码中不应该直接调用`drawRect`，而是调用`setNeedDisplay`和`setNeedDisplayAtRect` 去触发重新绘制

## Reference

[drawRect 苹果开发者文档](https://developer.apple.com/documentation/uikit/uiview/1622529-drawrect?language=occ)


# UITableView


# UITableView

> 在一个纵列中用一行行去显示数据的视图\
> A View that presents data using rows arranged in a single column

## Overview

> `UITableView` 在一个纵列中显示一个列表。`UITableView`是`UIScrollView`的子类，允许用户滑动列表，但是仅允许纵向滑动。列表中包含（comprise）单条记录的视图是`UITableViewCell`对象。Cell有标题，图片等内容；靠近右边也可以有附件视图（accessory view）。附件视图是披露（disclosure）下一层详细信息的指示器或者按钮。附件视图可以是系统提供的视图也可以是自定义视图。`UITableView`可以进入编辑模式,这样就可以插入，删除和重新排序列表中的行。

**UITableViewCell**

![UITableViewCell](/files/-MjGnV-e2YCc660sKAXS)

***

**UITableViewCell Hierarchy**

![UITableViewCell Hierarchy](/files/-MjGnV-g5MzNJWKryxHV)

> 如**图2 UITableViewCell Hierarchy**，一个`UITableView` 可以有\
> **backgroundView**（即其中的UIView，backgrondView默认为空，只有设置了`backgroundView`属性才会有），**contentView**（即`contentView`属性，可以添加自定义视图 ），**accessory view**（即其中的UIButton），**separatorView**（分割线默认显示，可以修改`UITableView`的`separatorStyle`隐藏分割线）

***

> `UITableView`由0个或多个章节（section）组成，每个章节由一行行组成。章节由它在`UITableView`中的索引号标示，行由它在章节中的索引号标示。每个章节可以有头部和尾部视图。

***

> `UITableView`有两种风格，`UITableViewStylePlain`和`UITableViewStyleGrouped`。当创建一个`UITableView`时，必须指定一种风格，而且不能够更改。在`UITableViewStylePlain`中，当一个section的rows有一部分可见时，section的header和footer浮动在内容顶部。plain style的tableView可以有一个section索引，作为一个bar在table的右边(例如A \~ Z)。你可以点击一个特定的标签，跳转到目标section。在 `UITableViewStyleGrouped`，所有单元格拥有一个默认的背景颜色和默认背景视图。背景视图为特定section中的所有cell提供可视分组。例如，一个group可以是一个人的名字和标题，另一个group可以是电话,电子邮件帐户等。

**UITableViewStylePlain**

![UITableViewStylePlain](/files/-MjGnV-hNWFAKOjhlujp)

**UITableViewStyleGrouped**

![UITableViewStyleGrouped](/files/-MjGnV-iaw9H5OXCX4gK)

事实上，两种style都可以有右边的索引栏。实现索引栏的效果，需要实现以下的方法：

```
@protocol UITableViewDataSource

- (nullable NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView;                               
// return list of section titles to display in section index view (e.g. "ABCD...Z#")

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index;  
// tell table which section corresponds to section title/index (e.g. "B",1))

@end

/**
 * 
 * example
 **/
 
 - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView 
{
    return @[@"1",@"2",@"3",@"4",@"5",@"6"];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index;
{
    return index;
}
 


```

***

> `UITableView` 必须有一个数据源对象和一个委托对象。数据源对象必须实现`UITableViewDataSource`协议，委托对象必须实现`UITableViewDelegate`协议。数据源对象提供信息，用于构建列表，并管理当插入，删除，重新排序操作时的数据模型。委托对象管理列表每一行的配置，选择，重排，高亮，附件视图和编辑操作。

***

> 当`UITableView`收到`setEditing:animated:`消息时，进入编辑模式。在这个模式下，每一个可见的行都会显示编辑和重排的视图。当点击这些视图时，数据源对象会收到`tableView:commitEditingStyle:forRowAtIndexPath:`消息，这样就可以修改数据模型，然后调用`deleteRowsAtIndexPaths:withRowAnimation:` 或者 `insertRowsAtIndexPaths:withRowAnimation:`提交修改。\
> 在编辑模式中，当`UITableView`的`showReorderControl`属性设置为YES，且数据源对象实现了`tableView:canMoveRowAtIndexPath:`方法，移动cell时，数据源对象就会收到`tableView:moveRowAtIndexPath:toIndexPath:`消息，这样就可以重排消息模型。

**edit mode**

![edit mode](/files/vHcihc65TxfygJarieXf)

```

// After a row has the minus or plus button invoked (based on the UITableViewCellEditingStyle for the cell), the dataSource must commit the change
// Not called for edit actions using UITableViewRowAction - the action's handler will be invoked instead
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;

// Data manipulation - reorder / moving support

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;

/**
 *
 * example
 **/

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if( UITableViewCellEditingStyleDelete == editingStyle)
    {
        [self.array[indexPath.section] removeObjectAtIndex:indexPath.row];
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }
}


- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
    NSString * str = self.array[sourceIndexPath.section][sourceIndexPath.row];
    [self.array[sourceIndexPath.section] removeObjectAtIndex:sourceIndexPath.row];

    [self.array[destinationIndexPath.section] insertObject:str atIndex:destinationIndexPath.row];

}

// 必须进入编辑模式，这些方法才有效。在这些方法中，必须实现对数据源的修改，否则会出现闪退


/**
 *
 * UITableView 自己也提供了这些方法，由于修改tableView中的行。
 * 调用这些方法前，必须对相应的数据源做修改，否则会闪退
 **/
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS(3_0);
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection NS_AVAILABLE_IOS(5_0);

- (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS(3_0);
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath NS_AVAILABLE_IOS(5_0);

```

***

> `UITableView`缓存了可见行的cell，你可以创建自定义的`UITableViewCell`。`UITableView`重写了`layoutSubviews`方法，这样仅当创建一个新的`UITableView`对象或者分配了一个新的数据源对象，都会自动调用`reloadData`方法。`reloadData`会清除当前的状态，包括当前选择的行。但是随后的`layoutSubviews`不会触发reload

#### 参考文档

[iOS UITableView 的 Plain和Grouped样式的区别](https://www.jianshu.com/p/764ed5aa46cf)


# UITableView的加载过程

> `UITableView`有一个UITableViewDelegate对象和UITableViewDataSource对象，我们通过研究这两个对象的方法的调用过程来研究UITableView的加载过程

* **首次加载和调用reloadData**

> `UITableView`的首次加载，其实就是`UITableView`调用`reloadData`之后的结果。

1. 首先会调用`numberOfSectionsInTableView:`获取section的数量
2. 然后调用`tableView:numberOfRowsInSection:`获取section中row的数量
3. 接着加载cell视图，`tableView:cellForRowAtIndexPath:`获取cell，`tableView:heightForRowAtIndexPath:`获取cell的高度。（这里仅加载需要显示的cell）
4. 最后加载section的header view和 footer view。先获取高度，`tableView: heightForHeaderInSection:`,再获取视图,`tableView:viewForHeaderInSection:`

***

* **滑动UITableView**

> 滑动`UITableView`的过程其实就是将不可见的cell加载出来的过程。因此在这个过程中，仅会与cell和header view ，footer view加载的方法。`tableView:cellForRowAtIndexPath:tableView:heightForRowAtIndexPath:tableView: heightForHeaderInSection:tableView:viewForHeaderInSection:`

***

* **插入和删除cell**

>


# 学习开发工具


# 静态分析


# OCLint

> 在ios开发过程中，我们使用Clang这个前端编译工具，将OC代码输出为抽象语法树，然后编译成LLVM的bitcode，最后再编译成machine code。OCLint就是基于Clang实现的通过检查C,C++和Objective-C代码来提高代码质量、降低错误率的静态代码分析工具，代码通过OCLint检测后，可以发现一些潜在的问题。OCLint检测依赖源代码的抽象语法树来保证精准度和效率，尽可能减少误报，同时动态加载规则到系统中进行检测

## OCLint 安装

```
brew install oclint
```

## OCLint 使用

* 首先使用 xcodebuild analyze 分析代码，在用xcpretty将分析报告解析为 compile\_commands.json

```sh

xcodebuild analyze -workspace ZLGitHubClient.xcworkspace -scheme ZLGitHubClient | tee  $analyzeDirPath"/analyze.log" | xcpretty --report json-compilation-database -o $analyzeDirPath'/compile_commands.json'

```

* 使用oclint-json-compilation-database 将 compile\_commands.json 转化为可视化的报告

```sh

oclint-json-compilation-database  -p $analyzeDirPath -- -report-type pmd -o $analyzeDirPath'/report.html'

```

## 问题

* oclint: error: `Not enough positional command line arguments specified!`

解决办法 ：

在podfile中添加

```ruby
post_install do |installer|
    installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['COMPILER_INDEX_STORE_ENABLE'] = "NO"
        end
    end
end
```

在pod工程中将COMPILER\_INDEX\_STORE\_ENABLE改为NO

* `oclint: Not enough positional command line arguments specified!`

解决办法：<https://github.com/oclint/oclint/issues/478>

## 参考文档

[OCLint的基础使用](https://www.jianshu.com/p/6868363b6072)

[OCLint官方文档](http://oclint.org/)


# infer

> infer是facebook开源的Java，C，C++以及Object-C的静态分析工具

[infer github](https://github.com/facebook/infer)


# SwiftLint

### 1. 安装及使用

\#swiftlint\
\#tool\
\#Swift

#### 1.1 安装

* homebrew
  * 不侵入原工程
  * 安装方便
  * 不便于版本控制\
    ​

```shell
# 安装当前最新版本的swiftlint
brew install swiftlint
```

​

* cocoapods
  * 通过 podfile.lock 可以控制版本
  * 修改podfile， 侵入原工程

```shell
# podfile

pod 'swiftlint'

# 执行命令
pod install 
```

```shell
# path 检查的路径
# reporter 输出报告的格式 xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, sonarqube, markdown, github-actions-logging
swiftlint lint --path <path> --reporter <reporter> 
```

​

[GitHub-SwiftLint](https://github.com/realm/SwiftLint)\
​

### 2. 配置文件 .swiftlint.yml

* `disabled_rules`: 关闭某些默认开启的规则。
* `opt_in_rules`: 一些规则是可选的。
* `only_rules`: 不可以和 `disabled_rules` 或者 `opt_in_rules` 并列。类似一个白名单，只有在这个列表中的规则才是开启的。

```yaml
disabled_rules: # 执行时排除掉的规则
  - colon
  - comma
  - control_statement
opt_in_rules: # 一些规则仅仅是可选的
  - empty_count
  - missing_docs
  # 可以通过执行如下指令来查找所有可用的规则:
  # swiftlint rules
included: # 执行 linting 时包含的路径。如果出现这个 `--path` 会被忽略。
  - Source
excluded: # 执行 linting 时忽略的路径。 优先级比 `included` 更高。
  - Carthage
  - Pods
  - Source/ExcludedFolder
  - Source/ExcludedFile.swift

# 可配置的规则可以通过这个配置文件来自定义
# 二进制规则可以设置他们的严格程度
force_cast: warning # 隐式
force_try:
  severity: warning # 显式
# 同时有警告和错误等级的规则，可以只设置它的警告等级
# 隐式
line_length: 110
# 可以通过一个数组同时进行隐式设置
type_body_length:
  - 300 # warning
  - 400 # error
# 或者也可以同时进行显式设置
file_length:
  warning: 500
  error: 1200
# 命名规则可以设置最小长度和最大程度的警告/错误
# 此外它们也可以设置排除在外的名字
type_name:
  min_length: 4 # 只是警告
  max_length: # 警告和错误
    warning: 40
    error: 50
  excluded: iPhone # 排除某个名字
identifier_name:
  min_length: # 只有最小长度
    error: 4 # 只有错误
  excluded: # 排除某些名字
    - id
    - URL
    - GlobalAPIKey
reporter: "xcode" # 报告类型 (xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, sonarqube, markdown, github-actions-logging)
```

### 3. 集成方式

![截屏2022-01-14 17.16.45.png](https://cdn.nlark.com/yuque/0/2022/png/22724999/1642151820546-636a7fc8-0c22-4497-993b-5b2c8b6487f5.png#clientId=u0978084c-3fd9-4\&from=drop\&id=u01a52711\&margin=%5Bobject%20Object%5D\&name=%E6%88%AA%E5%B1%8F2022-01-14%2017.16.45.png\&originHeight=492\&originWidth=1922\&originalType=binary\&ratio=1\&size=316887\&status=done\&style=none\&taskId=ue43cd458-186c-4392-8524-3400bb78cf1)

#### 3.1 集成至Xcode中

在build phrases中创建 run script中，执行 swiftlint\
​

* 每次编译时执行
* 直接在代码出显示warning或error
* 一般开发直接忽略warning ！

```shell
# Type a script or drag a script file from your workspace to insert its path.
echo "开始检查swiftlint安装"
if ! [ -e `which swiftlint` ] 
then 
    echo "swiftlint未安装，开始安装swiftlint"
    brew intall swiftlint
fi 

changeFilesArray=(`git diff --name-only head | grep '.swift$'`)

echo "一共修改了${#changeFilesArray[*]}文件"
echo ${changeFilesArray[*]}

swiftlint lint --path ${changeFilesArray[*]}

```

![截屏2022-01-14 16.37.32.png](https://cdn.nlark.com/yuque/0/2022/png/22724999/1642149466291-6c7d4487-caa5-4b2c-90eb-f05ecdd166f2.png#clientId=u0978084c-3fd9-4\&from=drop\&id=ue0c7b0d0\&margin=%5Bobject%20Object%5D\&name=%E6%88%AA%E5%B1%8F2022-01-14%2016.37.32.png\&originHeight=771\&originWidth=1364\&originalType=binary\&ratio=1\&size=124768\&status=done\&style=none\&taskId=ub367e967-2b2a-4005-8453-efae5b3bd1f)

#### 3.2 githook在pre-commit

[Git官方文档-githooks](https://git-scm.com/docs/githooks)\
​

[pre-commi(A framework for managing and maintaining multi-language pre-commit hooks)](https://pre-commit.com/)​

### 4. 参考文档

1. [GitHub-SwiftLint](https://github.com/realm/SwiftLint)
2. [Continuous code inspection with SwiftLint for iOS Apps](https://blogs.halodoc.io/continuous-code-inspection-ios/)
3. [Git官方文档-githooks](https://git-scm.com/docs/githooks)
4. [pre-commi(A framework for managing and maintaining multi-language pre-commit hooks)](https://pre-commit.com/)
5. [聊聊SwiftLint在团队的实践](https://www.jianshu.com/p/5aef8fc7e37b)​


# iOS构建工具


# fastlane


# fastlane

**Fastlane** 是一个基于Ruby的自动化工具。它能够自动化的打包和发布 iOS 和 Android 应用。[Fastlane官方文档](https://docs.fastlane.tools/)

![](https://docs.fastlane.tools/img/fastlane_text.png)

本文以iOS开发者视角，讲述 Fastlane 的主要作用以及使用教程。

Fastlane 可以将 证书管理，打包，签名以及发布等操作自动化，避免开发者在这些重复工作上浪费时间。还可以执行测试用例和生成应用截图。甚至还可以和[Apple Store Connect](https://https://appstoreconnect.apple.com/)交互。

* 执行测试
* 生成截图
* Beta发布
* App Store 发布


# fastlane的安装

\#fastlane

## 1. 使用ruby的包管理工具Gem 安装

**Fastlane** 是基于 **Ruby** 开发的工具，因此可以使用**Gem** 来安装

```sh
sudo gem install fastlane -NV
```

#### issue

1. ERROR: Error installing fastlane:\
   ERROR: Failed to build gem native extension.

![](https://pic.existorlive.cn/fastlane_install_issue1.png)

解决办法： 升级ruby，重新安装

## 2. 使用Homebrew安装

```sh
brew install fastlane
```

1. 使用 brew install fastlane 安装；安装位置在 `/usr/local/Cellar`

![](https://pic.existorlive.cn/fastlane_install_Step1.png)

2. 在环境变量中设置fastlane , 在`～/.bash_profile`中添加

```bash
PATH="/Library/Frameworks/Python.framework/Versions/3.7/bin:${PATH}"
PATH="/usr/local/Cellar/fastlane/2.141.0/bin:${PATH}"       # 增加fastlane路径
PATH="/usr/local/opt/openssl@1.1/bin:${PATH}"
export PATH 
export LC_ALL=en_US.UTF-8     # 设置fastlane本地化字符编码  
export LANG=en_US.UTF-8
```

[Getting started with fastlane for iOS](https://docs.fastlane.tools/getting-started/ios/setup/)


# fastlane一键打包

\#fastlane\
\#持续集成

## 1. 初始化fastlane

在项目目录下执行命令，并按照提示选择所需功能，输入应用信息以及Apple开发账号信息。

```sh
fastlane init 
```

执行完成后，项目目录下会自动生成`fastlane`文件夹，文件夹中生成两个文件`Appfile` 和 `Fastfile`

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-17%20%E4%B8%8A%E5%8D%886.05.49.png)

* **Appfile** 中包含该应用的基本信息(app id,开发者账号信息)

```ruby
app_identifier("com.zm.OCTest") # The bundle identifier of your app
apple_id("*********") # Your Apple email address

itc_team_id("*******") # App Store Connect Team ID
team_id("******") # Developer Portal Team ID

# For more information about the Appfile, see:
#     https://docs.fastlane.tools/advanced/#appfile
```

* **Fastfile** 中包含持续集成的步骤配置

一个命名的`lane`代表一个持续集成的任务，每个任务由多个步骤组成，步骤通常是已经定义好的`action`工具。

执行lane：

```sh
fastlane lanename
```

常用的action工具：

* **match** ： 证书和配置文件的管理
* **gym(build\_app)** ： 打包和签名
* **poilt(upload\_to\_app\_store)**：app store 发布

```ruby
default_platform(:ios)

# 以下是FastFile的配置示例

platform :ios do
  desc "Push a new release build to the App Store"
  lane :release do
    build_app(scheme: "OCTest")
    upload_to_app_store(skip_metadata: true, skip_screenshots: true)
  end
end

# 一个lane代表一个CI的配置，可以看到release中包括两个步骤build_app，upload_to_app_store。

# build_app，upload_to_app_store 这俩个步骤是fastlane中已经定义好的action工具

```

接下来，我们会使用`match`，`gym`，`pilot`等`fastlane`中已经定义好的`action`。查看某个action具体参数，请使用命令

```sh
fastlane action actionName
```

## 2. 使用match管理证书

**match(sync\_code\_signing)** 在github仓库中加密保存证书和描述配置文件，用于开发成员之间共享

[match官方文档](https://docs.fastlane.tools/actions/match/#match)

1. 在github上创建一个仓库用于保存证书
2. 在项目目录下执行`fastlane match init`,输入仓库地址；在fastlane目录下会生成`Matchfile`文件，包含match的配置信息

![](https://pic.existorlive.cn/fastlane_match_init.png)

3. 执行这些命令`fastlane match development`, `fastlane match adhoc`, `fastlane match enterprise`，`fastlane match appstore`，首次执行自动在`apple store connect`中创建provisioning file，证书并下载加密保存在git仓库，并上传.
4. 其他开发者就可以使用`fastlane match`命令共享github中的证书和配置文件。

![](https://pic.existorlive.cn/fastlane_match_github.png)

### match的参数

* `type`: 同步的配置文件类型: `appstore`,`adhoc`,`development`,`enterprise`,默认 `development`
* `readonly`: 默认false，如果是`true`,不会生成新的证书和描述配置文件
* `app_identifier`: 指定描述配置文件的bundle id；如果不指定则使用 AppFile 中的 app\_identifier
* `git_url`: 证书保存的github地址
* `keychain_name` : 保存证书的keychain，默认login.keychain

### 在CI中使用match

在一次CI中首先就是要同步证书和描述配置文件：

```ruby
    
# match 中需要配置参数
# 证书类型   appstore ， adhoc 还是 development
# app id  
# kaychain 证书保存的keychain
# git_url  证书保存的github远端库
lane :github_action_testFlight do

match(type: "appstore",       
      readonly: true,
      app_identifier: "com.zm.ZLGitHubClient",
      keychain_name: ENV['MATCH_KEYCHAIN_NAME'],
      keychain_password: ENV['MATCH_PASSWORD'],
      git_url: ENV['MATCH_GITHUB_URL'])

end

# ENV['MATCH_GITHUB_URL'] 是加密保存的参数
```

## 3. 使用gym打包，签名

`gym`是`fastlane`提供的用于构建，打包，签名的`action`，是`build_app`的别名。

[gym官方文档](http://docs.fastlane.tools/actions/gym/#gym)

### gym的参数

* `workspace`: 如果工程在workspace中需要指定，例如使用cocoapods的工程
* `project`: 如果工程不在workspace中指定工程名字
* `schema`： schema名
* `clean`: 是否在打包前clean工程
* `export_method`: 指定打包方式： `app-store`, `ad-hoc`, `package`, `enterprise`, `development`, `developer-id`
* `export_options`: 可以指定更详细的打包配置，可以是配置文件路径
* `skip_build_archive`: 跳过构建，打包阶段，直接签名；使用`archive_path` 作为输入
* `skip_archive`：仅构建
* `skip_codesigning`: 仅构建，打包，不签名
* `archive_path` : 读取xarchive的路径
* `output_directory`: 保存ipa的目录
* `output_name`: 保存ipa的名字

### 在CI中使用gym

```ruby
# 使用gym的参数
# 打包的工程
# 打包的方式 app-store，adhoc还是development
# 打包文件的目录

lane :github_action_testFlight do

    gym(workspace: "ZLGitHubClient.xcworkspace",
        scheme: "ZLGitHubClient",
        clean: true,
        include_symbols: true,
        include_bitcode: true,
        skip_build_archive: true,
        archive_path: "./fastlane/archive/ZLGitHubClient.xcarchive",
        output_directory: "./fastlane/ipa/TestFlight",
        output_name: "ZLGitHubClient.ipa",
        export_options: {
          method: "app-store",
          provisioningProfiles: {
            "com.zm.ZLGitHubClient" => "*** AppStore com.zm.ZLGitHubClient"
            }
            })
end
```

## 4. 使用pilot上传testfight

`pilot` 是可以与 `TestFlight` 交互的 `action`， 通过 `piolt` 你可以上传和发布新版本，添加和删除测试员。

[piolt 官方文档](http://docs.fastlane.tools/actions/pilot/#pilot)

`pilot` 在与 App store 交互之前需要双因素认证，这样就没有办法做自动化；不过苹果在 App Store Connect API 中提供Json Web Token的认证方法；具体在下面会介绍。

### pilot参数

* `api_key_path` : App Store Connect API Key JSON 文件的路径
* `api_key` : App Store Connect API Key, 通过 `app_store_connect_api_key` action 生成
* `app_identifier` : 上传版本的bundle id
* `ipa`: 上传的ipa文件路径

```ruby

# pilot 需要提供apple开发者账号信息，以及上传文件目录

lane :github_action_testFlight do
    pilot(
      username: "18351927991@163.com",
      app_identifier: "com.zm.ZLGitHubClient",
      changelog: "release to TestFlight",
      ipa: "./fastlane/ipa/TestFlight/ZLGitHubClient.ipa"
    )
end

```

## 5. 使用App Store Connect API 避免双因素认证

在早先，访问App Store Connect信息需要双因素认证。而在持续集成的过程中一般无法人机交互(例如github-action)，导致持续集成无法完成。在`WWDC18`中，苹果提出了App Store Connect API，提供另外的认证方式。`Fastlane`也对App Store Connect API提供了支持，具体查看[Using App Store Connect API](https://docs.fastlane.tools/app-store-connect-api/)

### 5.1 使用 App Store Connect

必须拥有访问权限才能访问 App Store Connect API

1. 申请访问权限

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-17%20%E4%B8%8A%E5%8D%886.53.35.png)

2. 生成API密钥

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-17%20%E4%B8%8A%E5%8D%886.57.12.png)

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2020-11-17%20%E4%B8%8A%E5%8D%886.59.03.png)

3. 密钥生成后需要关注`Issuer ID`,`密钥 ID`以及下载密钥文件(.p8); 密钥文件只能下载一次。

### 5.2 app\_store\_connect\_api\_key

`app_store_connect_api_key` 是用来为其他 `action` 生成`App Store Connect API token` 的 `action`; `match`,`pilot`以及 `deliver`等 `action` 都可以使用 `App Store Connect API token`

* `key_id`
* `issuer_id`
* `key_filePath`: p8文件的路径
* `key_content`： p8文件的内容，未编码直接提供需要将回车替换为
* `is_key_content_base64`: 是否key的内容经过base64编码
* `in_house`: 是app store还是 enterprise

```ruby

lane :release do
  api_key = app_store_connect_api_key(
    key_id: "D383SF739",
    issuer_id: "6053b7fe-68a8-4acb-89be-165aa6465141",
    key_filepath: "./AuthKey_D383SF739.p8",
    duration: 1200, # optional
    in_house: false, # optional but may be required if using match/sigh
  )
   
  # 在piolt中使用app_store_connect_api_key
  pilot(api_key: api_key)
end
```

***

## 6. 自动更新build number

如果上传TestFlight的新版本的build number，在TestFlight已经存在了，那么就会上传失败。

Fastlane 也提供了工具去自动更新build number

### 6.1 获取当前TestFlight中最新版本最新build number

`app_store_build_number` 可以返回当前已经发售或者正在测试的版本的最新build number

```ruby
currentBuildNumber = app_store_build_number(
  api_key: api_key,        # 使用app_store_connect_api_key认证
  live: false,             #  live 为 true 查询已发售的版本，false 查询测试的版本
  app_identifier: "com.zm.ZLGithubClient"
)

```

### 6.2 设置项目的build number

`increment_build_number` 可以修改项目的build number

```ruby
increment_build_number(
  build_number: currentBuildNumber + 1
)
```

## 7. 完整的CI配置

以下是使用 Github Action 作自动化打包的配置，一些关键性信息使用 Secret保存，Fastlane 通过环境变量读取.

```ruby

  desc "build one TestFlight release on github action"
  lane :github_action_testFlight do
    
    # 生成 app store connect api key
    api_key = app_store_connect_api_key(
    key_id: ENV['APPSTOREAPIKEYID'],
    issuer_id: ENV['APPSTOREAPIISSUERID'],
    key_content: ENV['APPSTOREAPIKEY'],
    duration: 1200, # optional
    in_house: false, # optional but may be required if using match/sigh
    )

    # 创建 key chain 保存 证书
    create_keychain(
      name: ENV['MATCH_KEYCHAIN_NAME'],
      password: ENV['MATCH_PASSWORD'],
      default_keychain: true,
      unlock: true,
      timeout: 3600,
      add_to_search_list: true
    ) 

    # 同步发布证书
    match(type: "appstore",
          readonly: true,
          app_identifier: "com.zm.*",
          profile_name: "*** AppStore com.zm.*",
          keychain_name: ENV['MATCH_KEYCHAIN_NAME'],
          keychain_password: ENV['MATCH_PASSWORD'],
          git_url: ENV['MATCH_GITHUB_URL'])
    
    # 更新build number
    build_num = app_store_build_number(
      app_identifier: "com.zm.ZLGitHubClient",
      live: false,
      api_key: api_key
    )
      
    increment_build_number(
      build_number: build_num + 1
    )
    
    # 构建，打包，签名
    gym(workspace: "ZLGitHubClient.xcworkspace",
        scheme: "ZLGitHubClient",
        export_method: "app-store",
        clean: true,
        include_symbols: true,
        include_bitcode: true,
        output_directory: "./fastlane/ipa/TestFlight",
        output_name: "ZLGitHubClient.ipa")
    
    # 上传 test
    pilot(
      api_key: api_key,
      app_identifier: "com.zm.ZLGitHubClient",
      changelog: "release to TestFlight",
      ipa: "./fastlane/ipa/TestFlight/ZLGitHubClient.ipa"
      )
```

## 参考文档

[iOS使用fastlane一键打包审核](https://note.youdao.com/ynoteshare1/index.html?id=467b57ae4e6f4f744948e531f54a42c3\&type=note)


# fastlane证书管理

\#fastlane\
\#证书管理

## 1. 证书同步

`fastlane match <type> --readonly` 从仓库中同步对应的证书

```sh
fastlane match development --readonly
```

## 2. 添加新设备

## 3. 证书过期

苹果证书的有效期只有一年，因此需要每年续费开发者账号并刷新证书。 当证书过期后，会自动从苹果开发者网站上移除；但是使用fastlane同步证书时，却不清楚是否时被某人移除，证书过期还是发生了错误，match会提示以下的错误，而不是执行任何对用户及其证书危险的操作。

> \[!] Your certificate 'XXXXXXXXXX.cer' is not valid, please check end date and renew it if necessary

![](https://pic.existorlive.cn/202202220123931.png)

在证书过期后，首先要删除仓库中的证书，有两种方式来处理：

* `fastlane match nuke` 自动删除证书
* 手动删除仓库中的证书

接着使用 fastlane match 创建新的证书

### 3.1 fastlane match nuke 自动删除证书

```sh
fastlane match nuke development
fastlane match nuke distribution
fastlane match nuke enterprise
```

`fastlane match nuke` 将会删除指定类型的所有证书

```sh
fastlane match appstore
fastlane match development
```

接着使用 `fastlane match` 创建指定类型的证书

### 3.2 手动删除仓库的证书

1. 在远程仓库中移除需要清除的证书

```sh
certs/distribution/XXXXXXXXXX.cer
certs/distribution/XXXXXXXXXX.p12

certs/development/XXXXXXXXXX.cer
certs/development/XXXXXXXXXX.p12
```

![](https://pic.existorlive.cn/202202220132059.png)

![](https://pic.existorlive.cn/202202220132048.png)

2. 在苹果开发者网站中删除对应的provision文件

![](https://pic.existorlive.cn/202202220135273.png)

3. 使用 `fastlane match` 创建指定类型的证书

[证书管理](https://juejin.cn/post/6844903663949840392)

[How to renew an expired certificate with Fastlane Match](https://sarunw.com/posts/how-to-renew-expired-certificate-with-fastlane-match/)


# Cocoapods


# 安装pod库-podfile

\#cocoapods\
\#依赖管理\
\#podfile

## 1. source

`source`指令指定索引库(specs)的地址

```ruby
source "https://github.com/aliyun/aliyun-specs.git"
source "https://github.com/CocoaPods/Specs.git"
```

使用source指定索引库，顺序很重要。Cocoapods 会使用第一个source中最高版本的pod，不论后面的source是否包含更高的版本。\
​

官方的source是隐式指定的。当你指定其他source时，需要显式制定官方source。\
​

## 2. Dependencies

### 2.1 pod

​

`pod` 用于指定project或者target依赖的pod库。\
​

`pod` 后面跟随pod库的名字以及可选的版本要求。\
​

```ruby
pod 'Objection', '0.9'
```

#### 2.1.1 版本控制

* '= 0.1', 指定版本为 0.1.
* '> 0.1', 指定版本高于 0.1
* '>= 0.1', 指定版本高于或等于 0.1
* '\~> 0.1.2' 指定版本高于或等于 0.1.2 ，且小于 0.2.0
* '\~> 0' 指定版本高于等于0，小于 1
* '\~> 0.1.3-beat.0' 指定版本高于等于0.1.3 小于0.2

​

`~>` 表示高于等于指定版本，但小于下一个版本。 下一个版本基于最后一个子版本号。如 '0.1.2'下一个版本为'0.2.0'，'1.3.4.5'下一个版本为'1.3.5.0' , '0' 下一个版本为 '1' ; 带 '-' 的版本号之后的部分会被忽略，'0.1.3-beat.0' 下一个版本为 '0.2.0'\
​

#### 2.1.2 pod库source配置

* 不指定pod库的地址，将从索引库中查找对应的.podspec文件，从.podspec文件中读取库地址

```ruby
pod 'QueryKit/Attribute'
```

* 指定source

```ruby
pod 'PonyDebugger', :source => 'https://github.com/CocoaPods/Specs.git'
```

* 指定git地址, 不指定 branch，tag，commit时，默认为master分支最新的commit

```ruby
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git'
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :branch => 'dev'
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :tag => '0.7.0'
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :commit => '082f8319af'
```

* 直接指定路径，developerment pod

```ruby
pod 'AFNetworking', :path => '~/Documents/AFNetworking'
```

* 通过 subspecs 安装指定的子库

```ruby
# 安装 QueryKit 的 Attribute 和 QuerySet 子库
pod 'QueryKit', :subspecs => ['Attribute', 'QuerySet']
```

* 如果某个库中没有.podspec文件，可以使用podspec指定库外的.podsepc文件

```ruby
pod 'JSONKit', :podspec => 'https://example.com/JSONKit.podspec'
```

* 通过 testspecs 可以指定测试库

```ruby
pod 'AFNetworking', :testspecs => ['UnitTests', 'SomeOtherTests']
```

* build configuration 默认情况下，pod 库将会安装在所有的build configuration下，也可以显示指定在某几个build configuration下

```ruby
pod 'PonyDebugger', :configurations => ['Debug', 'Beta']
```

###

### 2.2 target 和 abstract target

target 与 xcode 工程中的 target 对应，在 target 闭包中声明的 pod 库即对应 xcode 工程中的实际 target 所依赖的 pod库。\
target 定义了 pod 库的作用范围。\
target 之中可以包含 target，target 可以继承父 target 的pod库和其他配置

```ruby
target 'ZipApp' do
  pod 'SSZipArchive'
end
```

abstract\_target 是一个虚拟的target，不与 xcode 工程中的实际 target 对应。abstract\_target主要是 pod 库和其他一些配置的继承

```ruby
abstract_target 'Networking' do
  pod 'AlamoFire'

  # Networking App 1 , Networking App 2 继承 Networking 的 AlamoFire 库声明
  target 'Networking App 1'
  target 'Networking App 2'
end
```

在 Podfile 文件中，隐式声明了一个最外层的 abstract target

```ruby
# 最外层的隐式 abstract target 

platform :ios, '9.0'
use_modular_headers!

pod "ZMToolBase"

target 'ZMTool' do
  # 继承隐式abstract target 的 ZMToolBase
  pod "Alamofire"
end

target 'ZMTool_extension' do
  pod "CTMediator"
end
```

`inherit!` 可以指定继承的模式

* :complete 完全继承父target的所有行为
* :none 不继承
* :search\_paths 只继承search\_paths

```ruby
target 'App' do
  target 'AppTests' do
    # 只继承search_paths
    inherit! :search_paths
  end
end
```

##

## 3. target configuration

以下配置都是 target 闭包( 包括最外层的隐式abstract target )内的配置

### 3.1 platform

platform 指定支持的系统和版本号；如果未指定，则为默认值：iOS 4.3，OS X 10.6，tvOS 9.0，watchOS 2.0

```ruby
platform :ios, '4.0'
platform :ios
```

### 3.2 project

当 Podfile 文件用于多个 project 时，指定 target 所属的 project\
​

当 Podfile 文件只用于一个 project时，不需要显示指定

```ruby
target 'MyGPSApp' do
  project 'FastGPS'
  ...
end

# Same Podfile, multiple Xcodeprojects
target 'MyNotesApp' do
  project 'FastNotes'
  ...
end

project 'TestProject', 'Mac App Store' => :release, 'Test' => :debu
```

### 3.3 inhibit\_all\_warnings!

屏蔽所有来自pod库的告警\
也可以单独关闭或者打开告警屏蔽

```ruby
# 屏蔽 SSZipArchive 的告警
pod 'SSZipArchive', :inhibit_warnings => true

---- 
# 屏蔽所有告警
inhibit_all_warnings!

# 单独放开SSZipArchive 的告警
pod 'SSZipArchive', :inhibit_warnings => false
```

### 3.4 use\_frameworks!

pod 将被打包为 framework 而不是 static library。 :linkage 指定打包为静态framewok还是动态framework

```ruby
target 'MyApp' do
  # 动态framewok
  use_frameworks! :linkage => :dynamic
  pod 'AFNetworking', '~> 1.0'
end

target 'ZipApp' do
  # 静态framewok
  use_frameworks! :linkage => :static
  pod 'SSZipArchive'
end
```

### 3.5 use\_modular\_headers!

默认情况下，pod库会被打包为static library。use\_modular\_headers!可以为pod库创建modular header，这样pod库就可以作为模块被引入。\
​

```ruby
# 可以单独打开或者关闭modular header
pod 'SSZipArchive', :modular_headers => false
pod 'SSZipArchive', :modular_headers => true
```

​

## 4 Workspace

### 4.1 workspace

workspace命令 指定包含所有的project的workspace的名字

如果没有显式指定 workspace，且只有一个 project 在 Podfile 的目录下；project 的名字将作为 workspace 的名字

```ruby
workspace 'MyWorkspace'
```

### 4.2 generate\_bridge\_support!

??\
​

### 4.3 set\_arc\_compatibility\_flag!

set\_arc\_compatibility\_flag! 命令将 -fobjc-arc 添加到 OTHER\_LD\_FLAGS\
​

## 5 Hooks

Podfile 可以为 pod install 过程添加 hook；hook 是全局的，不是针对某个 target 的

### 5.1 plugin

plugin 指定在安装过程中使用的插件和插件的参数\
​

```ruby
plugin 'cocoapods-keys', :keyring => 'Eidolon'
plugin 'slather'
```

​

### 5.2 pre\_install

pre\_install 允许你在pod库下载完成之后，安装之前，执行修改

```ruby
pre_install do |installer|
  # Do something fancy!
end
```

### 5.3 pre\_integrate

pre\_integrate 允许你在工程写入磁盘之前做修改\
​

```ruby
pre_integrate do |installer|
  # perform some changes on dependencies
end
```

### 5.4 post\_install

post\_install允许你在生成Xcode project写入磁盘之前做最后的修改\
​

```ruby
post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['GCC_ENABLE_OBJC_GC'] = 'supported'
    end
  end
end
```

### 5.5 post\_integrate

post\_integrate 允许你在工程写入磁盘之后，做修改

```ruby
post_integrate do |installer|
  # some change after project write to disk
end
```

[Podfile Syntax Reference ](https://guides.cocoapods.org/syntax/podfile.html#source)​


# 创建pod库-podspec

\#cocoapods\
\#依赖管理\
\#podspec

## 创建pod库

这里以[SYDCentralPivot](https://github.com/ExistOrLive/SYDCentralPivot) 为例，阐述pod库从创建到发布的过程。

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2021-07-27%20%E4%B8%8B%E5%8D%8810.21.48.png)

### 1. pod lib

`pod lib`命令是用来创建 develop pod 库：

* `pod lib create` : 创建一个pod库
* `pod lib lint` : 验证一个pod库

### 2. 创建一个pod库

首先在项目目录下执行命令：

```ruby
pod lib create [podname]
```

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8A%E5%8D%8810.00.18.png)

经过一系列的配置(平台，开发语言，测试框架，是否创建Demo),就完成了pod库的创建。

在项目目录下，生成了Demo,源码目录以及配置文件：

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2021-02-03%20%E4%B8%8A%E5%8D%889.20.23.png)

* \*.podspec

  ```
   podspec是当前pod库的描述文件
  ```
* Example

  ```
   Demo 工程 
  ```
* pod库目录 (SYDCentralPivot)

  ```
  pod库源码目录
  ```
* LICENSE

  ```
   开源许可证
  ```
* README.md

### 3. 打开Demo 工程

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8A%E5%8D%8810.50.20.png)

在`podfile`中可以看到Demo工程以相对路径的方式引入了`SYDCentralPivot`库。此时，`SYDCentralPivot`属于 `Development Pod`,在 `Development Pods`目录下，而不是在`Pods` 目录下。

#### `Development Pod` 和 `普通 Pod` 的 区别

`Development Pod` 可以直接修改源文件，并在主工程中编译运行。而 `普通 Pod` 是不可以的

### 4. 在pod库中添加或者修改源码

直接将需要的源文件拖到 `SYDCentralPivot/Class`文件夹下； 在 `podspec`文件的配置中，源文件都在该目录下

```ruby
s.source_files = 'SYDCentralPivot/Classes/**/*'
```

执行`pod install`后，源文件就会出现在 `Development Pods`目录下：

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8A%E5%8D%8811.04.55.png)

在 Demo 工程中，调用pod库的代码

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8A%E5%8D%8811.08.47.png)

### 5. podspec文件

`podspec` 文件是对pod库命名，地址，版本，源文件，资源文件以及依赖的具体描述。

* version

  ```
   pod库通过tag控制版本，
   当前 `podspec`文件中的version应该与最新的tag保持一致 
  ```
* summary，description

  ```
    pod 库的描述
  ```
* homepage

  ```
    远端库主页地址
  ```
* source

  ```
    远端仓库地址
  ```
* source\_files

  ```
     源文件路径
  ```
* resource\_bundles

  ```
     资源文件路径
  ```
* public\_header\_files

  ```
     公共头文件路径
  ```
* dependency

  ```
      对于其他pod库的依赖
  ```
* frameworks

  ```
      依赖的系统框架
  ```
* vendored\_frameworks

  ```
       依赖的非系统框架
  ```
* libraries

  ```
       依赖的系统库
  ```
* vendored\_libraries

  ```
       依赖的非系统的静态库
    
  ```

```ruby
Pod::Spec.new do |s|

  s.name             = 'SYDCentralPivot'
  s.version          = '1.1.0'
  s.summary          = 'A Simple Factory and Router for UIViewController,ServiceModel and some other object'

# This description is used to generate tags and improve search results.
#   * Think: What does it do? Why did you write it? What is the focus?
#   * Try to keep it short, snappy and to the point.
#   * Write the description between the DESC delimiters below.
#   * Finally, don't worry about the indent, CocoaPods strips it!

  s.description      = 'Factory and Router'

  s.homepage         = 'https://github.com/ExistOrLive/SYDCentralPivot'
  # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'ExistOrLive' => '2068531506@qq.com' }
  s.source           = { :git => 'https://github.com/ExistOrLive/SYDCentralPivot.git', :tag => s.version.to_s }
  # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'

  s.ios.deployment_target = '9.0'

  s.source_files = 'SYDCentralPivot/Classes/**/*'
  
  # s.resource_bundles = {
  #   'SYDCentralPivot' => ['SYDCentralPivot/Assets/*.png']
  # }

  # s.public_header_files = 'Pod/Classes/**/*.h'
  # s.frameworks = 'UIKit', 'MapKit'
  # s.dependency 'AFNetworking', '~> 2.3'
end

```

在修改了`podspec`文件后，记得使用`pod lib lint` 和 `pod spec lint`验证修改是否有效。

### 6. 添加资源文件

如果希望在Pod库中添加资源文件(图片，音频以及其他一些配置文件等)，就需要用到配置`resource_bundles`

```ruby
s.resource_bundles = {
    'SYDCentralPivot' => ['SYDCentralPivot/Assets/*.png']
}
```

而在代码中，并不能够通过**MainBundle**访问这些资源文件，因为这些文件并不在主工程下，而是在主工程依赖的库中。

```swift
// 通过pod库中定义的类获取pod库对应的bundle
let bundle = Bundle(for: ZLServiceManager.self)

// 在通过bundle获取文件内容或者路径
let configPath = bundle.path(forResource: "ZLServiceConfig", ofType: "plist")
```

### 5. 将pod库推送到远程

这里推送到Github，首先在Github上创建一个仓库，将仓库克隆到本地，将pod库复制到本地的仓库中。

* 修改 `podspec`文件，

  ```
    homepage 应该设置为远端库主页地址
    source 设置为远端库地址
    version 与 tag 保持一致
  ```
* 提交到远端库

  ```sh
  git add .
  git commit -m “xxx”
  git remote add origin 远程代码仓库地址
  git push origin master 或者 git push -u origin master（一般第一次提交用）
  git tag 版本号/git tag -a 版本号 -m “version 版本号”（注：这里的版本号必须和podspec里写的版本号一致）
  git tag 查看版本号是否提交成功
  git push - -tags
  ```
* 可以直接使用地址依赖pod库

  ```ruby
  pod 'SYDCentralPivot', :git => 'https://github.com/ExistOrLive/SYDCentralPivot.git'
  ```

### 6. 将podspec推送公共pod repo 或者 私有pod repo中

当把`podspec`文件推送到公共pod repo后，你就可以通过 `pod search` 搜索该pod以及直接使用名字安装pod库

#### 6.1 推送到公共pod repo

`pod trunk [COMMAND]` 是用来和 `Cocoapods API` 交互的命令.

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%883.13.00.png)

* 首次推送pod，需要注册账号

![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%883.16.43.png)

* `pod trunk push [PATH]`

  `pod trunk push` 将本地开发的pod库的`podspec`文件推送公共Cocoapods库中，即[Cocoapods Specs](https://github.com/CocoaPods/Specs)

  在推送之前，pod 还会验证 `podspec` 文件是够有效。如果不通过的话，就无法推送上去

  ![](https://pic.existorlive.cn/%E6%88%AA%E5%B1%8F2021-02-02%20%E4%B8%8B%E5%8D%883.27.39.png)

  因此在推送之前必须使用 `pod lib lint` 或则 `pod spec lint` 去验证 `podspec` 文件

  后期pod库更新版本后，同样使用 `pod trunk push` 推送。

#### 6.2 推送到私有pod repo

### 7. 创建 subspec

`subspec` 需要指定子库的源文件和头文件

```ruby
 s.subspec 'Core' do |core| 
    core.ios.deployment_target = '9.0'
    core.source_files = 'SYDCentralPivot/Classes/Core/*'
    core.public_header_files = 'SYDCentralPivot/Classes/Core/*.h'
  end
```

### 参考文档

[podspec 语法](https://guides.cocoapods.org/syntax/podspec.html)

[制作Cocoapods库](https://www.cnblogs.com/strengthen/p/10639115.html)

[使用pod lib创建pod库](https://www.cnblogs.com/strengthen/p/10639183.html)

[iOS组建化—私有库](https://blog.csdn.net/sacrifice123/article/details/83958405)


# 开源库


# Tool


# Swiftlint源码学习

\#swiftlint\
\#开源库

[Swiftlint](https://github.com/realm/SwiftLint) 是统一Swift代码风格的工具，本身就是使用Swift语言编写。Swiftlint 使用 [Swift\_Package\_Manager](https://github.com/ExistOrLive/DocumentForLearning/blob/master/开发环境搭建/Swift开发环境/Swift_Package_Manager/Swift_Package_Manager.md)来组织源代码，可以直接用 Xcode打开。

![](https://pic.existorlive.cn//202309070142316.png)

## 1. 项目结构

### 1.1 Package.swift

**产物**

* swiftlint 可执行文件
* SwiftLintFramework 库

**依赖**

* [swift-argument-parser](https://github.com/apple/swift-argument-parser.git)\
  swift命令行的脚手架工具，用于使用swift快速创建命令行工具
* [SwiftSyntax](https://github.com/apple/swift-syntax.git)\
  允许 Swift 工具解析、检查、生成和转换 Swift 源代码。
* [SourceKitten](https://github.com/jpsim/SourceKitten.git)\
  用于和SourceKit进行交互的工具
* [Yams](https://github.com/jpsim/Yams.git)\
  解析yaml格式的工具
* [SwiftyTextTable](https://github.com/scottrhoyt/SwiftyTextTable.git)\
  生成文本格式表格的工具

关于Swift Package Manager 请参考

[Using the Package Manager](https://www.swift.org/getting-started/#using-the-package-manager)

[Package Manager](https://www.swift.org/package-manager/)

[PackageDescription](https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html)

### 1.2 代码结构

```sh
- swiftlint
   - main 
   - Commands                  // 命令的定义 
   - Extensions
   - Helpers 
      - LintOrAnalyzeCommand   // 对主流程的封装(查找swift文件，读取规则的配置，调用rule的检查方法)

- SwiftLintFramework
   - Rules                     // rule的定义
   - Documentation
   - Reporters                
   - Protocols
   - Models
   - Helpers
   - Extensions
```

## 2. SwiftLint的lint流程

```swift
// 1. 命令的解析 
// 2. 查询出所有的Swift文件，及文件对应的Swiftlint规格及配置
// 3. 利用规则扫描swift文件
// 4. 输出表格 

swiflint lint  

```

1. 命令的解析

```swift
// ParsableCommand.run()
Lint.Run()
```

```swift
struct LintOrAnalyzeOptions {
    let mode: LintOrAnalyzeMode
    let paths: [String]
    let useSTDIN: Bool
    let configurationFiles: [String]
    let strict: Bool
    let lenient: Bool
    let forceExclude: Bool
    let useExcludingByPrefix: Bool
    let useScriptInputFiles: Bool
    let benchmark: Bool
    let reporter: String?
    let quiet: Bool
    let cachePath: String?
    let ignoreCache: Bool
    let enableAllRules: Bool
    let autocorrect: Bool
    let format: Bool
    let compilerLogPath: String?
    let compileCommands: String?
    let inProcessSourcekit: Bool
} 
```

2. 查询出所有的Swift文件，及文件对应的Swiftlint配置及规则， 返回 `[CollectedLinter]`

```swift
LintOrAnalyzeCommand.run(_ options: LintOrAnalyzeOptions)
```

```swift
public struct CollectedLinter {
    /// The file to lint with this linter.
    public let file: SwiftLintFile
    private let rules: [Rule]
    private let cache: LinterCache?
    private let configuration: Configuration
    private let compilerArguments: [String]
}
```

3. 利用规则扫描swift文件, 返回 `[StyleViolation]`

```swift
Rules.validate(file: SwiftLintFile) -> [StyleViolation]
```

```swift
public struct StyleViolation: CustomStringConvertible, Equatable, Codable {
    /// The identifier of the rule that generated this violation.
    public let ruleIdentifier: String

    /// The description of the rule that generated this violation.
    public let ruleDescription: String

    /// The name of the rule that generated this violation.
    public let ruleName: String

    /// The severity of this violation.
    public private(set) var severity: ViolationSeverity

    /// The location of this violation.
    public private(set) var location: Location

    /// The justification for this violation.
    public let reason: String

    /// A printable description for this violation.
    public var description: String {
        return XcodeReporter.generateForSingleViolation(self)
    }
```

4. 按照指定格式输出表格

```swift
Reporter.report(violations: [StyleViolation], realtimeCondition: Bool)
```

## 3. Rule

```swift
/// An executable value that can identify issues (violations) in Swift source code.
public protocol Rule {

    static var description: RuleDescription { get }
    
    var configurationDescription: String { get }
    
    init()
    
    init(configuration: Any) throws

    func validate(file: SwiftLintFile, compilerArguments: [String]) -> [StyleViolation]

    func validate(file: SwiftLintFile) -> [StyleViolation]

    func isEqualTo(_ rule: Rule) -> Bool

    func collectInfo(for file: SwiftLintFile, into storage: RuleStorage, compilerArguments: [String])

    /// Executes the rule on a file after collecting file info for all files and returns any violations to the rule's
    /// expectations.
    ///
    /// - note: This function is called by the linter and is always implemented in extensions.
    ///
    /// - parameter file:              The file for which to execute the rule.
    /// - parameter storage:           The storage object containing all collected info.
    /// - parameter compilerArguments: The compiler arguments needed to compile this file.
    ///
    /// - returns: All style violations to the rule's expectations.
    func validate(file: SwiftLintFile, using storage: RuleStorage, compilerArguments: [String]) -> [StyleViolation]
}
```

### 3.1 SourceKitFreeRule

不需要AST抽象语法树，直接通过文本查询或正则匹配实现的Rule

以 [LeadingWhitespaceRule](https://realm.github.io/SwiftLint/leading_whitespace.html) 为例：

```swift
public struct LeadingWhitespaceRule: CorrectableRule, ConfigurationProviderRule, SourceKitFreeRule {

    public func validate(file: SwiftLintFile) -> [StyleViolation] {
        let countOfLeadingWhitespace = file.contents.countOfLeadingCharacters(in: .whitespacesAndNewlines)
        if countOfLeadingWhitespace == 0 {
            return []
        }

        let reason = "File shouldn't start with whitespace: " +
                     "currently starts with \(countOfLeadingWhitespace) whitespace characters"

        return [StyleViolation(ruleDescription: Self.description,
                               severity: configuration.severity,
                               location: Location(file: file.path, line: 1),
                               reason: reason)]
    }
} 

 // 检查文件开头的空格数量 
    public func countOfLeadingCharacters(in characterSet: CharacterSet) -> Int {
        let characterSet = characterSet.bridge()
        var count = 0
        for char in utf16 {
            if !characterSet.characterIsMember(char) {
                break
            }
            count += 1
        }
        return count
    }
```

### 3.2 ASTRule

需要抽象语法树配合的Rule, 抽象语法树通过sourcekit来获取

以 [ArrayInitRule](https://realm.github.io/SwiftLint/array_init.html) 为例：

```swift
import Foundation

let array = [1,2,3]

let array2 = array.map { return $0 }
```

通过 `sourcekitten structure --file main.swift`获取AST

```json
{
  "key.diagnostic_stage" : "source.diagnostic.stage.swift.parse",
  "key.length" : 74,
  "key.offset" : 0,
  "key.substructure" : [
    {
      "key.accessibility" : "source.lang.swift.accessibility.internal",
      "key.kind" : "source.lang.swift.decl.var.global",
      "key.length" : 19,
      "key.name" : "array",
      "key.namelength" : 5,
      "key.nameoffset" : 23,
      "key.offset" : 19
    },
    {
      "key.bodylength" : 5,
      "key.bodyoffset" : 32,
      "key.elements" : [
        {
          "key.kind" : "source.lang.swift.structure.elem.expr",
          "key.length" : 1,
          "key.offset" : 32
        },
        {
          "key.kind" : "source.lang.swift.structure.elem.expr",
          "key.length" : 1,
          "key.offset" : 34
        },
        {
          "key.kind" : "source.lang.swift.structure.elem.expr",
          "key.length" : 1,
          "key.offset" : 36
        }
      ],
      "key.kind" : "source.lang.swift.expr.array",
      "key.length" : 7,
      "key.namelength" : 0,
      "key.nameoffset" : 0,
      "key.offset" : 31
    },
    {
      "key.accessibility" : "source.lang.swift.accessibility.internal",
      "key.kind" : "source.lang.swift.decl.var.global",
      "key.length" : 29,
      "key.name" : "array2",
      "key.namelength" : 6,
      "key.nameoffset" : 44,
      "key.offset" : 40
    },
    {
      "key.bodylength" : 4,
      "key.bodyoffset" : 64,
      "key.kind" : "source.lang.swift.expr.call",
      "key.length" : 16,
      "key.name" : "array.map",
      "key.namelength" : 9,
      "key.nameoffset" : 53,
      "key.offset" : 53,
      "key.substructure" : [
        {
          "key.bodylength" : 4,
          "key.bodyoffset" : 64,
          "key.kind" : "source.lang.swift.expr.closure",
          "key.length" : 6,
          "key.namelength" : 0,
          "key.nameoffset" : 0,
          "key.offset" : 63,
          "key.substructure" : [
            {
              "key.bodylength" : 4,
              "key.bodyoffset" : 64,
              "key.kind" : "source.lang.swift.stmt.brace",
              "key.length" : 6,
              "key.namelength" : 0,
              "key.nameoffset" : 0,
              "key.offset" : 63
            }
          ]
        }
      ]
    }
}
```

通过 `sourcekitten syntax --file main.swift`获取Token list

```json
[
  {
    "length" : 6,
    "offset" : 0,
    "type" : "source.lang.swift.syntaxtype.keyword"
  },
  {
    "length" : 10,
    "offset" : 7,
    "type" : "source.lang.swift.syntaxtype.identifier"
  },
  {
    "length" : 3,
    "offset" : 19,
    "type" : "source.lang.swift.syntaxtype.keyword"
  },
  {
    "length" : 5,
    "offset" : 23,
    "type" : "source.lang.swift.syntaxtype.identifier"
  },
  {
    "length" : 1,
    "offset" : 32,
    "type" : "source.lang.swift.syntaxtype.number"
  },
  {
    "length" : 1,
    "offset" : 34,
    "type" : "source.lang.swift.syntaxtype.number"
  },
  {
    "length" : 1,
    "offset" : 36,
    "type" : "source.lang.swift.syntaxtype.number"
  },
  {
    "length" : 3,
    "offset" : 40,
    "type" : "source.lang.swift.syntaxtype.keyword"
  },
  {
    "length" : 6,
    "offset" : 44,
    "type" : "source.lang.swift.syntaxtype.identifier"
  },
  {
    "length" : 5,
    "offset" : 53,
    "type" : "source.lang.swift.syntaxtype.identifier"
  },
  {
    "length" : 3,
    "offset" : 59,
    "type" : "source.lang.swift.syntaxtype.identifier"
  },
  {
    "length" : 6,
    "offset" : 65,
    "type" : "source.lang.swift.syntaxtype.keyword"
  },
  {
    "length" : 2,
    "offset" : 72,
    "type" : "source.lang.swift.syntaxtype.identifier"
  }
]
```

```swift

   // 结合抽象语法树进行分析 
  public func validate(file: SwiftLintFile, kind: SwiftExpressionKind,
                         dictionary: SourceKittenDictionary) -> [StyleViolation] {

        guard kind == .call, let name = dictionary.name, name.hasSuffix(".map"),
            let bodyOffset = dictionary.bodyOffset,
            let bodyLength = dictionary.bodyLength,
            let bodyRange = dictionary.bodyByteRange,
            let nameOffset = dictionary.nameOffset,
            let nameLength = dictionary.nameLength,
            let offset = dictionary.offset else {
                return []
        }

        let tokens = file.syntaxMap.tokens(inByteRange: bodyRange).filter { token in
            guard let kind = token.kind else {
                return false
            }

            return !SyntaxKind.commentKinds.contains(kind)
        }

        guard let firstToken = tokens.first,
            case let nameEndPosition = nameOffset + nameLength,
            isClosureParameter(firstToken: firstToken, nameEndPosition: nameEndPosition, file: file),
            isShortParameterStyleViolation(file: file, tokens: tokens) ||
            isParameterStyleViolation(file: file, dictionary: dictionary, tokens: tokens),
            let lastToken = tokens.last,
            case let bodyEndPosition = bodyOffset + bodyLength,
            !containsTrailingContent(lastToken: lastToken, bodyEndPosition: bodyEndPosition, file: file),
            !containsLeadingContent(tokens: tokens, bodyStartPosition: bodyOffset, file: file) else {
                return []
        }

        return [
            StyleViolation(ruleDescription: Self.description,
                           severity: configuration.severity,
                           location: Location(file: file, byteOffset: offset))
        ]
    }
```

### 3.3 AnalyzerRule

需要抽象语法树和编译日志配合的Rule, 抽象语法树通过sourcekit来获取

以 [UnusedImportRule](https://realm.github.io/SwiftLint/unused_import.html) 为例：

```swift
import UIKit
import WebKit

class TestObject: NSObject {
    var a = 11
}
```

```json
[
  {
    "length" : 6,
    "offset" : 0,
    "type" : "source.lang.swift.syntaxtype.keyword"
  },
  {
    "length" : 5,
    "offset" : 7,
    "type" : "source.lang.swift.syntaxtype.identifier"
  },
  {
    "length" : 6,
    "offset" : 13,
    "type" : "source.lang.swift.syntaxtype.keyword"
  },
  {
    "length" : 6,
    "offset" : 20,
    "type" : "source.lang.swift.syntaxtype.identifier"
  },
  {
    "length" : 5,
    "offset" : 28,
    "type" : "source.lang.swift.syntaxtype.keyword"
  },
  {
    "length" : 10,
    "offset" : 34,
    "type" : "source.lang.swift.syntaxtype.identifier"
  },
  {
    "length" : 8,
    "offset" : 46,
    "type" : "source.lang.swift.syntaxtype.typeidentifier"
  },
  {
    "length" : 3,
    "offset" : 61,
    "type" : "source.lang.swift.syntaxtype.keyword"
  },
  {
    "length" : 1,
    "offset" : 65,
    "type" : "source.lang.swift.syntaxtype.identifier"
  },
  {
    "length" : 2,
    "offset" : 69,
    "type" : "source.lang.swift.syntaxtype.number"
  }
]
```

```swift
    func getImportsAndUSRFragments(compilerArguments: [String]) -> (imports: Set<String>, usrFragments: Set<String>) {
        var imports = Set<String>()
        var usrFragments = Set<String>()
        var nextIsModuleImport = false
        for token in syntaxMap.tokens {
            guard let tokenKind = token.kind else {
                continue
            }
            if tokenKind == .keyword, contents(for: token) == "import" {
                nextIsModuleImport = true
                continue
            }
            // 过滤掉没有模块信息的token 
            if SyntaxKind.kindsWithoutModuleInfo.contains(tokenKind) {
                continue
            }
            // 获取当前token对应的抽象语法树 
            let cursorInfoRequest = Request.cursorInfo(file: path!, offset: token.offset,
                                                       arguments: compilerArguments)
            guard let cursorInfo = (try? cursorInfoRequest.sendIfNotDisabled()).map(SourceKittenDictionary.init) else {
                queuedPrintError("Could not get cursor info")
                continue
            }
            if nextIsModuleImport {
                 // 通过语法树并获取当前文件的所有import 
                if let importedModule = cursorInfo.moduleName,
                    cursorInfo.kind == "source.lang.swift.ref.module" {
                    imports.insert(importedModule)
                    nextIsModuleImport = false
                    continue
                } else {
                    nextIsModuleImport = false
                }
            }
            // 获取使用的import 
            appendUsedImports(cursorInfo: cursorInfo, usrFragments: &usrFragments)
        }

        return (imports: imports, usrFragments: usrFragments)
    }


  func appendUsedImports(cursorInfo: SourceKittenDictionary, usrFragments: inout Set<String>) {
        if let rootModuleName = cursorInfo.moduleName?.split(separator: ".").first.map(String.init) {
            usrFragments.insert(rootModuleName)
            if rootModuleName == moduleToLog, let filePath = path, let usr = cursorInfo.value["key.usr"] as? String {
                queuedPrintError(
                    "[SWIFTLINT_LOG_MODULE_USAGE] \(rootModuleName) referenced by USR '\(usr)' in file '\(filePath)'"
                )
            }
        }
    }
```

## 4. SourceKit相关的工具

SourceKit 是一套工具集，使得大多数 Swift 源代码层面的操作特性得以支持，例如Xcode中的源代码解析、语法高亮、排版、自动补全、跨语言头文件生成等等。 [SourceKitten](https://github.com/jpsim/SourceKitten.git) 是与 SourceKit 后台进程进行交互的工具 ，基于 [SourceKitten](https://github.com/jpsim/SourceKitten.git) 还有很多开源工具

* [Jazzy](https://github.com/realm/Jazzy): 为Swift/OC生成文档
* [Sourcery](https://github.com/krzysztofzablocki/Sourcery): 生成模版代码
* [SwiftyMocky](https://github.com/MakeAWishFoundation/SwiftyMocky): 生成Mock代码的框架
* [SourceKittenDaemon](https://github.com/terhechte/SourceKittenDaemon): 为任何文本编辑器提供swift代码补全
* [SourceDocs](https://github.com/eneko/SourceDocs): 从源码注释中生成Markdown文档的命令行工具
* [Cuckoo](https://github.com/Brightify/Cuckoo): Swift Mock 框架
* [IBAnalyzer](https://github.com/fastred/IBAnalyzer): 无需运行代码或单元测试发现xib或storyboard相关的问题

## 参考文档

[Swiftlint Github](https://github.com/realm/SwiftLint)

[Using the Package Manager](https://www.swift.org/getting-started/#using-the-package-manager)

[SourceKit](https://academy.realm.io/posts/appbuilders-jp-simard-sourcekit/)


# 利用Swiftlint自定义规则实现自动化code review

\#swiftlint\
\#Swift\
\#开源库\
SwiftLint 是一个用于检测 Swift 代码风格和一致性的开源工具。它能够自动化实现代码规范、保证代码风格的统一性，避免因为代码风格不同带来的维护问题，同时也能够提供一些代码质量上的反馈，帮助程序员编写更加规范、优雅的 Swift 代码。本文参考 [Release 0.52.4: Lid Switch · realm/SwiftLint](https://github.com/realm/SwiftLint/releases/tag/0.52.4)

![](https://pic.existorlive.cn//202309070112391.png)

### 1. Swiftlint Lint的流程

```sh
swiftlint lint 
```

![](https://pic.existorlive.cn//202309070114495.jpg)

### 2. 2. SwiftLint Lint 规则

**2.1 规则类型**

* **SourceKitFreeRule** ： 不需要AST抽象语法树，直接通过文本查询或正则匹配实现的Rule\
  以 [LeadingWhitespaceRule](https://realm.github.io/SwiftLint/leading_whitespace.html) 为例：
* **ASTRule**： 需要抽象语法树配合的Rule, 抽象语法树通过sourcekit来获取\
  以 [IdentifierNameRule](https://realm.github.io/SwiftLint/identifier_name.html) 为例
* **SwiftSyntaxRule**： 基于swift-syntax分析Swift源码的规则\
  以 [ArrayInitRule](https://realm.github.io/SwiftLint/array_init.html) 为例：
* **AnalyzerRule**： 需要抽象语法树和编译日志配合的Rule, 抽象语法树通过sourcekit来获取\
  以 [UnusedImportRule](https://realm.github.io/SwiftLint/unused_import.html) 为例：![](https://pic.existorlive.cn//202309070117944.jpg)

**2.2 SwiftSyntaxRule**

**SwiftSyntaxRule** 继承于 Rule 协议，在扩展中实现了 `func validate(file:)` 方法，执行了对语法树的遍历，在遍历过程中调用了 SwiftSyntaxRule 声明的三个方法

```swift
/// A SwiftLint Rule backed by SwiftSyntax that does not use SourceKit requests.
public protocol SwiftSyntaxRule: SourceKitFreeRule {
    /// 返回语法遍历器
    func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor

    /// 创建违规对象
    func makeViolation(file: SwiftLintFile, violation: ReasonedRuleViolation) -> StyleViolation

    /// 预处理语法树 
    func preprocess(file: SwiftLintFile) -> SourceFileSyntax?
}

public extension SwiftSyntaxRule {
  func validate(file: SwiftLintFile) -> [StyleViolation] {
        guard let syntaxTree = preprocess(file: file) else {
            return []
        }

        return makeVisitor(file: file)
            .walk(tree: syntaxTree, handler: \.violations)
            .sorted()
            .map { makeViolation(file: file, violation: $0) }
   }
}
```

**ViolationsSyntaxVisitor** 是语法树遍历器，每当访问某个语法节点时，都会调用**ViolationsSyntaxVisitor**的访问方法；不同的规则会定义不同的语法树遍历器，在语法树遍历器中实现对规则的检查； 以 **ArrayInitRule** 为例，派生 **ViolationsSyntaxVisitor** 重写 `func visitPost(_:)` 方法实现了规则检查的逻辑；

```swift
extension ArrayInitRule {
    private final class Visitor: ViolationsSyntaxVisitor {
        override func visitPost(_ node: FunctionCallExprSyntax) {
            guard let memberAccess = node.calledExpression.as(MemberAccessExprSyntax.self),
                  memberAccess.name.text == "map",
                  let (closureParam, closureStatement) = node.singleClosure(),
                  closureStatement.returnsInput(closureParam)
            else {
                return
            }

            violations.append(memberAccess.name.positionAfterSkippingLeadingTrivia)
        }
    }
}
```

**2.3 ViolationsSyntaxVisitor / SyntaxVisitor**

```swift
/// A SwiftSyntax `SyntaxVisitor` that produces absolute positions where violations should be reported.
open class ViolationsSyntaxVisitor: SyntaxVisitor {
    /// 收集的违规对象
    public var violations: [ReasonedRuleViolation] = []
    /// 需要跳过的声明语法节点
    open var skippableDeclarations: [DeclSyntaxProtocol.Type] { [] }
    /// 执行语法树遍历，并返回违规对象 
    func walk<T, SyntaxType: SyntaxProtocol>(tree: SyntaxType, handler: (Self) -> T) -> T 
}

/// Swift-Syntax 中定义的语法树遍历器 
open class SyntaxVisitor {

  public let viewMode: SyntaxTreeViewMode
     
  /// 执行对传入语法树的遍历
  public func walk<SyntaxType: SyntaxProtocol>(_ node: SyntaxType)
 
  /// 访问某个语法节点前调用
  open func visit(_ node: T) -> SyntaxVisitorContinueKind 
  
  /// 访问语法节点之后调用 
  open func visitPost(_ node: T) 
}
```

实现一个基于 Swift-Syntax 的自定义规则，最主要的工作就是重写 `SyntaxVisitor 的 visit(_:)` 和 `visitPost(_ :)` 方法，通过传入的语法节点对象（`Syntax`）获取需要的信息，执行规则检查；

在正式编写自定义规则之前，还需要了解 Swift-Syntax 中定义的各种语法节点类型。

### 3. Swift-Syntax

[swift-syntax](https://github.com/apple/swift-syntax) 是苹果官方提供的，用于 解析、检查、生成和转换 Swift 源代码的Swift库。本文参考 [GitHub - apple/swift-syntax at swift-DEVELOPMENT-SNAPSHOT-2023-07-04-a](https://github.com/apple/swift-syntax/tree/swift-DEVELOPMENT-SNAPSHOT-2023-07-04-a)

**3.1 Swift-Syntax 语法节点类图**

![](https://pic.existorlive.cn//202309070124842.jpg)

**3.2 Syntax**

**Syntax** 表示一个通用的语法节点，也就是访问方法 `visit` 的入参。**Syntax** 提供了语法节点的通用操作，如获取子节点，父节点；查询节点类型等

```swift
/// A Syntax node represents a tree of nodes with tokens at the leaves.
/// Each node has accessors for its known children, and allows efficient
/// iteration over the children through its `children` property.
public struct Syntax: SyntaxProtocol, SyntaxHashable {
  let data: SyntaxData
  
   public var _syntaxNode: Syntax {
    return self
  }
  
  /// 节点类型 
  public var kind: SyntaxKind {
    return raw.kind
  }
  
  /// 字节点 
  func children(viewMode: SyntaxTreeViewMode) -> SyntaxChildren {
    return SyntaxChildren(_syntaxNode, viewMode: viewMode)
  }
  
  /// 父节点 
 var parent: Syntax? {
    return data.parent.map(Syntax.init(_:))
  }
}
```

**3.3 节点类型**

**SyntaxKind** 中定义了 274 种语法节点类型，并为不同语法节点类型定义了类，如 `VariableDeclSyntax` , `ArrayElementSyntax`，类中定义了特定语法节点的特有方法。

```swift
/// 语法节点类型
public enum SyntaxKind {
  case token
  case accessPathComponent
  case accessPath
  case accessesEffect
  case accessorBlock
  case accessorDecl
  case accessorEffectSpecifiers
  case accessorInitEffects
  case accessorList
  case accessorParameter
  case actorDecl
  case arrayElementList
  case arrayElement
  case arrayExpr
  case arrayType
  case arrowExpr
  case asExpr
  case assignmentExpr
  case associatedtypeDecl
  case attributeList
  case attribute
  case attributedType
  case availabilityArgument
  case availabilityCondition
  ......
}
```

```swift
/// 变量声明 语法 
public struct VariableDeclSyntax: DeclSyntaxProtocol, SyntaxHashable { 

/// 获取 属性 @objc
public var attributes: AttributeListSyntax? 

/// 获取修饰符 public lazy 等 
public var modifiers: ModifierListSyntax?

.... 
}
```

**3.4 Swift AST Explorer**

在 [Swift AST Explorer](https://swift-ast-explorer.com/) 工具中，可以直观地看到源码的语法树，辅助我们开发自定义的Swiftlint 规则

![](https://pic.existorlive.cn//202309070128442.png)

### 4. 编写自定义规则

以代码中经常使用的数组下标访问代码为例，在我司推荐使用安全访问方法 `array[safe: index]`，这个方法可以规避数组越界导致的闪退问题 ; 针对这种情况，我们定义规则如下：\
在未使用数组的安全下标访问方法将会告警；

```swift
import SwiftSyntax

struct DuSafeSubscriptRule:ConfigurationProviderRule, SwiftSyntaxRule {

    var configuration = SeverityConfiguration<Self>(.warning)

    static let description = RuleDescription(
        identifier: "Du_Safe_Subscript_Rule",
        name: "Safe Subscript Rule",
        description: "Swift中通过subscript访问数组元素，都要使用[safe:]",
        kind: .lint,
        nonTriggeringExamples: [
            Example("array[safe:1]")
        ],
        triggeringExamples: [
            Example("array[1]\n")
        ]
    )

    func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
        Visitor(viewMode: .sourceAccurate)
    }
}
```

通过 [Swift AST Explorer](https://swift-ast-explorer.com/) 工具，可以看到数组下标访问的节点类型为 **SubscriptExpr**，这样我们就需要重写 **SubscriptExprSyntax** 相关的 `visit` 方法。![](https://pic.existorlive.cn//202309070203992.png)

在 `func visitPost(_ node: SubscriptExprSyntax)` 方法中，获取下标表达式的参数列表，如果参数中不包含 **safe:** 则不符合规则

```swift
final class Visitor: ViolationsSyntaxVisitor { 
   
override func visit(_ node: SubscriptExprSyntax) -> SyntaxVisitorContinueKind {
   return .visitChildren
}


override func visitPost(_ node: SubscriptExprSyntax) {
     var hasSafe = false
           
     if node.argumentList.count > 1 || node.argumentList.isEmpty {
         return
     } else if let firstArgument = node.argumentList.first {
         if firstArgument.label?.text == "safe" && firstArgument.colon?.text == ":" {
             /// 使用了safe
             hasSafe = true
         }
     }
     
     if !hasSafe {
     /// 未使用 safe， 则添加违规信息
         violations.append(node.position)
     }
}
  
}
```

在Xcode中集成后：

![](https://pic.existorlive.cn//202309070133156.png)

### 5. 自动化Code Review

1. 根据质量左移原则，在代码编译阶段的就进行代码扫描，尽早发现代码问题是最好的方式；在Xcode中集成Swiftlint检查工具，在开发阶段做检查和修改
2. 开发Swiftlint自定义规则，对开发中常用的CR规则做检查
3. 开发阶段做弱规则检查
4. 在发起PR后触发CI构建，构建过程中做强规则检查，避免不符合规则的代码被提交![](https://pic.existorlive.cn//202309070136437.jpg)

### 参考文档

[Swiftlint源码学习](/kai-yuan-ku/tool/swiftlint-yuan-ma-xue-xi)

[GitHub - realm/SwiftLint: A tool to enforce Swift style and conventions.](https://github.com/realm/SwiftLint)

[GitHub - apple/swift-syntax: A set of Swift libraries for parsing, inspecting, generating, and trans](https://github.com/apple/swift-syntax)


