计算机技术相关知识与操作

有关计算机技术的相关内容。

综合

软件源代码集合

1
http://www.columbia.edu/kermit/archive.html

学习资料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
https://cn.dolphin-emu.org/
http://ebadillo_computacion.tripod.com/
https://www.examcoo.com/index/ku
https://github.com/greyireland/algorithm-pattern/
https://oi-wiki.org/
https://github.com/neolee/wop-ecnu-pub/blob/master/HOWTO.md
https://github.com/cucygh/fe-material
https://github.com/JacksonTian/fks
https://github.com/oldratlee/translations
https://github.com/ahangchen/How-to-Be-A-Programmer-CN
https://github.com/euphrat1ca/security_w1k1
https://github.com/prakhar1989/awesome-courses
https://github.com/No-Github/1earn
https://www.mooc.cn/
https://www.icourse163.org/
https://www.imooc.com/
https://www.waitsun.com/
https://refactoringguru.cn/
https://www.felix021.com/blog/
http://www.dxzy163.com/
https://hoochanlon.github.io/fq-book/#/abc/3vm
https://www.cnblogs.com/hustskyking/p/hosts-modify.html
https://gitee.com/wanzheng_96/Modules-Learn/tree/master

iOS逆向

1
http://www.iosre.com/

DOS文件配置

1
https://www.cubic.org/docs/configuring.htm

各校课程共享计划

北京大学

1
2
3
4
https://github.com/martinwu42/project-hover/tree/legacy/project-hover
https://github.com/tongtzeho/PKUCourse
https://github.com/lib-pku/libpku
https://lib-pku.github.io/

清华大学

1
2
https://github.com/PKUanonym/REKCARC-TSC-UHT
https://rekcarc-tsc-uht.readthedocs.io/en/latest/

浙江大学

1
2
3
https://github.com/QSCTech/zju-icicles
https://qsctech.github.io/zju-icicles/
https://github.com/LeadroyaL/ZJU_ISEE_Project

东南大学

1
https://github.com/zjdx1998/seucourseshare

中国科学技术大学

1
2
3
https://github.com/ustcwpz/USTC-CS-Courses-Resource
https://github.com/USTC-Resource/USTC-Course
https://ustc-resource.github.io/USTC-Course/

上海交通大学

1
https://github.com/c-hj/SJTU-Courses

中山大学

1
https://github.com/sysuexam/SYSU-Exam

南京大学

1
https://github.com/idealclover/NJU-Review-Materials

郑州大学

1
https://github.com/CooperNiu/ZZU-Courses-Resource

华南师范大学

1
https://www.yuque.com/0xffff.one/cs-learning

广东工业大学

1
2
https://github.com/brenner8023/gdut-course
https://brenner8023.github.io/gdut-course/

北京林业大学

1
https://github.com/bljx/BFU-leaf

山东科技大学

1
https://github.com/deepwzh/sdust-examination-materials

数据结构

学习笔记

算法分析

基础知识

算法分析的主要任务是分析时间复杂度和空间复杂度。

算法可以没有输入,但应该要有输出。

使用的存储结构会影响算法的复杂度。

常见模型

最大子序列问题。

习题

判断题
1
The Fibonacci number sequence {F​N} is defined as: F0=0, F​1​=1, F​N​​=F​N−1+F​N−2, N=2, 3, .... The time complexity of the function which calculates FN recursively is Θ(N!). (F)

时间复杂度:(3/2)^N,空间复杂度:求FN,需要从F0到FN的值,需要O(N)。

选择题
1
2
3
4
5
6
7
8
9
10
The recurrent equations for the time complexities of programs P1 and P2 are:

P1: T(1)=1,T(N)=T(N/3)+1
P2: T(1)=1,T(N)=3T(N/3)+1

Then the correct conclusion about their time complexities is: (B)
A.they are both O(logN)
B.O(logN) for P1, O(N) for P2
C.they are both O(N)
D.O(logN) for P1, O(NlogN) for P2

P1的加号左边经过logN次递归等式计算恒等于1,而右边每次递归等式都加1,经过logN次计算最后为logN,两边取大是logN;
P2加号的左边经过logN次递归等式计算是N,右边每次递归等式都加1,经过logN次计算最后为logN,两边取大总体为N。

1
2
3
4
5
6
To merge two singly linked ascending lists, both with N nodes, into one singly linked ascending list, the minimum possible number of comparisons is: (B)

A.1
B.N
C.2N
D.NlogN

至少遍历完一个链表。

表/栈/队列

基本知识

若代表队列的数组大小为Size,那么只能存放Size-1个元素。

常见模型

后缀表达式(遇到当前运算符优先级低于或等于栈顶时,弹出栈顶运算符;对于括号,在左括号入栈后,未遇到右括号前都不会出栈)。

基础知识

总是可以用一维数组表示一棵树。

在任何二叉树中,空指针的数目永远比非空指针多。

普通树转二叉树

左儿子,右兄弟(每个点的左儿子是它的第一个儿子,右儿子是它从左往右数的第一个兄弟)。

也可以在所有兄弟结点之间加一连线,对每个结点除保留与其第一个儿子的连线外,去掉该结点与其它孩子的连线。

普通树的后序遍历等于该二叉树的中序遍历。

线索二叉树

对于左/右孩子为空的节点来说,若左孩子为空,将其指向中序遍历的前驱;若右孩子为空,将其指向中序遍历的后继。

不可有松散的线索,因此必须要有头节点head。中序遍历的第一个节点的左孩子指向头节点head,最后一个节点的右节点指向NULL。

二叉查找树

基本知识

左孩子中的所有数比它自身小,右孩子中的所有数比它自身大。

同一级的所有节点,从左到右按升序排列。

中序遍历可以获得一个升序序列。

若二叉查找树为完全二叉树,则最小值必定在叶节点(因为是完全二叉树,必然有最左边),中间值必定在根节点或左子树,但最大值不一定都在叶节点(可能没有最右边)。

假设4,5,6这三个数字都在二叉查找树中,且4和6在同一级,那么5不一定是它们的父亲,只需4和6不在一个子树就可以。

1
2
3
4
5
   5
/ \
3 7
/ \ / \
2 4 6 8

删除节点时,若为树叶可直接删除。若有一个孩子则让父节点绕过要删除的节点,让其孩子指向要删除节点的子节点。若有两个孩子,取右子树中的最小数据以取代要删除的节点,然后再删除右子树中该最小数据,也可以是左子树中的最大数据。

判定树

二叉查找判定树描述了查找一个数的过程。以{1,2,3,4,5,6,7,8,9,10}为例,每次折半比较,故根节点为5。若小于5,则落入左孩子,左孩子的范围为{1,2,3,4},否则落入右孩子,范围为{6,7,8,9,10}。若无法整除则取整。

1
2
3
4
5
6
7
8
9
10
11
12
13
         5
4/ \6
/ \
/ \
/ \
/ \
2 8
1/ \3 7/ \9
/ \ / \
1 3 6 9
\4 \7 \10
\ \ \
4 7 10

习题

判断题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
There exists a binary tree with 2016 nodes in total, and with 16 nodes having only one child. (F)

全满二叉树,设高度为h,则节点总数为2^h-1,最后一层的节点数为2^(h-1)
2^h-1>=2016,h的最小值为11,此时节点总数为2047,2047-2016=31,需要移除31个节点
在移除节点时,会移除该节点连带着的树,所以移除任意一个节点所带来的节点的减少=一棵满二叉树的节点数=2^m-1(m等于移除的节点与最底层节点的层数之差+1),因此可以是1、3、7、15、31、...,每移除一个子树可以带来一个只有一个孩子的节点
现在需要移除31个节点,有16个只有一个孩子的节点,所以移除16棵子树
1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1=16
1+1+1+1+1+1+1+1+1+3+3+3+3+3+3+3=30
无法到达31(因为把1换成3/7等等,增加的都是偶数,永远无法到达奇数)

The number of degree 3 nodes in a ternary tree (三叉树) is only related to the number of degree 2 nodes and that of leaf nodes, i.e it has nothing to do with the number of degree 1 nodes. (T)

对于树:度为孩子数目,对于图:度为入度+出度
设0/1/2/3度节点数量为n0(即叶节点)、n1、n2、n3,有
节点总数:n=n0+n1+n2+n3=n1+2n2+3n3+1(1为根节点)
可知n0=n2+2n3+1,即n3=(n0-n2-1)/2,与n1无关


选择题
1
Among the following binary trees, which one can possibly be the decision tree (the external nodes are excluded) for binary search? (A)

A.
B.
C.
D.

1
2
3
4
5
6
7
8
9
In a complete binary tree with 1238 nodes, there must be ____ leaf nodes. (C)

A.214
B.215
C.619
D.620

对于h层的完全二叉树,节点有2^h-1个,2^10-1=1023<1238,2^11-1=2047>1238,从第十层开始增加节点,需要增加1238-1023=215个
原本叶节点有2^(h-1)=512个,每连接两个节点上去,叶节点数+1,215/2=107,最后一个节点连接上去后叶节点数量不变,所以为512+107=619

优先队列(堆)

基础知识

对于以数列表示的完全二叉树,节点n的左孩子为2n,右孩子为2n+1。

堆是特殊的完全二叉树,最小堆:父亲<孩子,最大堆:父亲>孩子。

在最小堆中,从根节点到任意叶节点的任意一条路径都按照升序排列。

若一个d-heap以数组形式存储,则对于节点i,其父亲为⌊(i+d−2)/d⌋,第一个孩子为(i−1)d+2,最后一个孩子为id+1。

习题

选择题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Using the linear algorithm to build a min-heap from the sequence {15, 26, 32, 8, 7, 20, 12, 13, 5, 19}, and then insert 6. Which one of the following statements is FALSE? (C)

A.The root is 5
B.The path from the root to 26 is {5, 6, 8, 26}
C.32 is the left child of 12
D.7 is the parent of 19 and 15

5
/ \
/ \
/ \
6 12
/ \ / \
/ \ 32 20
8 7
/ \ / \
26 13 19 15

Suppose that the level-order traversal sequence of a min-heap is { 2, 17, 5, 46, 22, 8, 10 }. Use the linear algorithm to adjust this min-heap into a max-heap, and then call DeleteMax. The postorder traversal sequence of the resulting tree is: ()

A.22, 17, 5, 2, 10, 8
B.5, 2, 17, 8, 10, 22
C.2, 8, 10, 5, 17, 22
D.2, 8, 17, 5, 10, 22

并查集

基础知识

在father[]数组中,正数表示该节点对应的父亲,负数代表该节点为根节点,且该树的大小为这个数的绝对值。若Union按照大小执行,则任何节点的深度都不会超过logN,即最高为log2(N)+1。

图论

基础知识

在有向图中,入度的和必须等于出度的和。

强连通和弱联通针对有向图而言,强连通从一个顶点可以到达其余任意顶点,弱联通将方向去掉后从一个顶点可以到达其余任意顶点。

(无向)联通和(有向)弱联通图中,若有V个节点,则至少有V-1条边。

最小生成树

最小生成树只有连通图才有,用于生成最小的连通图(再少一条边就不联通,多一条边就成圈)。

Prim算法:从已知节点出发找最小边
Kruskal算法:选择最小边直至图联通

欧拉回路

欧拉回路(Euler circuit)需要回到原点,不可有奇数度顶点;欧拉环游(Euler tour)可以不回到原点,允许0或2个奇数度顶点。

习题

判断题
1
2
3
4
5
In a weighted undirected graph, if the length of the shortest path from b to a is 12, and there exists an edge of weight 2 between c and b, then the length of the shortest path from c to a must be no less than 10. (T)

不小于10,不是不大于10


选择题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
If graph G is NOT connected and has 35 edges, then it must have at least ____ vertices. (D)

A.7
B.8
C.9
D.10

对于n个节点,若两两均有边连接,则边的数量为Cn,2,由于要未联通,因此需要多加一个独立的节点,而C9,2=36>35,9+1=10


A graph with 90 vertices and 20 edges must have at least __ connected component(s). (B)

A.69
B.70
C.84
D.85

connected component是指不联通的区域总数,对于20条边,每加一条边,可以减少一个连通区域(连接非该区域内的节点)或不减少连通区域(连接该区域内的节点),故最多可以减少20个连通节点,90-20=70


Given an undirected graph G with 16 edges, where 3 vertices are of degree 4, 4 vertices are of degree 3, and all the other vertices are of degrees less than 3. Then G must have at least __ vertices. (B)

A.10
B.11
C.13
D.15

其余节点需要为2个度,这样才能让节点数最少
先从一条链开始,注意只在外面加节点,不考虑链间节点连接,因为链长度不确定,可以直接缩短链,如:
1---2---1
首尾相连变为:
2---2---2
| |
---------
等同于:
1---1
在外面加一个节点:
2
/ \
2---2
从2个度开始,慢慢加边(数字代表该节点的度),头尾不相连的话必会出现度为1的情况:
1---2---2---2---2...---2---2---1
在外面加一个节点,让头尾的度变为2,同时因为需要度为3的节点,因此分别再连接一个节点:
2 2
/ \ / \
2---3---2---2---2...---2---3---2
假设继续连:
3 3
/ | \ / | \
/ | \ / | \
2---3---3---2---2...---3---3---2
需要有度为4的节点,但现在度为3的节点太多了,需要将上面两个节点进行连接:
4 -------------------- 4
/ | \ / | \
/ | \ / | \
2---3---3---2---2...---3---3---2
少了一个度为4的节点,且已经没办法修改(没有多余的度),因此需要多加一个节点:
4 -------- 2 -------- 4
/ | \ / | \
/ | \ / | \
2---3---3---2---2...---3---3---2
需要度为4的节点做出调整:
3 ---- 4 ----------- 3
/ | / | | \
/ | / | | \
2---3---3---3---2...---2---3---2
最后两个度为3的节点连接,完成:
------------------------
| |
4 ---- 4 ----------- 4
/ | / | | \
/ | / | | \
2---3---3---3---2...---2---3---2

DFS

基础知识

DFS先标记自己为已访问(防止循环),然后对于每一个邻接且未被访问的节点递归调用自身。

习题

选择题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Given the adjacency matrix of a graph as shown by the figure. Then starting from V5, an impossible DFS sequence is: (C)

0 1 1 0 1 0
1 0 0 1 0 0
1 0 0 0 1 1
0 1 0 0 1 0
1 0 1 1 0 1
0 0 1 0 1 0

A.V5, V6, V3, V1, V2, V4
B.V5, V1, V2, V4, V3, V6
C.V5, V1, V3, V6, V4, V2
D.V5, V3, V1, V2, V4, V6

邻接情况:
5-1,3,4,6
1-2,3,5
对于C:5先标记自己visited,然后DFS(1),1去DFS(3),倒回来1
这个时候DFS(1)还没结束,会去DFS(2),而不会一步回到5
所以应该是:
V5, V1, V3, V2, V4, V6
--> -->
<--
-------> -->
<---------------
------------------->

排序

基础知识

插入排序:第P趟保证0-P上的元素已排序(移动第P个位置上的元素到适当位置)。链表会比数组快。

选择排序:第P趟选择从P-1位置开始到最后的数字中的最小者放到位置P-1中。链表会比数组快。

归并排序:对前半部分和后半部分分别归并排序,然后合并两个数组,时间复杂度O(NlogN)。

Shell排序:相隔k的元素得到排序,k从大变小。数组会比链表快。

堆排序:建立堆后执行DeleteMin。数组会比链表快。

快速排序:选枢纽元-分割-对另外两部分快速排序,时间复杂度最好情况O(NlogN),最坏情况O(N^2)。

基数排序,LSD/MSD:最低位/最高位优先,从最低位/最高位开始将各数字放入0-9的bucket中,完成后将数列串起来,变成一个新的数列,再从第二低位/第二高位放置bucket,循环直至结束。

习题

判断题
1
2
3
During the sorting, processing every element which is not yet at its final position is called a "run". To sort a list of integers using quick sort, it may reduce the total number of recursions by processing the small partion first in each run. (F)

递归的总数是不变的
选择题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
To sort N elements by heap sort, the extra space complexity is: (A)

A.O(1)
B.O(logN)
C.O(N)
D.O(NlogN)

用大顶堆删最大元素法,不需要额外空间。


During the sorting, processing every element which is not yet at its final position is called a "run". Which of the following cannot be the result after the second run of quicksort? ()

A.5, 2, 16, 12, 28, 60, 32, 72
B.2, 16, 5, 28, 12, 60, 32, 72
C.2, 12, 16, 5, 28, 32, 72, 60
D.5, 2, 12, 28, 16, 32, 72, 60

已经在正确位置上(左边比它小,右边比它大)的为pivot
第一趟:一分为二,一个pivot
第二趟:两边一分为二,多出来两个pivot,一共有三个pivot,但第一趟有可能有特殊情况
对A:
5 2 16 12 28 60 32 72
| |
第二趟 第一趟(只需管左边的)
对B:
2 16 5 28 12 60 32 72
| |
第二趟 第一趟(只需管左边的)
对C:
2 12 16 5 28 32 72 60
| |
第一趟 第二趟
(只需管右边的)
对D:
5 2 12 28 16 32 72 60
| |
假设第一趟为32,第二趟时32左侧有pivot为12,但右侧没有

散列

基础知识

在散列中搜索一个值的时间复杂度不确定。

若散列表大小为质数且仅为半满,则必定可以用平方探测法寻找到元素插入位置。

在分离链接法中,插入一般比删除快。

在线性探测中,插入元素所需要的探测大于成功搜索到元素所需要的探测,因为有可能找不到插入的位置。

负载密度loading density=n/(B*S),其中n为使用的identifier个数,B为bucket数,S为每个bucket可容纳的元素数。

identifier density=n/T,T为表内存放的元素总数。

双散列:冲突解决函数为F(i)=i*hash(X),用散列函数作为冲突时需要移动到的位置。

习题

选择题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Suppose that the numbers {4371, 1323, 6173, 4199, 4344, 9679, 1989} are hashed into a table of size 10 with the hash function h(X)=X%10, and hence have indices {1, 3, 4, 9, 5, 0, 2}. What are their indices after rehashing using h(X)=X%TableSize with linear probing? (C)

A.11, 3, 13, 19, 4, 0, 9
B.1, 3, 4, 9, 5, 0, 2
C.1, 12, 9, 13, 20, 19, 11
D.1, 12, 17, 0, 13, 8, 14

再散列后的表长应该是20,但是要取大于20的第一个素数,所以表长取23


Rehashing is required when an insertion to a hash table fails. Which of the following is NOT necessary to rehashing? ()

A.change the collision resolution strategy
B.use a new function to hash all the elements into the new table
C.build another table that is bigger than the original one
D.scan down the entire original hash table for non-deleted elements

iOS开发

资料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
https://objccn.io/
https://swiftgg.gitbook.io/swift/
https://swift.org/
https://github.com/anupamchugh/iOS14-Resources
https://awesome-tips.gitbook.io/ios/

# 动画
https://github.com/CalvinCheungCoder/SwiftAnimation
https://zhangdinghao.cn/2017/07/02/Swift-Animation10/

# 侧滑菜单
https://ithelp.ithome.com.tw/articles/10195966
https://www.jianshu.com/p/1b704103be1f
https://blog.coding.net/blog/creating-a-sidebar-menu-using-swrevealviewcontroller-in-swift

设计规范

1
2
3
4
5
https://developer.apple.com/documentation/
https://developer.apple.com/design/human-interface-guidelines/
https://www.microsoft.com/design/fluent/
https://principleformac.com/tutorial.html
https://material.io/design

基本知识

常量

if let

判断对象的值是否为nil。

1
2
3
4
5
6
7
let name: String? = "老王"
let age: Int? = 10

if let nameNew = name,
let ageNew = age {
// nameNew和ageNew不为nil时执行
}

guard let

保证对象的值不为nil。

1
2
3
4
5
6
7
let name: String? = "老王"
let age: Int? = 10

guard let nameNew = name,
let ageNew = age else {
// nameNew和ageNew有其一为nil时执行
}

Extension

当类A需要遵循协议B时,可按照以下原始写法。

1
2
3
class A: B{
...
}

也可通过extension的方式实现,在协议众多的情况下会使代码逻辑更加清楚。

1
2
3
4
5
6
7
class A{
...
}

extension A: B{
...
}

页面生命周期

viewDidLoad

新建页面时被调用,即页面首先载入的方法,类似初始化。一个页面只会调用一次viewDidLoad方法。

viewDidAppear

将页面放置到视图时被调用。每次显示页面都会调用该方法,比如进入页面后返回时会调用一次viewDidAppear方法。

viewDidLayoutSubviews

当页面被放置好时被调用。viewDidLayoutSubviews只会在视图上所有Auto Layout设定或是大小自动计算完成后才会执行,因此会在视图更新、旋转、变动时调用该方法。

在开启APP时,viewDidLayoutSubviews会在viewDidLoad之后执行。

注意,如果要获取控件在设备上的实际尺寸,需要在viewDidLayoutSubviews而不是viewDidLoad进行操作。在viewDidLayoutSubviews调用会得到计算完约束后得到的尺寸,而在viewDidLoad调用会得到在Storyboard或程序添加控件时设定的默认尺寸。

Xcode

设置代码折叠

打开Xcode的Preference-Text Editing,勾选code folding ribbon即可。

组件

Collection View

展示Cell列表。

使用方法

在Storyboard中添加UICollectionView,在其中的Cell布局所需要的控件。

在该页对应的ViewController建立该UICollectionView的Outlet,此处将名称设定为collectionView。新建一个类并继承自UICollectionViewCell,此处取名为MyCell。

将Storyboard中UICollectionView的Cell的Class设定为MyCell,并设置Identifier,此处设置为identifierMyCell

将Cell中的控件通过Outlet添加到MyCell中。回到ViewController的代码页,修改以下代码以建立委托。

1
2
3
4
5
6
override func viewDidLoad(){
super.viewDidLoad()
...
collectionView.delegate = self
collectionView.dataSource = self
}

建立委托后会提示错误,点击错误提示后会自动添加UICollectionViewDataSource协议。需要重写以下协议实现方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// 返回Cell的数量
return 4
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// 对Cell进行设置
// 获取当前操作的Cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "identifierMyCell", for: indexPath) as! MyCell

// 设置对应控件的值
// 控件Outlet在MyCell中被定义
cell.label.text = "Text"

// 返回Cell
return cell
}

布局设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 新建layout
let layout = UICollectionViewFlowLayout()

# 设置滚动方向
layout.scrollDirection = .horizontal

# 设置cell的大小
layout.itemSize = CGSize(width: 100, height: 100)

# 设置分组的间距
layout.sectionInset = UIEdgeInsets.zero

# 设置最小行间距
layout.minimumLineSpacing = 0

# 设置最小列间距
layout.minimumInteritemSpacing = 10

# 设置边界的填充距离
flowLayout.sectionInset = UIEdgeInsets.init(top: 10, left: 10, bottom: 10, right: 10)

# 最后将layout赋值给UICollectionView
self.mCollectionView.collectionViewLayout = layout

点击事件

添加

直接在Storyboard中将Cell拖动至需要跳转的页面即可。

获取点击的位置

点击上面添加的跳转线即segue,设置该segue的identifier,此处为bookJump。重写跳转前的ViewController的prepare方法,示例代码如下。

1
2
3
4
5
6
7
8
# DestViewController为跳转后的ViewController
# indexInCollection为DestViewController的变量,用于存储当前点击的index
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "bookJump") {
(segue.destination as! DestViewController).indexInCollection = collectionView.indexPath(for: sender as! UICollectionViewCell)?.item

}
}

UIColorPickerViewController

颜色选择器。

使用方法

通过以下代码新建。

1
2
3
4
5
6
7
8
9
10
11
// 组件初始化
let picker = UIColorPickerViewController()

// 设置初始颜色
picker.selectedColor = self.view.backgroundColor!

// 设置delegate
picker.delegate = self

// 展示组件
self.present(picker, animated: true, completion: nil)

需要给呈现该组件的页面ViewController添加UIColorPickerViewControllerDelegate协议,示例如下。

1
2
3
4
5
6
7
8
9
10
11
extension ViewController: UIColorPickerViewControllerDelegate {
// 选择颜色完成,关闭picker时被调用
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
// 需要进行的设置
}

// 每次切换颜色时被调用
func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
// 需要进行的设置
}
}

也可不使用以上协议,而使用Combine框架。示例代码如下。

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

class ViewController: UIViewController{
// Global declaration, to keep the subscription alive.
var cancellable: AnyCancellable?

@IBAction func changeBackground(_ sender: Any) {

let picker = UIColorPickerViewController()
picker.selectedColor = self.view.backgroundColor!

// Subscribing selectedColor property changes.
self.cancellable = picker.publisher(for: \.selectedColor)
.sink { color in

// Changing view color on main thread.
DispatchQueue.main.async {
self.view.backgroundColor = color
}
}

self.present(picker, animated: true, completion: nil)
}
}

UIFontPickerViewController

字体选择器。

使用方法

通过以下代码新建。

1
2
3
let vc = UIFontPickerViewController()
vc.delegate = self
present(vc, animated: true)

需要给呈现该组件的页面ViewController添加UIFontPickerViewControllerDelegate协议,示例如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extension ViewController: UIFontPickerViewControllerDelegate {
// 完成字体选择后调用
func fontPickerViewControllerDidPickFont(_ viewController: UIFontPickerViewController) {
// 读取字体描述符
guard let descriptor = viewController.selectedFontDescriptor else { return }

// 从字体描述符获取字体
let font = UIFont(descriptor: descriptor, size: 36)

// 根据需求完成剩余操作
}

// 取消选择字体时调用
func fontPickerViewControllerDidCancel(_ viewController: UIFontPickerViewController) {
// 根据需求完成剩余操作
}
}

UIImageView

添加点击事件

1
2
3
let singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(imageViewClick))
imageView?.addGestureRecognizer(singleTapGesture)
imageView?.isUserInteractionEnabled = true

Button

设置按钮为系统图标

1
2
3
4
5
6
7
# 一般做法
button.setImage(UIImage(systemName: "search"), for: .normal)

# 设置图标权重
let boldConfig = UIImage.SymbolConfiguration(weight: .bold)
let boldSearch = UIImage(systemName: "search", withConfiguration: boldConfig)
button.setImage(boldSearch, for: .normal)

UIView

添加点击事件

1
2
3
4
5
6
7
8
view.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapGestureAction))
tapGesture.numberOfTapsRequired = 1
view.addGestureRecognizer(tapGesture)

@objc func tapGestureAction(){
// todo
}

属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 描述UIView的大小和在父系坐标系中的位置
testView.frame = CGRect(x: 100, y: 100, width: 50, height: 50)

# 描述UIView的大小和在自身坐标系中的位置
# 一般bounds.origin = (0,0),而bounds.size = frames.size
testView.bounds = CGRect(x: 0, y: 0, width: 50, height: 50)

# 背景颜色
testView.backgroundColor = UIColor.black

# 是否切除子视图超出部分
testView.clipsToBounds = true

# 透明度
testView.alpha = 0.5

# 是否隐藏视图
# 若为true,表示该View及其子视图都被隐藏
# 同时该View会从响应链中移除,而响应链的下一个会成为第一响应者
testView.isHidden = false

插入/删除与移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 将子视图从父视图中删除
removeFromSuperview()

# 添加视图,加在父视图层级结构的最上层
addSubview(view:)

# 在指定位置插入视图
insertSubview(view:,at:)

# 将视图添加到指定视图的下方
insertSubview(view:,belowSubview:)

# 将视图添加到指定视图的下方
insertSubview(view:,aboveSubview:)

# 交换两个指定位置的子视图在父视图中的位置
exchangeSubview(at:,withSubviewAt:)

# 将指定子视图移动到最前面
bringSubview(toFront:)

# 将指定子视图移动到最后面
sendSubview(toBack:)

UIBarButtonItem

绑定点击事件

无法调用addTarget()方法。

1
2
barbuttonitem.target = self;
barbuttonitem.action = @selector(myMethod);

UITextView

检测所点击的文本内容

1

Activity Indicator

显示转圈动画。

1
2
3
4
5
6
# 假设通过Outlet,连接为activityIndicator
# 开始转圈
activityIndicator.startAnimating()

# 停止转圈
activityIndicator.stopAnimating()

AVPlayer

用于播放视频。需要AVFoundation库,使用前添加以下语句。

1
import AVFoundation

初始化

1
2
3
4
5
6
7
8
9
guard let path = Bundle.main.path(forResource: "video", ofType:"m4v") else {
debugPrint("video not found")
return
}

let player = AVPlayer(url: URL(fileURLWithPath: path))
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
self.view.layer.addSublayer(playerLayer)

也可通过函数调用的方式,如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var player: AVPlayer!
var playerLayer: AVPlayerLayer!

func viewDidLoad(){
...
addVideo(file: "video.m4v")
}

func addVideo(file: String) {
let files = file.components(separatedBy: ".")

guard let path = Bundle.main.path(forResource: files[0], ofType:files[1]) else {
debugPrint( "\(files.joined(separator: ".")) not found")
return
}
player = AVPlayer(url: URL(fileURLWithPath: path))

playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
self.view.layer.addSublayer(playerLayer)
}

播放

1
player.play()

暂停

1
player.pause()

停止

先将时间设为起始点,然后暂停播放。

1
2
player.seek(to: CMTime.zero)
player.pause()

UIPageViewController

页面切换控制器,可独立使用或嵌入到其它页面使用。

放置

独立使用

直接在Storyboard中添加Page View Controller即可。

嵌入到页面

在页面上添加Container View,再添加一个Page View Controller,连接Container View和Page View Controller,选择Embed。

初始化

将Storyboard中的Page View Controller链接到自定义类,此处为PageViewController。示例代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import UIKit

class PageViewController: UIPageViewController {
// 要展示的所有页面,放置在数组之中
var controllers: [UIViewController] = []

override func viewDidLoad() {
super.viewDidLoad()

// 新建要展示的页面
let firstVC = storyboard!.instantiateViewController(withIdentifier: "FirstView") as! FirstViewController
let secondVC = storyboard!.instantiateViewController(withIdentifier: "SecondView") as! SecondViewController
let ThirdVC = storyboard!.instantiateViewController(withIdentifier: "ThirdView") as! ThirdViewController

// 放置到数组
self.controllers = [ firstVC, secondVC, ThirdVC ]

// 设置首先显示的页面
setViewControllers([self.controllers[0]], direction: .forward, animated: true, completion: nil)

// 数据来源为自身
self.dataSource = self
}
}

// MARK: - UIPageViewController DataSource
extension PageViewController: UIPageViewControllerDataSource {
// 页面数量
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return self.controllers.count
}

// 左滑时的操作
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if let index = self.controllers.firstIndex(of: viewController),
index < self.controllers.count - 1 {
// 后面还有页面,页面后滑一页
return self.controllers[index + 1]
} else {
// 后面没有页面,不允许滑动
return nil

// 后面没有页面,滑动到第一页
return self.controllers.first
}
}

// 右滑时的操作
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if let index = self.controllers.firstIndex(of: viewController),
index > 0 {
// 前面还有页面,页面前滑一页
return self.controllers[index - 1]
} else {
// 前面没有页面,不允许滑动
return nil

// 前面没有页面,滑动到最后一页
return self.controllers.last
}
}
}

添加当前页显示

若需要添加页控件用以显示当前页码,则需要在放置时选择将UIPageViewController嵌入到页面,然后在Container View下添加UIPageControl控件。

添加完成后,除了完成初始化中的步骤外,还需要在显示时和切换页面时给UIPageControl控件传递信息。该操作通过自定义协议PageViewControllerDelegate实现,代码如下,仅显示比初始化时增加的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
protocol PageViewControllerDelegate: class {
// 当页面数量改变时调用
func pageViewController(pageViewController: PageViewController,
didUpdatePageCount count: Int)

// 当前页索引改变时调用
func pageViewController(pageViewController: PageViewController,
didUpdatePageIndex index: Int)
}

class PageViewController: UIPageViewController, UIPageViewControllerDelegate {
...
var pageDelegate: PageViewControllerDelegate?

override func viewDidLoad() {
...
// 协议来源为自身
self.delegate = self

// 页面数量改变,通知委托对象
pageDelegate?.pageViewController(self, didUpdatePageCount: allViewControllers.count)
}
}

// MARK: - UIPageViewController DataSource
extension PageViewController: UIPageViewControllerDataSource {
...
// 页面切换完毕
func pageViewController(pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool) {
if let firstViewController = viewControllers?.first,
let index = controllers.indexOf(firstViewController) {
//当前页改变,通知委托对象
pageDelegate?.pageViewController(self, didUpdatePageIndex: index)
}
}
}

// Container View的父控制器类
class ViewController: UIViewController, PageViewControllerDelegate {

// 页控件
@IBOutlet weak var pageControl: UIPageControl!

override func viewDidLoad() {
super.viewDidLoad()
}

// 场景切换
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
if let pageViewController = segue.destinationViewController as? PageViewController {
// 设置委托(当页面数量、索引改变时当前视图控制器能触发页控件的改变)
pageViewController.pageDelegate = self
}
}

// 当页面数量改变时调用
func pageViewController(pageViewController: PageViewController,
didUpdatePageCount count: Int) {
pageControl.numberOfPages = count
}

// 当前页索引改变时调用
func pageViewController(pageViewController: PageViewController,
didUpdatePageIndex index: Int) {
pageControl.currentPage = index
}
}

样式设置

修改滑动样式

将Transition Style设置成Scroll时为滚动形式,Page Curl时为翻页形式。

设置UIPageControl大小
1
2
3
4
5
6
7
// 整体放大,包括圆点和间距
pageControl.transform = CGAffineTransform(scaleX: 2, y: 2)

// 仅放大圆点
pageControl.subviews.forEach {
$0.transform = CGAffineTransform(scaleX: 2, y: 2)
}

框架

不规则按钮

类定义

定义IrregularButton类,在默认按钮的基础上进行拓展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import UIKit

enum BtnType {
case leftUp
case leftDown
case rightUp
case rightDown
case center
}

class IrregularButton: UIButton {
private var path = UIBezierPath()
private var drawLayer = CAShapeLayer()
private var textLayer = CATextLayer()

// 初始化时为let btn = IrregularButton(frame: CGRect(...))
// 用于标记按钮在父控制器的位置
override init(frame: CGRect) {
super.init(frame: frame)

self.layer.addSublayer(self.drawLayer)
self.layer.addSublayer(self.textLayer)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// 通过path定义按钮形状
// path.move为移动到起点
// (0, 0)为初始化时标记的位置
// 用(0, 0)标记为无左侧偏移
// path.addLine为从当前点绘制到指定点
// path.addArc为添加圆弧
// path.close()标记路径完成
func path(type: BtnType) {

let path = UIBezierPath()

switch type {
case .leftUp:
path.move(to: CGPoint(x: 60, y: 100))
path.addLine(to: CGPoint(x: 0, y: 100))
path.addArc(withCenter: CGPoint(x: 100, y: 100), radius: 100, startAngle: .pi, endAngle: .pi*1.5, clockwise: true)
path.addLine(to: CGPoint(x: 100, y: 60))
path.addArc(withCenter: CGPoint(x: 100, y: 100), radius: 40, startAngle: .pi*1.5, endAngle: .pi, clockwise: false)
path.close()

self.path = path

case .leftDown:
path.move(to: CGPoint(x: 60, y: 100))
path.addLine(to: CGPoint(x: 0, y: 100))
path.addArc(withCenter: CGPoint(x: 100, y: 100), radius: 100, startAngle: .pi, endAngle: .pi*0.5, clockwise: false)
path.addLine(to: CGPoint(x: 100, y: 140))
path.addArc(withCenter: CGPoint(x: 100, y: 100), radius: 40, startAngle: .pi*0.5, endAngle: .pi, clockwise: true)
path.close()

self.path = path
case .rightUp:
path.move(to: CGPoint(x: 100, y: 60))
path.addLine(to: CGPoint(x: 100, y: 0))
path.addArc(withCenter: CGPoint(x: 100, y: 100), radius: 100, startAngle: .pi*1.5, endAngle: 0, clockwise: true)
path.addLine(to: CGPoint(x: 140, y: 100))
path.addArc(withCenter: CGPoint(x: 100, y: 100), radius: 40, startAngle: 0, endAngle: .pi*1.5, clockwise: false)
path.close()

self.path = path
case .rightDown:
path.move(to: CGPoint(x: 140, y: 100))
path.addLine(to: CGPoint(x: 200, y: 100))
path.addArc(withCenter: CGPoint(x: 100, y: 100), radius: 100, startAngle: 0, endAngle: .pi*0.5, clockwise: true)
path.addLine(to: CGPoint(x: 100, y: 140))
path.addArc(withCenter: CGPoint(x: 100, y: 100), radius: 40, startAngle: .pi*0.5, endAngle: 0, clockwise: false)
path.close()

self.path = path
case .center:
path.move(to: CGPoint(x: 140, y: 100))
path.addArc(withCenter: CGPoint(x: 100, y: 100), radius: 40, startAngle: 0, endAngle: .pi*2, clockwise: true)
path.close()

self.path = path
}

self.drawLayer.path = self.path.cgPath

setNeedsDisplay()
}

func text(text: String) {

let stringSize = text.boundingRect(with: CGSize(width:100,height:CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font:UIFont.systemFont(ofSize: 14)], context: nil).size

textLayer.frame = CGRect(x: self.path.bounds.origin.x+(self.path.bounds.size.width/2)-(stringSize.width/2), y: self.path.bounds.origin.y+(self.path.bounds.size.height/2)-(stringSize.height/2), width: stringSize.width, height: stringSize.height)

textLayer.string = NSAttributedString(string: text, attributes: [NSAttributedString.Key.foregroundColor:UIColor.black,
NSAttributedString.Key.font:UIFont.systemFont(ofSize: 14)])
textLayer.backgroundColor = UIColor.clear.cgColor

// 设置是否自动换行
textLayer.isWrapped = false

// 寄宿图的像素尺寸和视图大小的比
// 不设置为屏幕比例文字就会像素化
textLayer.contentsScale = UIScreen.main.scale

setNeedsDisplay()
}

func backgroundColor(color: UIColor) {

self.drawLayer.fillColor = color.cgColor

setNeedsDisplay()
}

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
if self.path.contains(point) {
return true
} else {
return false
}
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
let btn = IrregularButton(frame: CGRect(x: 80, y: 100, width: 0, height: 0))

btn.path(type: .leftUp)
btn.backgroundColor(color: UIColor(red: 0, green: 0, blue: 0, alpha: 1))
btn.text(text: "Some Text")
btn.addTarget(self, action: #selector(btnClick), for: .touchUpInside)

self.view.addSubview(btn)

func btnClick(){
// todo
}

OCR识别

1
2
https://developer.apple.com/documentation/vision/structuring_recognized_text_on_a_document
https://developer.apple.com/documentation/vision/recognizing_text_in_images

修改识别语言

默认识别语言为英文。

1
2
# 修改识别语言为简体中文
textRecognitionRequest.recognitionLanguages = ["zh-CN"]

手部识别

例程

1
2
https://developer.apple.com/documentation/vision/detecting_hand_poses_with_vision
https://heartbeat.fritz.ai/swipeless-tinder-using-ios-14-vision-hand-pose-estimation-64e5f00ce45c

常见问题

镜头方向随设备旋转而变化

需要旋转cameraPreviewLayer,即相机预览层。

1
cameraPreviewLayer.connection.videoOrientation = getCaptureVideoOrientation()

其中getCaptureVideoOrientation()函数如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func getCaptureVideoOrientation() -> AVCaptureVideoOrientation {
switch UIDevice.current.orientation {
case .portrait,.faceUp,.faceDown:
return .portrait
case .portraitUpsideDown:
return .portrait
case .landscapeLeft:
return .landscapeRight
case .landscapeRight:
return .landscapeLeft
default:
return .portrait
}
}
【进阶】旋转视频

按照以下方式设置。

1
session.outputs[0].connection(with: .video)?.videoOrientation = true

文本转语音

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 
class ViewController: UIViewController, AVSpeechSynthesizerDelegate {
// 开始朗读
func StartSpeech(){
let utterance = AVSpeechUtterance(string: inportTextField.text!)
utterance.voice = AVSpeechSynthesisVoice(language: "zh-CN")
let synthesizer = AVSpeechSynthesizer()

// 音调
utterance.pitchMultiplier = tonebar.value

// 速度
utterance.rate = speedbar.value
synthesizer.speak(utterance)
}

// 停止朗读
func StopSpeech() {
// 立即中断语音
synth.stopSpeaking(at: AVSpeechBoundary.immediate)
}

// 语音结束后的操作
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
// code
}
}

类型

NSMutableAttributedString

可设置样式的富文本。

样式设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let labelString = "Underline Label"
let textColor: UIColor = .blue
let underLineColor: UIColor = .red
let underLineStyle = NSUnderlineStyle.styleSingle.rawValue


let labelAtributes:[NSAttributedStringKey : Any] = [
NSAttributedStringKey.foregroundColor: textColor,
NSAttributedStringKey.underlineStyle: underLineStyle,

# 下划线颜色
NSAttributedStringKey.underlineColor: underLineColor
]

let underlineAttributedString = NSAttributedString(string: labelString, attributes: labelAtributes)
textView.attributedText = underlineAttributedString

样式追加

1
2
3
let attributed = NSMutableAttributedString(attributedString: textView.attributedText!)
attributed.addAttribute(.foregroundColor, value: ChangeColor, range: .init(location: 0, length: attributed.length))
textView.attributedText = NSAttributedString(attributedString: attributed)

转换为String

使用string方法即可。

1
2
var attributedString = NSMutableAttributedString(string: "hello, world!")
var s = attributedString.string

数组

创建

1
var animals = ["cats", "dogs", "chimps", "moose"]

删除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 删除第一个元素
animals.removeFirst() // "cats"

# 删除最后一个元素
animals.removeLast() // "moose"

# 删除索引处的元素
animals.remove(at: 2) // "chimps"
## 或以下方法
let pets = animals.filter { $0 != "chimps" }

# 删除未知索引的元素(仅一个元素)
if let index = animals.firstIndex(of: "chimps") {
animals.remove(at: index)
}

# 删除未知索引的元素(多个元素)
animals = animals.filter(){$0 != "chimps"}

页面/进程/动作控制

子视图添加和移除

1
2
3
4
5
# 添加
self.addSubview(subView)

# 移除
subView.removeFromSuperView()

监听进程事件

在AppDelegete.swift下的AppDelegete类添加以下函数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func applicationDidFinishLaunching(_ application: UIApplication) {
print("程序启动")
}

func applicationWillResignActive(_ application: UIApplication) {
print("程序变为不活跃状态")
}

func applicationDidEnterBackground(_ application: UIApplication) {
print("程序进入后台")
}

func applicationDidEnterForeground(_ application: UIApplication) {
print("程序进入前台")
}

func applicationDidBecomeActive(_ application: UIApplication) {
print("变为活跃状态")
}

func applicationWillTerminate(_ application: UIApplication) {
print("程序终止")
}

监听触摸事件

UIView等相关视图是UIResponder的子类,而UIResponder可对相关触摸事件做出反馈。将Storyboard中的视图绑定到自定义类后,即可重写以下方法对触摸事件做出反馈。

1
2
3
4
5
6
7
8
9
10
11
# 点击事件触发
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)

# 移动事件触发
func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)

# 结束点击事件触发
func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)

# 取消点击事件触发
func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)

在View中获取点击坐标

需要重写View的touchesBegan方法。

1
2
3
4
5
6
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch:AnyObject in touches {
let t:UITouch = touch as! UITouch
print(t.location(in: self.contentView))
}
}

隐藏返回键

在需要隐藏返回键页面的ViewController的viewDidLoad()方法中添加以下语句即可。

1
self.navigationController?.navigationBarHidden = true

从跳转中获取父/子控制器

在Storyboard中对需要设置的segue设置identifier,此处为Jump。假设父控制器为ParentViewController,子控制器为ChildViewController。

父控制器代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
class ParentViewController: UIViewController {
var child: ChildViewController?

// Set the child delegate
// And child's parent delegate
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier =="Jump" {
self.child = (segue.destinationViewController as ChildViewController)
(segue.destinationViewController as ChildViewController).parent = self
}
}
}

子控制器代码如下。

1
2
3
class ChildViewController: UITableViewController {
var parent: ParentViewController?
}

从视图获取父控制器

1
2
3
4
5
6
7
8
9
func nextresponsder(viewself:UIView) -> UIViewController{
var vc:UIResponder = viewself

while vc.isKind(of: UIViewController.self) != true {
vc = vc.next!
}

return vc as! UIViewController
}

获取子视图在父视图中的坐标

1
2
3
4
5
# 获取childView在fatherView的坐标,size是childView
let crect = childView?.convert((childView?.frame)!, to: fatherView)

# 或以下方式
let crect2 = fatherView?.convert((childView?.frame)!, from: childView)

不可退回的页面跳转

设置

使用present()方法即可。

淡出切换

1
2
# toVC为目标控制器
toVC.modalTransitionStyle = .crossDissolve

可退回的页面跳转

原理

若使用普通的present方法,将无法实现页面回退。

设当前有两个页面firstVC和secondVC,通过按钮进行两个页面的切换,按钮均绑定btnClicked方法。若使用正常跳转逻辑,代码示例如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// firstVC的类
class FirstViewController: UIViewController {
var secondVC: SecondViewController!
...

func btnClicked() {
secondVC = SecondViewController()
secondVC.firstVC = self
self.present(secondVC, animated: true)
}
}

// secondVC的类
class SecondViewController: UIViewController {
var firstVC: FirstViewController!
...

func btnClicked() {
self.present(firstVC, animated: true)
}
}

在firstVC点击按钮可以顺利跳转到secondVC,但在secondVC点击按钮时将会报错,原因是跳转到的页面已经被呈现过。因此需要使用NavigationController作为页面跳转的载体,通过pushViewController和popViewController方法实现回退跳转。

初始化与跳转设置

在Storyboard点击底层页面,即无法再回退的页面,此处以firstVC为例,在菜单栏选择Editor-Embed in-Navigation Controller以使NavigationController嵌入到页面中。

嵌入后代码示例如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// firstVC的类
class FirstViewController: UIViewController {
...

func btnClicked() {
let secondVC = SecondViewController()
self.navigationController?.pushViewController(secondVC, animated: true)
}
}

// secondVC的类
class SecondViewController: UIViewController {
...

func btnClicked() {
// 返回前一页
self.navigationController?.pushViewController(firstVC, animated: true)

// 返回到最前页(根页面)
// self.navigationController?.popToRootViewController(animated: true)

// 返回前两页
// let count = self.navigationController!.viewControllers.count
// if let preController = self.navigationController?.viewControllers[count-3] {
self.navigationController?.popToViewController(preController, animated: true)
}
}
}

隐藏导航栏

若需要隐藏所有页面的导航栏,可点击Navigation Controller的导航栏,在右侧勾选isHidden即可。或者通过以下代码实现。

1
self.navigationController?.navigationBar.isHidden = true

若需要隐藏特定页面的导航栏,则在该页面的控制器添加以下重写函数即可。

1
2
3
4
5
6
7
8
9
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: animated)
}

转场样式

淡出转场

添加以下扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extension UINavigationController {
func fadeIn(_ viewController: UIViewController) {
let transition: CATransition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.fade
view.layer.add(transition, forKey: nil)
pushViewController(viewController, animated: false)
}

func fadeOut(_ viewController: UIViewController) {
let transition: CATransition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.fade
view.layer.add(transition, forKey: nil)
popViewController(animated: false)
}
}

按照如下方式使用即可。

1
2
3
4
5
# 用于取代self.navigationController?.pushViewController(viewController, animated: true)
self.navigationController?.fadeIn(viewController)

# 用于取代self.navigationController?.popViewController(animated: true)
self.navigationController?.fadeOut()

转场动画

在页面跳转时,可设置转场动画。转场动画通过自定义的TransitionCoordinator类实现,该类用于承接NavigationController的delegate,定义转场时的操作。

为向TransitionCoordinator类传递动画前后的控件位置,首先定义animTransitionable协议用于从主控制器获取相关内容。

1
2
3
4
5
# 定义需要获取的控件
protocol animTransitionable {
var backGroundView: UIView { get }
var AnimateButton: UIButton { get }
}

在主控制器添加关于animTransitionable的拓展,从而指定每个选项对应的应当获取的内容。

1
2
3
4
5
6
7
8
9
10
11
12
extension MainViewController : animTransitionable
{
# 保证动画过程中背景一致
var backGroundView: UIView {
return backgroundView
}

# 需要进行动画操作的控件
var AnimateButton: UIButton {
return animateButton
}
}

在需要转场的页面添加以下代码。

1
2
3
4
5
6
7
let transition = TransitionCoordinator()

# 开启转场动画
navigationController?.delegate = transition

# 关闭转场动画
navigationController?.delegate = nil

TransitionCoordinator类定义如下。在进行push时,会调用PushAnimator()方法,在进行pop时则调用PopAnimator()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import UIKit

class TransitionCoordinator: NSObject, UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationControllerOperation,
from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {

switch operation {
case .push:
return PushAnimator()
case .pop:
return PopAnimator()
default:
return nil
}
}
}

以PushAnimator()方法为例,示例如下。其原理为新建一个视图,在其上隐藏来源控制器,完成动画操作,然后显示目标控制器。PopAnimator()方法原理完全一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import UIKit

class PushAnimator: NSObject, UIViewControllerAnimatedTransitioning {
# 动画时长
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 2.0
}

# 动画设置
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
# 新建视图
let containerView = transitionContext.containerView

# 用于提取来源控制器和目标控制器中的指定控件
# 这些控件在animTransitionable协议中被定义
guard let fromVC = transitionContext.viewController(forKey: .from) as? animTransitionable,
let toVC = transitionContext.viewController(forKey: .to) as? animTransitionable else {
transitionContext.completeTransition(false)
return
}

# 获取来源控制器和目标控制器
let fromViewController = transitionContext.viewController(forKey: .from)!
let toViewController = transitionContext.viewController(forKey: .to)!

# 添加背景到containerView并设置为来源控制器的颜色
let backgroundView = UIView()
backgroundView.frame = fromVC.backGroundView.frame
backgroundView.backgroundColor = fromVC.backGroundView.backgroundColor
containerView.addSubview(backgroundView)

# 添加需要设置动画的控件
# 初始位置来源于来源控制器,通过convert进行坐标变换
let animateButton = UIButton()
animateButton.frame = containerView.convert(fromVC.AnimateButton.frame, from: fromVC.AnimateButton.superview)
animateButton.backgroundColor = fromVC.AnimateButton.backgroundColor
animateButton.layer.cornerRadius = fromVC.AnimateButton.layer.cornerRadius
animateButton.layer.masksToBounds = fromVC.AnimateButton.layer.masksToBounds

# 添加来源控制器和目标控制器
# 并设置为隐藏
containerView.addSubview(fromViewController.view)
containerView.addSubview(toViewController.view)
fromViewController.view.isHidden = true
toViewController.view.isHidden = true

# 动画设置
# 缩放为原来的0.9倍,持续时间0.4秒,弹跳动作强度1.3
let animator1 = {
UIViewPropertyAnimator(duration: 0CGAffineTransform(scaleX: 0.9, y: 0.9).4, dampingRatio: 1.3) {
animateButton.transform =
}
}()

# 消除圆角,持续时间0.3秒,弹跳动作强度0.9
let animator2 = {
UIViewPropertyAnimator(duration: 0.3, dampingRatio: 0.9) {
animateButton.layer.cornerRadius = 0
}
}()

# 在第一个动画完成后再进行第二个动画
animator1.addCompletion { _ in
animator2.startAnimation()
# 若需要添加延迟则使用以下代码
animator2.startAnimation(afterDelay: 0.1)
}

# 在第二个动画完成后显示目标控制器
animator2.addCompletion { _ in
# 隐藏相关控件
cellBackground.removeFromSuperview()

# 显示目标控制器
toViewController.view.isHidden = false

transitionContext.completeTransition(true)
}

# 开启动画
animator1.startAnimation()
}
}

界面与外观设置

添加自定义字体

将所需字体拖动到Xcode的文件栏,选择Copy items if needed和create groups。然后打开Info.plist,添加Fonts provided by application,内容为字体文件名称,注意包括扩展名。

可在编辑组件时直接修改组件的Font属性使用自定义字体,也可在代码中调用。由于字体名称与字体文件名称不一定完全对应,因此需要通过以下代码输出当前APP可用的字体列表。该段代码放置于任意可被执行的位置即可,比如放置在会出现的ViewController的viewDidLoad()方法中。

1
2
3
4
for family in UIFont.familyNames.sorted() {
let names = UIFont.fontNames(forFamilyName: family)
print("Family: \(family) Font names: \(names)")
}

知道所用字体的名称后,通过以下命令即可。

1
2
3
4
5
6
7
8
9
10
# label为需要改变字体的标签
guard let customFont = UIFont(name: "CustomFont-Light", size: UIFont.labelFontSize) else {
fatalError("""
Failed to load the custom font.
Make sure the font file is included in the project and the font name is spelled correctly.
""")
}

label.font = UIFontMetrics.default.scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true

启动页相关

设置启动页

启动页一般在LaunchScreen.storyboard中设置,可点击该文件后,查看右侧Interface Builder Document下Use as Launch Screen是否已被勾选,若未勾选则勾选即可。

也可在项目设置中点击APP,在General的Deployment Info-Main Interface选择启动后显示的页面,一般为Main。在App Icons and Launch Images-Launch Screen File选择启动页,即LaunchScreen。

修改启动页界面

在LaunchScreen.storyboard中放置控件即可。注意该文件中的ViewController不可绑定到自定义类,因此该ViewController的控件属性只可提前设定。

若启动页有图片,注意需要直接放置到项目目录,而不要放置到Assets.xcasset文件夹中,且应当为JPG而非PNG格式。

设置启动页停留时间

在AppDelegate.swift下设置启动页时长,如下。

1
2
3
4
5
6
7
8
9
10
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
# 在顶部状态栏显示风火轮
UIApplication.shared.isNetworkActivityIndicatorVisible = true
# 启动页显示时间3s
Thread.sleep(forTimeInterval: 3)
# 关闭风火轮
UIApplication.shared.isNetworkActivityIndicatorVisible = false

return true
}

在不同设备设置不同布局

主要用于在iPhone和iPad设置不同的布局。假设Main_iPad.storyboard为用于iPad的布局,Main_iPhone.storyboard为用于iPhone的布局,在AppDelegate.swift下添加以下代码即可。

1
2
3
4
5
6
7
8
9
10
11
12
func applicationDidFinishLaunching(_ application: UIApplication) {
if(UIDevice.current.userInterfaceIdiom == .pad){
currentStoryBoard = UIStoryboard(name: "Main_iPad", bundle: nil)
initViewController = currentStoryBoard.instantiateInitialViewController()
self.window?.rootViewController = initViewController
}
else if(UIDevice.current.userInterfaceIdiom == .phone){
currentStoryBoard = UIStoryboard(name: "Main_iPhone", bundle: nil)
initViewController = currentStoryBoard.instantiateInitialViewController()
self.window?.rootViewController = initViewController
}
}

添加圆角/描边

可通过以下代码设置。

1
2
3
4
5
6
7
# 圆角
label.layer.cornerRadius = 10

# 描边
label.layer.masksToBounds = true
label.layer.borderWidth = 1
label.layer.borderColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)

也可在Storyboard中点击需要设置的控件,在右侧的Runtime Attributes添加以下项目。

1
2
3
4
layer.borderWidth
layer.borderColorFromUIColor
layer.cornerRadius
clipsToBounds

添加阴影

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 设置阴影颜色
view.layer.shadowColor = sColor.cgColor

# 设置透明度
view.layer.shadowOpacity = opacity

# 设置阴影半径
view.layer.shadowRadius = radius

# 设置阴影偏移量
# offset为CGSize(width: height:)类型
# width为正数时向右偏移,height为正数时向下偏移
view.layer.shadowOffset = offset

# 显示阴影
view.layer.masksToBounds = false

控件变换与动画

通过transform属性可以修改控件的位移、缩放、旋转。

变换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 修改位置
# 每次形变都基于原始值
btn.transform = CGAffineTransformMakeTranslation()
# 基于btn上次的值
btn.transform = CGAffineTransformTranslate()

# 缩放
## 按照比例缩放
btn.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
## a表示x水平方向的缩放,tx表示x水平方向的偏移
## d表示y垂直方向的缩放,ty表示y垂直方向的偏移
## b和c不为零表示视图发生了旋转
btn.transform = CGAffineTransform(a: 0.9, b: 0, c: 0, d: 0.9, tx: 100, ty: 100)

# 旋转
# angle是弧度值,通过宏M_PI设置
btn.transform = CGAffineTransformMakeRotation()
btn.transform = CGAffineTransformRotate()

# 旋转和移动
btn.transform = CGAffineTransformMakeTranslation()
btn.transform = CGAffineTransformRotate()

# 重置位置
btn.transform = CGAffineTransformIdentity

动画

以为按钮添加缩放动画为例,代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 设置动画类型
let zoomInAndOut = CABasicAnimation(keyPath: "transform.scale")

# 起始比例
zoomInAndOut.fromValue = 1.0

# 终止比例
zoomInAndOut.toValue = 0.5

# 动画时长
zoomInAndOut.duration = 1.0

# 重复次数
zoomInAndOut.repeatCount = 5

# 自动翻转
# fromValue->toValue后自动进行toValue->fromValue
zoomInAndOut.autoreverses = true

# 速度
# 由于设置了autoreverses,则fromValue->toValue和toValue->fromValue各耗费1.0s
# 将速度设置为2倍后保证这组动作在1.0s完成
zoomInAndOut.speed = 2.0

# 为按钮添加动作
button.layer.addAnimation(zoomInAndOut, forKey: nil)

Popover

Popover是类似气泡的ViewController显示模式,一般在点击按钮时触发。

通过segue

若在Storyboard中通过segue实现跳转,在拉segue时选择Present As Popover即可。在segue的Anchor设置中可设置箭头指向的对象。

在iPhone上Popover默认无法显示出像iPad的效果,需要进行以下设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ViewController为Popover对应控制器的父控制器
// 添加UIPopoverPresentationControllerDelegate,让本页的相关函数控制该Popover的行为
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// 设置delegate为该控制器
segue.destination.popoverPresentationController?.delegate = self

// 设置大小
segue.destination.preferredContentSize = CGSize(width: 150, height: 200)

}

// 使iPhone显示Popover效果
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
}

通过纯代码

以按钮点击事件触发Popover显示为例,触发代码如下。在iPhone上显示iPad的Popover效果需要的代码操作与用Storyboard的实现相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@IBAction func buttonPressed(_ sender: Any) {
// TableViewController为需要显示的Popover的控制器的identifier
if let controller = storyboard?.instantiateViewController(withIdentifier: "TableViewController") {
// 设置显示效果为Popover
controller.modalPresentationStyle = .popover

// 指定箭头指向的位置
controller.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem
// 也可指定为指向特定的View
controller.popoverPresentationController?.sourceView = eyeSwitch
// 指定为特定View后,可通过以下代码设置箭头在View中的具体位置(不设置时默认指向左上方)
// 以下代码将位置改为View的右下方
controller.popoverPresentationController?.sourceRect = CGRect(origin: .zero, size: eyeSwitch.frame.size)

// 显示Popover
present(controller, animated: true, completion: nil)
}
}

功能实现

分页

准备好处理完成的NSAttributedString,提前调整好字体、颜色、格式等信息。然后通过以下代码实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 创建NSLayoutManager
let layoutManager = NSLayoutManager()

# 如果没有给特定部分文字区域设置单独的布局,可设置此项为false以提高性能
layoutManager.allowsNonContiguousLayout = false

# 使用之前准备好的NSAttributedString进行初始化NSTextStorage
let textStorage = NSTextStorage(attributedString: string)
textStorage.addLayoutManager(layoutManager)

# 设定文字显示区域参数
let viewSize: CGSize = CGSize(width: textAreaWidth, height: textAreaHeight)

# 设定textView的内间距
let textInsets = UIEdgeInsets.zero
let textViewFrame = CGRect(x: 0, y: 0, width: viewSize.width, height: viewSize.height)

# 开始分页
var glyphRange: Int = 0
var numberOfGlyphs: Int = 0

var ranges: [NSRange] = []
repeat {
let textContainer = NSTextContainer(size: viewSize)
layoutManager.addTextContainer(textContainer)

# 不断创建textView让NSLayoutManager进行内容分页
let textView = UITextView(frame: textViewFrame, textContainer: textContainer)
textView.isEditable = false
textView.isSelectable = false
textView.textContainerInset = textInsets
textView.showsVerticalScrollIndicator = false
textView.showsHorizontalScrollIndicator = false
textView.isScrollEnabled = false
textView.bounces = false
textView.bouncesZoom = false

# 获取当前分页内容所在位置
let range = layoutManager.glyphRange(for: textContainer)
ranges.append(range)

# 判定是否分页完成
glyphRange = NSMaxRange(range)
numberOfGlyphs = layoutManager.numberOfGlyphs
} while glyphRange < numberOfGlyphs - 1

分词/分句

为String添加Extension,将需要分词/分句的内容设为String类型后调用其tokenize()方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extension String {
func tokenize() -> [String] {
let word = self

# kCFStringTokenizerUnitWord更改为kCFStringTokenizerUnitSentence即为分句
let tokenize = CFStringTokenizerCreate(kCFAllocatorDefault, word as CFString!, CFRangeMake(0, word.characters.count), kCFStringTokenizerUnitWord, CFLocaleCopyCurrent())
CFStringTokenizerAdvanceToNextToken(tokenize)
var range = CFStringTokenizerGetCurrentTokenRange(tokenize)
var keyWords : [String] = []
while range.length > 0 {
let wRange = word.index(word.startIndex, offsetBy: range.location)..<word.index(word.startIndex, offsetBy: range.location + range.length)
let keyWord = word.substring(with:wRange)
keyWords.append(keyWord)
CFStringTokenizerAdvanceToNextToken(tokenize)
range = CFStringTokenizerGetCurrentTokenRange(tokenize)
}
return keyWords
}
}

Unity相关

Unity工程文件可以打包为Xcode工程文件,但其语言为Objective-C。

嵌入到Swift工程

新版教程

适用于Unity 2020及以上版本。官方示例如下。

1
https://github.com/Unity-Technologies/uaal-example/blob/master/docs/ios.md

准备好需要嵌入的Swift工程,注意新建工程时需要将Interface设为Storyboard,Life Cycle设为UIKit App Delegate。

打开Unity工程,点击File-Build Settings,将Platforms设为iOS,右侧设置中Run in Xcode as选择Release,选项全部取消勾选,然后导出工程文件。

将以上两个工程放置到同一目录,示例如下。

1
2
3
4
5
6
7
└── APP
├── Interface
│ ├── ...
│ └── Interface.xcodeproj
└── Unity
├── ...
└── Unity-iPhone.xcodeproj

打开Xcode并点击File-New-Workspace,存放位置为上述的目录,此处为APP。完成后在左侧点击右键并选择Add Files to ...,选择所有的工程文件,此处为Interface.xcodeproj和Unity-iPhone.xcodeproj。

添加完成后左侧点击Interface工程,选择TARGETS下的APP,在General-Frameworks, Libraries, and Embedded Content下点击+号,选择Unity-iPhone下的UnityFramework.framework。若没有该文件,需要先点击Unity-iPhone工程,设置好签名后运行一次代码,使该Framework得以生成。

然后点击Unity-iPhone下的Data文件夹,在右侧的Target Membership勾选UnityFramework。然后打开Interface工程下的Info.plist,删除Application Scene Manifest一项。

在Interface工程下新建Unity.swift文件,继承自UIResponder。该类用于控制Unity的启动与关闭,代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import Foundation
import UnityFramework

class Unity: UIResponder, UIApplicationDelegate {

static let shared = Unity()

private let dataBundleId: String = "com.unity3d.framework"
private let frameworkPath: String = "/Frameworks/UnityFramework.framework"

private var ufw : UnityFramework?
private var hostMainWindow : UIWindow?

private var isInitialized: Bool {
ufw?.appController() != nil
}

func show() {
if isInitialized {
showWindow()
} else {
initWindow()
}
}

func setHostMainWindow(_ hostMainWindow: UIWindow?) {
self.hostMainWindow = hostMainWindow
}

private func initWindow() {
if isInitialized {
showWindow()
return
}

guard let ufw = loadUnityFramework() else {
print("ERROR: Was not able to load Unity")
return unloadWindow()
}

self.ufw = ufw
ufw.setDataBundleId(dataBundleId)
ufw.register(self)
ufw.runEmbedded(
withArgc: CommandLine.argc,
argv: CommandLine.unsafeArgv,
appLaunchOpts: nil
)
}

private func showWindow() {
if isInitialized {
ufw?.showUnityWindow()
}
}

private func unloadWindow() {
if isInitialized {
ufw?.unloadApplication()
}
}

private func loadUnityFramework() -> UnityFramework? {
let bundlePath: String = Bundle.main.bundlePath + frameworkPath

let bundle = Bundle(path: bundlePath)
if bundle?.isLoaded == false {
bundle?.load()
}

let ufw = bundle?.principalClass?.getInstance()
if ufw?.appController() == nil {
let machineHeader = UnsafeMutablePointer<MachHeader>.allocate(capacity: 1)
machineHeader.pointee = _mh_execute_header

ufw?.setExecuteHeader(machineHeader)
}
return ufw
}
}

extension Unity: UnityFrameworkListener {

func unityDidUnload(_ notification: Notification!) {
ufw?.unregisterFrameworkListener(self)
ufw = nil
hostMainWindow?.makeKeyAndVisible()
}
}

然后打开AppDelegate.swift,删除所有与场景相关的代码,并添加以下内容,以指定关闭Unity后应当返回的窗口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

Unity.shared.setHostMainWindow(window)

return true
}
}

在需要调用Unity模块的地方调用以下代码即可。

1
Unity.shared.show()

若需要退出Unity,需要在Unity新建一个按钮,点击按钮时执行退出操作并返回到iOS App中。新建一个Button后新建一个名为QuitBehavior.cs的脚本并绑定到该Button,脚本代码如下,事件为OnButtonPressed。完成后重新导出Xcode工程文件并重复上述步骤即可。

1
2
3
4
5
6
7
8
9
10
11
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class QuitBehavior : MonoBehaviour
{
public void OnButtonPressed()
{
Application.Unload();
}
}

常见问题

加载Vuforia框架时输出台显示dataset xxx could not be loaded and cannot be activated

数据集没有加载到原Swift工程,在此处为Interface工程。

数据集位置在Unity-iPhone工程的Data/Raw/Vuforia中。点击Unity-iPhone工程,点击TARGETS下的App,在Build Phases-Copy Bundle Resources下删除Vuforia文件夹并重新添加,注意不要勾选Copy items if needed,选择create folder references方式。

点击左侧新出现的Vuforia文件夹,在右侧勾选UnityFramework。点击Interface工程下TARGETS的App,在Build Phases-Copy Bundle Resources添加Unity-iPhone工程的Vuforia,同样不要勾选Copy items if needed,选择create folder references方式。完成后重新运行即可。

加载Unity时输出台显示dyld: Library not loaded: @rpath/ARFoundationDriver.framework/ARFoundationDriver…Reason: image not found

点击Interface工程,选择Build Phases,点击左侧+号并选择New Copy Files Phase,然后在下面添加的Copy Files中,将Destination选为Frameworks,然后点击+号,添加所需要的Frameworks,此处为ARFoundationDriver.framework。

旧版教程

适用于Unity 2017/2018。

准备好需要嵌入的Swift工程。打开Unity工程,在Scripts/Editor下新建一个文件,名为XcodePostBuild.cs,内容如下。注意需要修改XcodeProjectRoot和XcodeProjectName,可使用相对路径,至xcodeproj所在目录为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
/*
MIT License

Copyright (c) 2017 Jiulong Wang

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#if UNITY_IOS

using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Text;

using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;

/// <summary>
/// Adding this post build script to Unity project enables Unity iOS build output to be embedded
/// into existing Xcode Swift project.
///
/// However, since this script touches Unity iOS build output, you will not be able to use Unity
/// iOS build directly in Xcode. As a result, it is recommended to put Unity iOS build output into
/// a temporary directory that you generally do not touch, such as '/tmp'.
///
/// In order for this to work, necessary changes to the target Xcode Swift project are needed.
/// Especially the 'AppDelegate.swift' should be modified to properly initialize Unity.
/// See https://github.com/jiulongw/swift-unity for details.
/// </summary>
public static class XcodePostBuild
{
/// <summary>
/// Path to the root directory of Xcode project.
/// This should point to the directory of '${XcodeProjectName}.xcodeproj'.
/// It is recommended to use relative path here.
/// Current directory is the root directory of this Unity project, i.e. the directory of 'Assets' folder.
/// Sample value: "../xcode"
/// </summary>
private const string XcodeProjectRoot = <PROJECT PATH>;

/// <summary>
/// Name of the Xcode project.
/// This script looks for '${XcodeProjectName} + ".xcodeproj"' under '${XcodeProjectRoot}'.
/// Sample value: "DemoApp"
/// </summary>
private const string XcodeProjectName = <PROJECT NAME>;

/// <summary>
/// Directories, relative to the root directory of the Xcode project, to put generated Unity iOS build output.
/// </summary>
private const string ClassesProjectPath = XcodeProjectName + "/Unity/Classes";
private const string LibrariesProjectPath = XcodeProjectName + "/Unity/Libraries";

/// <summary>
/// Path, relative to the root directory of the Xcode project, to put information about generated Unity output.
/// </summary>
private const string ExportsConfigProjectPath = XcodeProjectName + "/Unity/Exports.xcconfig";

private const string PbxFilePath = XcodeProjectName + ".xcodeproj/project.pbxproj";

private const string BackupExtension = ".bak";

/// <summary>
/// The identifier added to touched file to avoid double edits when building to existing directory without
/// replace existing content.
/// </summary>
private const string TouchedMarker = "https://github.com/jiulongw/swift-unity#v1";

[PostProcessBuild]
public static void OnPostBuild(BuildTarget target, string pathToBuiltProject)
{
if (target != BuildTarget.iOS)
{
return;
}

PatchUnityNativeCode(pathToBuiltProject);

UpdateUnityIOSExports(pathToBuiltProject);

UpdateUnityProjectFiles(pathToBuiltProject);
}

/// <summary>
/// Writes current Unity version and output path to 'Exports.xcconfig' file.
/// </summary>
private static void UpdateUnityIOSExports(string pathToBuiltProject)
{
var config = new StringBuilder();
config.AppendFormat("UNITY_RUNTIME_VERSION = {0};", Application.unityVersion);
config.AppendLine();
config.AppendFormat("UNITY_IOS_EXPORT_PATH = {0};", pathToBuiltProject);
config.AppendLine();

var configPath = Path.Combine(XcodeProjectRoot, ExportsConfigProjectPath);
var configDir = Path.GetDirectoryName(configPath);
if (!Directory.Exists(configDir))
{
Directory.CreateDirectory(configDir);
}

File.WriteAllText(configPath, config.ToString());
}

/// <summary>
/// Enumerates Unity output files and add necessary files into Xcode project file.
/// It only add a reference entry into project.pbx file, without actually copy it.
/// Xcode pre-build script will copy files into correct location.
/// </summary>
private static void UpdateUnityProjectFiles(string pathToBuiltProject)
{
var pbx = new PBXProject();
var pbxPath = Path.Combine(XcodeProjectRoot, PbxFilePath);
pbx.ReadFromFile(pbxPath);

ProcessUnityDirectory(
pbx,
Path.Combine(pathToBuiltProject, "Classes"),
Path.Combine(XcodeProjectRoot, ClassesProjectPath),
ClassesProjectPath);

ProcessUnityDirectory(
pbx,
Path.Combine(pathToBuiltProject, "Libraries"),
Path.Combine(XcodeProjectRoot, LibrariesProjectPath),
LibrariesProjectPath);

pbx.WriteToFile(pbxPath);
}

/// <summary>
/// Update pbx project file by adding src files and removing extra files that
/// exists in dest but not in src any more.
///
/// This method only updates the pbx project file. It does not copy or delete
/// files in Swift Xcode project. The Swift Xcode project will do copy and delete
/// during build, and it should copy files if contents are different, regardless
/// of the file time.
/// </summary>
/// <param name="pbx">The pbx project.</param>
/// <param name="src">The directory where Unity project is built.</param>
/// <param name="dest">The directory of the Swift Xcode project where the
/// Unity project is embedded into.</param>
/// <param name="projectPathPrefix">The prefix of project path in Swift Xcode
/// project for Unity code files. E.g. "DempApp/Unity/Classes" for all files
/// under Classes folder from Unity iOS build output.</param>
private static void ProcessUnityDirectory(PBXProject pbx, string src, string dest, string projectPathPrefix)
{
var targetGuid = pbx.TargetGuidByName(XcodeProjectName);
if (string.IsNullOrEmpty(targetGuid)) {
throw new Exception(string.Format("TargetGuid could not be found for '{0}'", XcodeProjectName));
}

// newFiles: array of file names in build output that do not exist in project.pbx manifest.
// extraFiles: array of file names in project.pbx manifest that do not exist in build output.
// Build output files that already exist in project.pbx manifest will be skipped to minimize
// changes to project.pbx file.
string[] newFiles, extraFiles;
CompareDirectories(src, dest, out newFiles, out extraFiles);

foreach (var f in newFiles)
{
if (ShouldExcludeFile(f))
{
continue;
}

var projPath = Path.Combine(projectPathPrefix, f);
if (!pbx.ContainsFileByProjectPath(projPath))
{
var guid = pbx.AddFile(projPath, projPath);
pbx.AddFileToBuild(targetGuid, guid);

Debug.LogFormat("Added file to pbx: '{0}'", projPath);
}
}

foreach (var f in extraFiles)
{
var projPath = Path.Combine(projectPathPrefix, f);
if (pbx.ContainsFileByProjectPath(projPath))
{
var guid = pbx.FindFileGuidByProjectPath(projPath);
pbx.RemoveFile(guid);

Debug.LogFormat("Removed file from pbx: '{0}'", projPath);
}
}
}

/// <summary>
/// Compares the directories. Returns files that exists in src and
/// extra files that exists in dest but not in src any more.
/// </summary>
private static void CompareDirectories(string src, string dest, out string[] srcFiles, out string[] extraFiles)
{
srcFiles = GetFilesRelativePath(src);

var destFiles = GetFilesRelativePath(dest);
var extraFilesSet = new HashSet<string>(destFiles);

extraFilesSet.ExceptWith(srcFiles);
extraFiles = extraFilesSet.ToArray();
}

private static string[] GetFilesRelativePath(string directory)
{
var results = new List<string>();

if (Directory.Exists(directory))
{
foreach (var path in Directory.GetFiles(directory, "*", SearchOption.AllDirectories))
{
var relative = path.Substring(directory.Length).TrimStart('/');
results.Add(relative);
}
}

return results.ToArray();
}

private static bool ShouldExcludeFile(string fileName)
{
if (fileName.EndsWith(".bak", StringComparison.OrdinalIgnoreCase))
{
return true;
}

return false;
}

/// <summary>
/// Make necessary changes to Unity build output that enables it to be embedded into existing Xcode project.
/// </summary>
private static void PatchUnityNativeCode(string pathToBuiltProject)
{
EditMainMM(Path.Combine(pathToBuiltProject, "Classes/main.mm"));
EditUnityAppControllerH(Path.Combine(pathToBuiltProject, "Classes/UnityAppController.h"));
EditUnityAppControllerMM(Path.Combine(pathToBuiltProject, "Classes/UnityAppController.mm"));

if (Application.unityVersion == "2017.1.1f1")
{
EditMetalHelperMM(Path.Combine(pathToBuiltProject, "Classes/Unity/MetalHelper.mm"));
}

// TODO: Parse unity version number and do range comparison.
if (Application.unityVersion.StartsWith("2017.3.0f")
|| Application.unityVersion.StartsWith("2017.3.1f")
|| Application.unityVersion.StartsWith("2017.4.1f")
|| Application.unityVersion.StartsWith("2017.4.2f"))
{
EditSplashScreenMM(Path.Combine(pathToBuiltProject, "Classes/UI/SplashScreen.mm"));
}
}

/// <summary>
/// Edit 'main.mm': removes 'main' entry that would conflict with the Xcode project it embeds into.
/// </summary>
private static void EditMainMM(string path)
{
EditCodeFile(path, line =>
{
if (line.TrimStart().StartsWith("int main", StringComparison.Ordinal))
{
return line.Replace("int main", "int old_main");
}

return line;
});
}

/// <summary>
/// Edit 'UnityAppController.h': returns 'UnityAppController' from 'AppDelegate' class.
/// </summary>
private static void EditUnityAppControllerH(string path)
{
var inScope = false;
var markerDetected = false;
var markerAdded = false;

EditCodeFile(path, line =>
{
markerDetected |= line.Contains(TouchedMarker);
inScope |= line.Contains("inline UnityAppController");

if (inScope && !markerDetected)
{
if (line.Trim() == "}")
{
inScope = false;

return new string[]
{
"// }",
"",
"NS_INLINE UnityAppController* GetAppController()",
"{",
" NSObject<UIApplicationDelegate>* delegate = [UIApplication sharedApplication].delegate;",
@" UnityAppController* currentUnityController = (UnityAppController*)[delegate valueForKey: @""currentUnityController""];",
" return currentUnityController;",
"}",
};
}

if (!markerAdded)
{
markerAdded = true;
return new string[]
{
"// Modified by " + TouchedMarker,
"// " + line,
};
}

return new string[] { "// " + line };
}

return new string[] { line };
});
}

/// <summary>
/// Edit 'UnityAppController.mm': triggers 'UnityReady' notification after Unity is actually started.
/// </summary>
private static void EditUnityAppControllerMM(string path)
{
var inScope = false;
var markerDetected = false;

EditCodeFile(path, line =>
{
inScope |= line.Contains("- (void)startUnity:");
markerDetected |= inScope && line.Contains(TouchedMarker);

if (inScope && line.Trim() == "}")
{
inScope = false;

if (markerDetected)
{
return new string[] { line };
}
else
{
return new string[]
{
" // Modified by " + TouchedMarker,
" // Post a notification so that Swift can load unity view once started.",
@" [[NSNotificationCenter defaultCenter] postNotificationName: @""UnityReady"" object:self];",
"}",
};
}
}

return new string[] { line };
});
}

/// <summary>
/// Edit 'MetalHelper.mm': fixes a bug (only in 2017.1.1f1) that causes crash.
/// </summary>
private static void EditMetalHelperMM(string path)
{
var markerDetected = false;

EditCodeFile(path, line =>
{
markerDetected |= line.Contains(TouchedMarker);

if (!markerDetected && line.Trim() == "surface->stencilRB = [surface->device newTextureWithDescriptor: stencilTexDesc];")
{
return new string[]
{
"",
" // Modified by " + TouchedMarker,
" // Default stencilTexDesc.usage has flag 1. In runtime it will cause assertion failure:",
" // validateRenderPassDescriptor:589: failed assertion `Texture at stencilAttachment has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)'",
" // Adding MTLTextureUsageRenderTarget seems to fix this issue.",
" stencilTexDesc.usage |= MTLTextureUsageRenderTarget;",
line,
};
}

return new string[] { line };
});
}

/// <summary>
/// Edit 'SplashScreen.mm': Unity introduces its own 'LaunchScreen.storyboard' since 2017.3.0f3.
/// Disable it here and use Swift project's launch screen instead.
/// </summary>
private static void EditSplashScreenMM(string path) {
var markerDetected = false;
var markerAdded = false;
var inScope = false;
var level = 0;

EditCodeFile(path, line =>
{
inScope |= line.Trim() == "void ShowSplashScreen(UIWindow* window)";
markerDetected |= line.Contains(TouchedMarker);

if (inScope && !markerDetected)
{
if (line.Trim() == "{")
{
level++;
}
else if (line.Trim() == "}")
{
level--;
}

if (line.Trim() == "}" && level == 0)
{
inScope = false;
}

if (level > 0 && line.Trim().StartsWith("bool hasStoryboard"))
{
return new string[]
{
" // " + line,
" bool hasStoryboard = false;",
};
}

if (!markerAdded)
{
markerAdded = true;
return new string[]
{
"// Modified by " + TouchedMarker,
line,
};
}
}

return new string[] { line };
});
}

private static void EditCodeFile(string path, Func<string, string> lineHandler)
{
EditCodeFile(path, line =>
{
return new string[] { lineHandler(line) };
});
}

private static void EditCodeFile(string path, Func<string, IEnumerable<string>> lineHandler)
{
var bakPath = path + ".bak";
if (File.Exists(bakPath))
{
File.Delete(bakPath);
}

File.Move(path, bakPath);

using (var reader = File.OpenText(bakPath))
using (var stream = File.Create(path))
using (var writer = new StreamWriter(stream))
{
string line;
while ((line = reader.ReadLine()) != null)
{
var outputs = lineHandler(line);
foreach (var o in outputs)
{
writer.WriteLine(o);
}
}
}
}
}

#endif

导出Xcode工程文件,在嵌入的Swift工程下会自动生成Unity文件夹,该文件夹内容由以上脚本生成。

下载以下仓库,将demo下的unity文件夹复制到项目文件夹中,与xcodeproj工程文件同级,与上面的文件夹内容合并。将导出的工程文件的Classes、Libraries和Data文件夹也复制到项目文件夹中,与xcodeproj工程文件同级,与上面的文件夹内容合并。

1
https://github.com/jiulongw/swift-unity

用Xcode打开工程,将Unity文件夹下的Classes和Libraries文件夹拖动到左侧文件树,选择Copy items if needed和Create groups。然后将Data文件夹拖动到左侧文件树,选择Create folder references。

点击工程文件,在左侧PROJECT下选择工程,在右侧General选项卡的Configurations下选择Unity配置文件,注意Debug和Release都应当选择。

然后用以下仓库中的AppDelegate.swift、Main.storyboard和ViewController.swift替换掉原工程的对应文件,具体方法为先以Move to trash的方式删除原工程的文件,再通过Copy items if needed和Create groups的方式拖动加入以上文件。

1
https://github.com/jiulongw/swift-unity/tree/master/demo/xcode/DemoApp

为防止错误,可删除LaunchScreen.storyboard,并在工程文件属性General选项卡的Launch Screen File下选择Main.storyboard。注意删除时应当以Remove Reference的方式。完成后即可运行。

常见问题

Modifications to the > layout engine must not be performed from a background thread after it has been accessed from the main thread

修改视图内容的代码放到了后台线程导致该错误。修改为如下即可。

1
2
3
DispatchQueue.main.async {
# 原出错代码
}

Java

资料

1
https://github.com/akullpp/awesome-java

Arduino

教程

1
https://www.w3cschool.cn/arduino/arduino_for_loop.html

汇编

资料

1
https://www.hack520.com/

调试器

TASM

1
https://cs.nyu.edu/courses/fall99/V22.0201-002/debug.html

HTML与微信小程序

HTML教程

1
2
https://www.w3school.com.cn/html/index.asp
https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Getting_started

数据库系统

Mysql

安装

从以下网站下载并安装。

1
https://dev.mysql.com/downloads/mysql/

配置环境变量

对于Mac,打开用户目录下的.bash_profile(zsh为.zshrc)文件,在末尾复制以下内容并保存。

1
2
export PATH=$PATH:/usr/local/mysql/bin
export PATH=$PATH:/usr/local/mysql/support-files

打开终端并输入以下命令。

1
2
3
4
5
// bash
source ~/.bash_profile

// zsh
source ~/.zshrc

启动服务

在终端输入以下命令。

1
2
sudo mysql.server start
sudo mysql.server status

进入命令行

输入以下命令即可。其中-u表示username,-p表示password。

1
mysql -u <用户名> -p [<密码>]

基本操作

创建数据库

1
create database <数据库名>;

创建新表

1
2
3
4
5
6
7
create table <表名>{
<键名> <键值> (<属性>),
(Cno tinyint not null auto_increment primary key,)
<键名> <键值>,
...
<键名> <键值>
};

插入数据

注意,如果键的属性为auto_increment,则插入数据时可以不用写该值。

1
insert into <表名>(<键名>,...,<键名>) values (<键值>,...,<键值>);

更新数据

如果有外码约束,需先消除外码约束。

1
2
3
4
5
6
7
8
// 取消外码约束
set foreign_key_checks = 0;

// 更新数据
update <表名> set <键名>=<键值> where <条件>;

// 恢复外码约束
set foreign_key_checks = 1;

删除数据

如果有外码约束,需先消除外码约束。

1
2
3
4
5
6
7
8
// 取消外码约束
set foreign_key_checks = 0;

// 更新数据
delete from <表名> where <条件>;

// 恢复外码约束
set foreign_key_checks = 1;

端口号相关

查询

在mysql命令行输入以下命令即可。

1
show global variables like ‘port’;

修改

对于Mac,打开配置文件/Library/LaunchDaemons/com.oracle.oss.mysql.mysqld.plist,修改-port=后面的数字即可。

CLion集成

具体可参照以下链接。

1
https://www.jetbrains.com/help/clion/connecting-to-a-database.html

配置ODBC

以下操作均在Windows下完成。

安装驱动

在安装MySQL时选中ODBC组件即可。

配置ODBC源

打开控制面板,选择管理工具-ODBC数据源,点击添加,选择MySQL ODBC 8.0 ANSI Driver,按照信息填写即可。

Python

机器学习

可使用sklearn框架。

以训练图片识别为例,需要提取特征、训练与测试。

提取特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import sklearn

# 从文件夹中读取图片集,并收集其label
# label为每个图片的名称,一般需要提前对图片重命名为对应的实体,如飞机等
def load_data_from_folder(self, dir):
# read all images into an image collection
ic = io.ImageCollection(self.folder + dir + '*.bmp',load_func=self.imread_convert)

# create one large array of image data
data = io.concatenate_images(ic)

# extract labels from image names
labels = np.array(ic.files)
for i, f in enumerate(labels):
m = re.search('_', f)
labels[i] = (f[len(dir):m.start()]).split('/')[-1]

return(data,labels)

# 训练集
(train_raw, train_labels) = img_clf.load_data_from_folder('train/')
# 测试集
(test_raw, test_labels) = img_clf.load_data_from_folder('test/')


# HOG方法
# 具体参数参见以下链接
# https://blog.csdn.net/weixin_44791964/article/details/103549605
# https://scikit-image.org/docs/dev/api/skimage.feature.html#skimage.feature.hog
#
train_feature = sklearn.feature.hog(train_raw, ...)
test_feature = sklearn.feature.hog(test_raw, ...)
# Canny方法
# image_feature = sklearn.feature.canny(train_raw, ...)
# test_feature = sklearn.feature.canny(test_raw, ...)

训练与测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# KNeighbors法
classifier = KNeighborsClassifier()
# MLP法
# classifier = MLPClassifier()

# 用训练集训练模型
# 具体参数参见以下链接
# https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html#sklearn.neighbors.KNeighborsClassifier.fit
#
classifier.fit(train_feature, train_labels)

# 用测试集测试模型并得出识别结果
# 具体参数参见以下链接
# https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html#sklearn.neighbors.KNeighborsClassifier.predict
#
predicted_labels = classifier.predict(test_feature)

# 根据识别出的结果和实际的结果,生成混淆矩阵
# 具体参数参见以下链接
# https://scikit-learn.org/stable/modules/generated/sklearn.metrics.confusion_matrix.html
# https://machinelearningmastery.com/confusion-matrix-machine-learning/
#
confusion_matrix = metrics.confusion_matrix(test_labels, predicted_labels)

# 根据混淆矩阵计算准确性
# 具体参数参见以下链接
# https://scikit-learn.org/stable/modules/generated/sklearn.metrics.accuracy_score.html
#
accuracy = metrics.accuracy_score(test_labels, predicted_labels)

# 根据混淆矩阵计算F1得分
# 具体参数参见以下链接
# https://scikit-learn.org/stable/modules/generated/sklearn.metrics.f1_score.html
#
f1 = sklearn.metrics.f1_score(test_labels, predicted_labels)

计算机网络

数字证书/签名

1
2
https://hoochanlon.github.io/fq-book/#/abc/dc_zhenshu
https://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html

实验

阿里云MQTT

使用MQTT客户端连接阿里云MQTT服务器

1
https://yq.aliyun.com/articles/592279

使用TinyLink完成MQTT通信

1
http://linklab.tinylink.cn/static/tutorial/tutorial05.html

阿里云IoT Studio物模型入门

1
http://linklab.tinylink.cn/static/tutorial/tutorial06.html

GNS3

安装和使用

1
2
3
https://github.com/GNS3/gns3-gui/releases
https://github.com/zhang0peter/gns3-intro/
https://blog.csdn.net/zhangpeterx/article/details/86407065

RIP配置

1
2
3
https://jingyan.baidu.com/article/00a07f38043ff782d028dc23.html
https://jingyan.baidu.com/article/a65957f4ec38d224e77f9b4d.html
https://zhidao.baidu.com/question/2051649583868305907.html

简单Socket连接

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <winsock2.h>

#pragma comment(lib,"ws2_32.lib")

#define MAXLINE 4096

int main()
{
SOCKET listenfd;
int connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE + 1];
int n;

// windows 下需要初始化socket
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(sockVersion, &wsaData) != 0)
{
printf("init socket error: %s(error: %d)\n", strerror(errno), errno);
system("pause");
return 0;
}
// end socket初始化

if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("create socket error: %s(error: %d)\n", strerror(errno), errno);
system("pause");
return 0;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);

if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)
{
printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
system("pause");
return 0;
}

if (listen(listenfd, 10) == -1)
{
printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
system("pause");
return 0;
}

printf("======waiting for client's request======\n");
if ((connfd = accept(listenfd, NULL, NULL)) == -1)
{
printf("accept socket error: %s(errno: %d)", strerror(errno), errno);
system("pause");
return 0;
}
// 接收消息
while (true)
{
memset(buff, 0, sizeof(buff));
n = recv(connfd, buff, MAXLINE, 0);
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
}

// 释放socket
closesocket(connfd);
closesocket(listenfd);

system("pause");
return 0;
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <winsock2.h>
#include <WS2tcpip.h>

#pragma comment(lib,"ws2_32.lib")

int main()
{
int sockfd, n;
char recvline[4096], sendline[4096];
struct sockaddr_in servaddr;

WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(sockVersion, &wsaData) != 0)
{
printf("init socket error: %s(error: %d)\n", strerror(errno), errno);
system("pause");
return 0;
}

printf("please enter service ipv4 address: ");
char IPV4[20];
gets(IPV4);

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
system("pause");
return 0;
}

memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);

if (inet_pton(AF_INET, IPV4, &servaddr.sin_addr) <= 0){
printf("inet_pton error for %s\n", IPV4);
system("pause");
return 0;
}

if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
printf("connect error: %s(errno: %d)\n", strerror(errno), errno);
system("pause");
return 0;
}
while (true)
{
printf("send msg to server: \n");
memset(sendline, 0, sizeof(sendline));
fgets(sendline, 4096, stdin);
if (send(sockfd, sendline, strlen(sendline), 0) < 0)
{
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
system("pause");
return 0;
}
}
closesocket(sockfd);
system("pause");
return 0;
}

常见问题

网站PING不通但能访问

有可能为该网站禁用了ICMP回应或者开启了ICMP过滤。

习题集

第一章:引言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
(1) Imagine that you have trained your St. Bernard, Bernie, to carry a box of three 8mm tapes instead of a flask of brandy. (When your disk fills up, you consider that an emergency.) These tapes each contain 7 gigabytes. The dog can travel to your side, wherever you may be, at 18 km/hour. For what range of distances does Bernie have a higher data rate than a transmission line whose data rate (excluding overhead) is 150 Mbps?

Sol.

7 * 1024 * 8 * 3 / (d / 5) = 150
d = 5734.4m

Wrong!

1 kbps = 1000 bit/s
1024 should be 1000
d = 5600m

(2) An alternative to a LAN is simply a big timesharing system with terminals for all users. Give two advantages of a client-server system using a LAN.

Sol.

(1) 可以更好地利用网络资源,timesharing system容易造成资源浪费
(2) 构建简单,客户机和服务机上各运行一个进程即可

(3) The performance of a client-server system is influenced by two network factors: the bandwidth of the network (how many bits/sec it can transport) and the latency (how many seconds it takes for the first bit to get from the client to the server). Give an example of a network that exhibits high bandwidth and high latency. Then give an example of one with low bandwidth and low latency.

Sol.

(1). 下载大型文件,建立连接需要很久,一旦建立下载很快
(2). 语音通话,不能有高延迟

A transcontinental fiber link might have many gigabits/sec of bandwidth, but the latency will also be high due to the speed of light propagation over thousands of kilometers. In contrast, a 56-kbps modem calling a computer in the same building has low bandwidth and low latency.

(4) Besides bandwidth and latency, what other parameter is needed to give a good characterization of the quality of service offered by a network used for digitized voice traffic?

Sol.

(1) 丢包率
(2) 安全性
(3) 断点传输
(4) 对语音通话而言,统一的传输时间也很重要

(5) A factor in the delay of a store-and-forward packet-switching system is how long it takes to store and forward a packet through a switch. If switching time is 10 μsec, is this likely to be a major factor in the response of a client-server system where the client is in New York and the server is in California? Assume the propagation speed in copper and fiber to be 2/3 the speed of light in vacuum.

Sol.

2000km / 200000km/s = 10ms

It won't be a major factor.

(6) A client-server system uses a satellite network, with the satellite at a height of 40,000 km. What is the best-case delay in response to a request?

Sol.

40,000 * 2 / 300000 = 267ms

(7) In the future, when everyone has a home terminal connected to a computer network, instant public referendums on important pending legislation will become possible. Ultimately, existing legislatures
could be eliminated, to let the will of the people be expressed directly. The positive aspects of such a
direct democracy are fairly obvious; discuss some of the negative aspects.

Sol.

安全性存在问题

(8) A collection of five routers is to be connected in a point-to-point subnet. Between each pair of routers,
the designers may put a high-speed line, a medium-speed line, a low-speed line, or no line. If it takes 100 ms of computer time to generate and inspect each topology, how long will it take to inspect all of them?

Sol.

4 ^ (4 + 3 + 2 + 1) * 100ms = more than one day

(9) A disadvantage of a broadcast subnet is the capacity wasted when multiple hosts attempt to access the channel at the same time. As a simplistic example, suppose that time is divided into discrete slots, with each of the n hosts attempting to use the channel with probability p during each slot. What fraction of the slots are wasted due to collisions?

Sol.

P = 1 - n * p * (1 - p) ^ (n - 1) - p ^ n

(10) What are two reasons for using layered protocols?

Sol.

(1) 使得层与层之间不会影响,一层内的协议更新不会使得整个网络连接发生变化
(2) 把大的任务分细,明确各层提供的服务

(11) The president of the Specialty Paint Corp. gets the idea to work with a local beer brewer to produce an
invisible beer can (as an anti-litter measure). The president tells her legal department to look into it, and they in turn ask engineering for help. As a result, the chief engineer calls his counterpart at the other company to discuss the technical aspects of the project. The engineers then report back to their respective legal departments, which then confer by telephone to arrange the legal aspects. Finally, the two corporate presidents discuss the financial side of the deal. Is this an example of a multilayer protocol in the sense of the OSI model?

Sol.

除了第一层(最底层,这里是engineer),高层之间不能有直接通信。

(12) What is the principal difference between connectionless communication and connection-oriented communication?

Sol.

面向连接的服务是先建立连接再通信。无连接服务是在传输时候带上目标地址,然后交由网络去通信。

(13) Two networks each provide reliable connection-oriented service. One of them offers a reliable byte stream and the other offers a reliable message stream. Are these identical? If so, why is the distinction made? If not, give an example of how they differ.

Sol.

message的话每条信息有确定边界,而byte stream则没有确定边界。

(14) What does ''negotiation'' mean when discussing network protocols? Give an example.

Sol.

协商就是建立一个双方接受的通信规则。

(15) Which of the OSI layers handles each of the following:
a. (a) Dividing the transmitted bit stream into frames.
b. (b) Determining which route through the subnet to use.

Sol.

Transmission Layer and Network Layer

Wrong!

Data link layer and Network layer

这里很重要,数据是在data link层被划分为帧(frame)的

(16) If the unit exchanged at the data link level is called a frame and the unit exchanged at the network level
is called a packet, do frames encapsulate packets or do packets encapsulate frames? Explain your
answer.

Sol.

高层的应该被封到低层里去。

(17) A system has an n-layer protocol hierarchy. Applications generate messages of length M bytes. At each
of the layers, an h-byte header is added. What fraction of the network bandwidth is filled with headers?

Sol.

hn / (M + hn)

(18) List two ways in which the OSI reference model and the TCP/IP reference model are the same. Now list
two ways in which they differ.

Sol.

OSI:
Application
Representation
Session
Transmission
Network
Data link
Physical

TCP/IP:
Application: HTTP, SMTP, RTP, DNS
Transmission: TCP, UDP
internet: IP (Internet Protocal), ICMP
Link: DSL, SONET, 802.11, Ethernet

(19) What is the main difference between TCP and UDP?

Sol.

TCP is connection oriented, reliable, fast.
UDP is connectionless oriented, unreliable, slow.

(20) When a file is transferred between two computers, two acknowledgement strategies are possible. In the first one, the file is chopped up into packets, which are individually acknowledged by the receiver, but the file transfer as a whole is not acknowledged. In the second one, the packets are not acknowledged
individually, but the entire file is acknowledged when it arrives. Discuss these two approaches.

Sol.

If the network is reliable, use the second one.

(21) How long was a bit on the original 802.3 standard in meters? Use a transmission speed of 10 Mbps and
assume the propagation speed in coax is 2/3 the speed of light in vacuum.

Sol.

802.3是以太网,这里计算出来是20米

(22) An image is 1024 x 768 pixels with 3 bytes/pixel. Assume the image is uncompressed. How long does it
take to transmit it over a 56-kbps modem channel? Over a 1-Mbps cable modem? Over a 10-Mbps Ethernet? Over 100-Mbps Ethernet?

Sol.

337s, 19s, 1.9s, 0.19s

(23) Ethernet and wireless networks have some similarities and some differences. One property of Ethernet is that only one frame at a time can be transmitted on an Ethernet. Does 802.11 share this property with Ethernet? Discuss your answer.

Sol.

Wireless networks also transmit one frame at a time, but determining when one can send is more difficult. Ethernet is a broadcast bus so the sender can just listen to see if there is any traffic before sending. Due to range issues with wireless, a sender cannot be sure that there are not other transmissions just because it cannot "hear" other transmissions. Wireless senders use either a central base station or use methods discussed in Chapter 4 to avoid collisions.

(24) Wireless networks are easy to install, which makes them inexpensive since installation costs usually far overshadow equipment costs. Nevertheless, they also have some disadvantages. Name two of them.

Sol.

无法控制范围,容易被人利用。任何人可以接收到传送中的包。
此外,传输更慢。

(25) List two advantages and two disadvantages of having international standards for network protocols.

Sol.

好处:统一的标准使得兼容性更好,成本降低。
坏处:不好的标准可能很难被淘汰,比如OSI比TCP/IP先进但没有被采用。

第五章:网络层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
(1) Give two example computer applications for which connection-oriented service is appropriate. Now give two examples for which connectionless service is best.

Sol.

面向连接服务:SSH,文件传输

无连接服务:视频通话,游戏在线对战,需要快速应答的服务一般需要无连接服务。

(2) Are there any circumstances when connection-oriented service will (or at least should) deliver packets out of order? Explain.

Sol.

接收端将无法正确接收数据,比如视频传输,顺序一错视频就乱了。

Wrong!

Circumstance是情况的意思,在terminal中ctrl-c应该被最先传送,而不是排在队尾。

(3) Datagram subnets route each packet as a separate unit, independent of all others. Virtual-circuit subnets do not have to do this, since each data packet follows a predetermined route. Does this observation mean that virtual-circuit subnets do not need the capability to route isolated packets from an arbitrary source to an arbitrary destination? Explain your answer.

Sol.

并不是如此,虚电路网络对不同方向的数据包需要建立不同的虚电路,因此也要具备这样的能力。

补充:在建立虚电路的时候需要把setup package从任意端传输到任意接收方。

(4) Give three examples of protocol parameters that might be negotiated when a connection is set up.

Sol.

IP协议,协商IP包的最大跳数,是否可以分段,还有出错时的处理方式。

(5) Consider the following design problem concerning implementation of virtual-circuit service. If virtual circuits are used internal to the subnet, each data packet must have a 3-byte header and each router must tie up 8 bytes of storage for circuit identification. If datagrams are used internally, 15-byte headers are needed but no router table space is required. Transmission capacity costs 1 cent per 106 bytes, per hop. Very fast router memory can be purchased for 1 cent per byte and is depreciated over two years, assuming a 40-hour business week. The statistically average session runs for 1000 sec, in which time 200 packets are transmitted. The mean packet requires four hops. Which implementation is cheaper, and by how much?

Sol.

Virtual Circuits: 200 * 3 * 4 / 10 ^ 6 + 1000 * 8 * 5 / 2 / 52 / 40 / 3600
Datagrams: 15 * 4 * 200 / 10 ^ 6

(6) Assuming that all routers and hosts are working properly and that all software in both is free of all errors, is there any chance, however small, that a packet will be delivered to the wrong destination?

Sol.

有可能,当有ip冲突或者传输过程中发生ip地址变更的情况。

Wrong!

答案是错误可能出在低层上,比如物理层

(7) Give a simple heuristic for finding two paths through a network from a given source to a given destination that can survive the loss of any communication line (assuming two such paths exist). The routers are considered reliable enough, so it is not necessary to worry about the possibility of router crashes.

Sol.

先找出一条最短路径,再把这条路径删除,找出另一条最短路径。只要两条路径没有公共部分即可。

(8) Consider the subnet of Fig.5-12(a). Distance vector routing is used, and the following vectors have just come in to router C: from B: (5, 0, 8, 12, 6, 2); from D: (16, 12, 6, 0, 9, 10); and from E: (7, 6, 3, 9, 0, 4). The measured delays to B, D, and E, are 6, 3, and 5, respectively. What is C's new routing table? Give both the outgoing line to use and the expected delay.

Sol.

这题是距离矢量算法。

C 到 A 11 经过 B
A 11 B
B 6 B
C 0 -
D 3 D
E 5 E
F 8 B
(9) If delays are recorded as 8-bit numbers in a 50-router network, and delay vectors are exchanged twice a second, how much bandwidth per (full-duplex) line is chewed up by the distributed routing algorithm? Assume that each router has three lines to other routers.

Sol.

每个路由器需要维护一张400bit的表,因此传输0.5s一次会浪费单个方向800bps的带宽

(10) In Fig. 5-14 the Boolean OR of the two sets of ACF bits are 111 in every row. Is this just an accident here, or does it hold for all subnets under all circumstances?

Sol.

这题讲的是链路状态路由算法。

这个永远是对的,ACK标志标志表示来自哪里,它可能由两条路线过来,而发送标志表示要发送往哪里。

(11) For hierarchical routing with 4800 routers, what region and cluster sizes should be chosen to minimize the size of the routing table for a three-layer hierarchy? A good starting place is the hypothesis that a solution with k clusters of k regions of k routers is close to optimal, which means that k is about the cube root of 4800 (around 16). Use trial and error to check out combinations where all three parameters are in the general vicinity of 16.

Sol.

这题讲的是层次路由。

层次可以分许多层,和网络中路由器的数量规模有关。

16 15 20

(12) In the text it was stated that when a mobile host is not at home, packets sent to its home LAN are intercepted by its home agent on that LAN. For an IP network on an 802.3 LAN, how does the home agent accomplish this interception?

Sol.

家乡代理拥有主机的IP地址即可截获,有什么问题吗?

Conceivably it might go into promiscuous mode, reading all frames dropped onto the LAN, but this is very inefficient. Instead, what is normally done is that the home agent tricks the router into thinking it is the mobile host by re- sponding to ARP requests. When the router gets an IP packet destined for the mobile host, it broadcasts an ARP query asking for the 802.3 MAC-level ad- dress of the machine with that IP address. When the mobile host is not around, the home agent responds to the ARP, so the router associates the mobile user’s IP address with the home agent’s 802.3 MAC-level address.

上面是标准答案,实际上就是在ARP广播时相应家乡代理自己的MAC地址,实际上就是拥有了主机的IP地址嘛。

(13) Looking at the subnet of Fig. 5-6, how many packets are generated by a broadcast from B, using a. (a)reverse path forwarding? b. (b)the sink tree?

Sol.

reverse path forwarding,路由器接收到广播包时,检查是否是自己给广播源发包的路径,是的话说明是从最优路径发过来的,这时给所有其他节点发包,最后算下来发了21次

sink tree是汇集树,是由B到所有节点最短路径的集合,这样发的话发14个包,每个树枝必会到一个节点,而且不会重复。

(14) Suppose that node B in Fig. 5-20 has just rebooted and has no routing information in
its tables. It suddenly needs a route to H. It sends out broadcasts with TTL set to 1, 2,
3, and so on. How many rounds does it take to find a route?

Sol.

这题考查自组织网络路由,就是路由器本身也在移动的情况。TTL分别设置为1,2,3是为了使搜索半径不断增大。由于B和H距离为3,因此TTL设置为3的时候可以发现,因此3轮可以发现路由。

(15) As a possible congestion control mechanism in a subnet using virtual circuits internally,
a router could refrain from acknowledging a received packet until (1) it knows its last transmission along the virtual circuit was received successfully and (2) it has a free buffer. For simplicity, assume that the routers use a stop-and-wait protocol and that each virtual circuit has one buffer dedicated to it for each direction of traffic. If it takes T sec to transmit a packet (data or acknowledgement) and there are n routers on the path, what is the rate at which packets are delivered to the destination host? Assume that transmission errors are rare and that the host-router connection is infinitely fast.

Sol.

讲的是拥塞控制,这个方法不好,需要前一个包完全确认了才能传输下一个包。

The protocol is terrible. Let time be slotted in units of T sec. In slot 1 the source router sends the first packet. At the start of slot 2, the second router has received the packet but cannot acknowledge it yet. At the start of slot 3, the third router has received the packet, but it cannot acknowledge it either, so all the routers behind it are still hanging. The first acknowledgement can only be sent when the destination host takes the packet from the destination router. Now the acknowledgement begins propagating back. It takes two full transits of the network, 2(n − 1)T sec, before the source router can send the second packet. Thus, the throughput is one packet every 2(n − 1)T sec.

(17) Describe two major differences between the ECN and the RED method.

Sol.

这两种是拥塞控制算法。

ECN (Explicit Congestion Notification, 显式拥塞通知),路由器在它转发的数据包上打上标记,发出信号,接收方注意到拥塞时,发送应答包的同时告知发送方,让发送方降低传送速率。

RED (Random Early Detection, 随机早期检测), 路由器维护一个运行队列长度的平均值,当超过阈值的时候就开始随机丢弃数据包,快速发送方发现丢包就开始降低发送速率。

主要区别是一个显式通知,一个隐式通知,一个是缓存区开始不够了才通知,另一个是提前预知。

(18) An ATM network uses a token bucket scheme for traffic shaping. A new token is put into the bucket every 5 μsec. Each token is good for one cell, which contains 48 bytes of data. What is the maximum sustainable data rate?

Sol.

With a token every 5 μsec, 200,000 cells/sec can be sent. Each packet holds 48 data bytes or 384 bits. The net data rate is then 76.8 Mbps.

(19) A computer on a 6-Mbps network is regulated by a token bucket. The token bucket is filled at a rate of 1 Mbps. It is initially filled to capacity with 8 megabits. How long can the computer transmit at the full 6 Mbps?

Sol.

8 / (6 - 1) = 1.6s

(21) The CPU in a router can process 2 million packets/sec. The load offered to it is 1.5 million packets/sec. If a route from source to destination contains 10 routers, how much time is spent being queued and serviced by the CPUs?

Sol.

这题用到排队论,服务速率为2,到达速率为1.5,那么服务时间为 1/2million / (1 - 1.5 / 2) = 2us。 10个routers就是20us.

(22) Consider the user of differentiated services with expedited forwarding. Is there a guarantee that expedited packets experience a shorter delay than regular packets? Why or why not?

Sol.

不保证,过多包被标记为加速的,那么可能反而变慢了。

(23) Suppose that host A is connected to a router R 1, R 1 is connected to another router, R 2, and R 2 is connected to host B. Suppose that a TCP message that contains 900 bytes of data and 20 bytes of TCP header is passed to the IP code at host A for delivery to B. Show the Total length, Identification, DF, MF, and Fragment offset fields of the IP header in each packet transmitted over the three links. Assume that link A-R1 can support a maximum frame size of 1024 bytes including a 14-byte frame header, link R1-R2 can support a maximum frame size of 512 bytes, including an 8-byte frame header, and link R2-B can support a maximum frame size of 512 bytes including a 12- byte frame header.

Sol.

Link A-R1 :
Length: 900 + 20 (TCP) + 20 (IP) = 940 Bytes, ID:X , DF:0 , MF:0 , Fragment offset:0
Link R1-R2:
(1) Length = 500; ID = x; DF = 0; MF = 1; Offset = 0 (2) Length = 460; ID = x; DF = 0; MF = 0; Offset = 60
Link R2-B:
(1) Length = 500; ID = x; DF = 0; MF = 1; Offset = 0 (2) Length = 460; ID = x; DF = 0; MF = 0; Offset = 60
不用去考虑数据链路层的成帧部分,IP协议不关心。
(24) A router is blasting out IP packets whose total length (data plus header) is 1024 bytes. Assuming that packets live for 10 sec, what is the maximum line speed the router can operate at without danger of cycling through the IP datagram ID number space?

Sol.

IP包的ID字段拥有16位,因此65536个不同编号

65536 * 1024 * 8 / 10 = 54 Gbps

(25) An IP datagram using the Strict source routing option has to be fragmented. Do you think the option is copied into each fragment, or is it sufficient to just put it in the first fragment? Explain your answer.

Sol.

Strict Source Routing 严格源路由,是IPv4头的可选项,表明该包必须经过指定路由。

必须要在每个fragment都要包括。

(26) Suppose that instead of using 16 bits for the network part of a class B address originally, 20 bits had been used. How many class B networks would there have been?

Sol.

B类地址,前缀10定死,因此18bits可以用,所以有2^18个B类网络。

(28) A network on the Internet has a subnet mask of 255.255.240.0. What is the maximum number of hosts it can handle?

Sol.

4096

(29) 为什么以太网地址不能特定于一个网络,而IP地址却可以?

Sol.

Each Ethernet adapter sold in stores comes hardwired with an Ethernet (MAC) address in it. When burning the address into the card, the manufac- turer has no idea where in the world the card will be used, making the address useless for routing. In contrast, IP addresses are either assigned either stati- cally or dynamically by an ISP or company, which knows exactly how to get to the host getting the IP address.

(30) A large number of consecutive IP address are available starting at 198.16.0.0. Suppose that four organizations, A, B, C, and D, request 4000, 2000, 4000, and 8000 addresses, respectively, and in that order. For each of these, give the first IP address assigned, the last IP address assigned, and the mask in the w.x.y.z/s notation.

Sol.

A: 198.16.0.0 – 198.16.15.255 written as 198.16.0.0/20
B: 198.16.16.0 – 198.23.15.255 written as 198.16.16.0/21
C: 198.16.32.0 – 198.47.15.255 written as 198.16.32.0/20
D: 198.16.64.0 – 198.95.15.255 written as 198.16.64.0/19
(31) A router has just received the following new IP addresses: 57.6.96.0/21, 57.6.104.0/21, 57.6.112.0/21, and 57.6.120.0/21. If all of them use the same outgoing line, can they be aggregated? If so, to what? If not, why not?

Sol.

可以聚合成57.6.96.0/19, 这个时候会有57.6.120.0/21没有被聚合,但是有最大匹配原则所以不要紧。

(32) The set of IP addresses from 29.18.0.0 to 19.18.128.255 has been aggregated to 29.18.0.0/17. However, there is a gap of 1024 unassigned addresses from 29.18.60.0 to 29.18.63.255 that are now suddenly assigned to a host using a different outgoing
line. Is it now necessary to split up the aggregate address into its constituent blocks, add the new block to the table, and then see if any reaggregation is possible? If not, what can be done instead?

Sol.

不需要,因为有最长匹配,所以单独聚合即可。

(34) Many companies have a policy of having two (or more) routers connecting the company to the Internet to provide some redundancy in case one of them goes down. Is this policy still possible with NAT? Explain your answer.

Sol.

After NAT is installed, it is crucial that all the packets pertaining to a single connection pass in and out of the company via the same router, since that is where the mapping is kept. If each router has its own IP address and all traffic belonging to a given connection can be sent to the same router, the mapping can be done correctly and multihoming with NAT can be made to work.

所以是可以的,只要网络中的每个主机发给特定router即可。

(36) Describe a way to reassemble IP fragments at the destination.

Sol.

In the general case, the problem is nontrivial. Fragments may arrive out of order and some may be missing. On a retransmission, the datagram may be fragmented in different-sized chunks. Furthermore, the total size is not known until the last fragment arrives. Probably the only way to handle reassembly is to buffer all the pieces until the last fragment arrives and the size is known. Then build a buffer of the right size, and put the fragments into the buffer, maintaining a bit map with 1 bit per 8 bytes to keep track of which bytes are present in the buffer. When all the bits in the bit map are 1, the datagram is complete.

所以就是等尾巴来,就可以确定总长度,然后等所有分段都来就可以重组了。

(37) Most IP datagram reassembly algorithms have a timer to avoid having a lost fragment tie up reassembly buffers forever. Suppose that a datagram is fragmented into four fragments. The first three fragments arrive, but the last one is delayed. Eventually, the timer goes off and the three fragments in the receiver's memory are discarded. A little later, the last fragment stumbles in. What should be done with it?

Sol.

前三段已经被discard了,那么第四段再来会被当成新的,过一段时间一样被扔。

(38) In both IP and ATM, the checksum covers only the header and not the data. Why do you suppose this design was chosen?

Sol.

其他部分的checksum可以交给上层协议,而且开销太大,此外头的错误非常严重。

(39) A person who lives in Boston travels to Minneapolis, taking her portable computer with her. To her surprise, the LAN at her destination in Minneapolis is a wireless IP LAN, so she does not have to plug in. Is it still necessary to go through the entire business with home agents and foreign agents to make e-mail and other traffic arrive correctly?

Sol.

当然需要,无线网是数据链路层和物理层的事情,和IP层无关,还是要利用家乡代理。

(41) The Protocol field used in the IPv4 header is not present in the fixed IPv6 header. Why not?

Sol.

因为在IPv6头中有一个字段叫下一个头,会说明该字段要交给哪一种上层协议处理。

(42) When the IPv6 protocol is introduced, does the ARP protocol have to be changed? If so, are the changes conceptual or technical?

Sol.

不需要做任何改变,只是IP地址需要更长的空间而已。

第六章:传输层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
(1) In our example transport primitives of Fig.6-2, LISTEN is a blocking call. Is this strictly necessary? If not, explain how a nonblocking primitive could be used. What advantage would this have over the scheme described in the text?

Sol.

The LISTEN call could indicate a willingness to establish new connections but not block. When an attempt to connect was made, the caller could be given a signal. It would then execute, say, OK or REJECT to accept or reject the con- nection. In our original scheme, this flexibility is lacking.

(2) 像bittorrent这种点对点应用如何区分哪个是connect哪个listen呢?

Sol.

可以竞争,也可以随机,也可以由上层协议控制。

(3) 假设网络层是百分百正确的那么三次握手协议会有什么样的变化?

Sol.

第三次握手就没有必要了,主机2收到主机1的请求时,直接就确立连接,返回给1确认信息后,1也确立连接。

(7) Suppose that the clock-driven scheme for generating initial sequence numbers isused with a 15-bit wide clock counter. The clock ticks once every 100 msec, and the maximum packet lifetime is 60 sec. How often need resynchronization take place
a. (a)in the worst case?
b. (b)when the data consumes 240 sequence numbers/min?

Sol.

首先,现在的规定是这样的,通信的时候初始段的序号等于始终序号,因此在x秒的时候同步的信号序号为x,注意,建立连接之后,序号就和时间无关了!!!!

第二,在时间x时,不允许发送x+T(T是lifetime)序号的段,这是为什么呢?因为你这个段会生存T秒,而在随后的T秒内如果要建立一个连接,可能会和你现在发的这个段序号一样的懂吗?

(a) 题目是这样的,假设在70秒的时候,你建立了一个连接,这个时候你发出去的信号序号为70。注意,之后你要发送的序号是71,然后你一直忍着不发(这就是最坏的情况),然后时间绕了一圈回来了,时间变成了11秒,此时你再想发71,对不起,规则不允许,因为在11秒的时候,不允许发71。这就是最坏情况了,所以必须重新同步一次。答案是3276.8-60=3216.8

(b) 这个时候变成追及问题,最后计算出来结果是5361.3

总结,你发送速率与时钟速率越接近,需要同步的间隔就越长。但是如果一上来超过时钟速率就直接GG。

因为这种方法不好所以后来创造了三次握手,两边的初始序列号都是随机值,就不需要那么麻烦的时钟什么的啦。只要双方确认了,后面都好办。

(9) Imagine that a two-way handshake rather than a three-way handshake were used to
set up connections. In other words, the third message was not required. Are deadlocks
now possible? Give an example or show that none exist.

Sol.

有可能出问题,主机2的确认被延迟了,那么可能会建立起一个重复的连接。

(11) Consider the problem of recovering from host crashes (i.e.,Fig.6-18). If the interval
between writing and sending an acknowledgement, or vice versa, can be made relatively small, what are the two best sender-receiver strategies for minimizing the chance of a protocol failure?

Sol.

If the AW or WA time is small, the events AC(W) and WC(A) are unlikely events. The sender should retransmit in state S1; the receiver’s order does not matter.

(13) Discuss the advantages and disadvantages of credits versus sliding window protocols.

Sol.

滑动窗口协议用于流量控制。

The sliding window is simpler, having only one set of parameters (the win- dow edges) to manage. Furthermore, the problem of a window being increased and then decreased, with the segments arriving in the wrong order, does not occur. However, the credit scheme is more flexible, allowing a dynamic management of the buffering, separate from the acknowledgements.

(14) 拥塞控制的公平性方面的策略,加法递增乘法递减(AIMD).

(15) Why does UDP exist? Would it not have been enough to just let user processes send
raw IP packets?

Sol.

无法确认端口。

(17) A client sends a 128-byte request to a server located 100 km away over a 1-gigabit optical fiber. What is the efficiency of the line during the remote procedure call?

Sol.

发送包需要128*8/1G=1.024us, 在路上的时间100km/200000 = 50us, 然后一来一回100us,因此利用率约为1%。
所以说这个时候的制约不是带宽而是距离。

(19) Both UDP and TCP use port numbers to identify the destination entity when delivering a message. Give two reasons for why these protocols invented a new abstract ID (port numbers), instead of using process IDs, which already existed when these protocols were designed.

Sol.

进程id动态变化,不易管理,端口号可以被进程绑定,而且一些知名服务需要用固定端口号。

Here are three reasons. First, process IDs are OS-specific. Using process IDs would have made these protocols OS-dependent. Second, a single process may establish multiple channels of communications. A single process ID (per process) as the destination identifier cannot be used to distinguish between these channels. Third, having processes listen on well-known ports is easy, but well-known process IDs are impossible.

(20) 何时选用基于UDP的RPC,合适使用基于TCP的RPC。

Sol.

RPC是远程过程调用,可以建立客户端-服务器应用。如果请求不是幂等的(幂等是如同计数器加一这种命令,执行次数不同会产生不同结果),那就可以考虑使用UDP。同时,如果传递的数据包并不大,可以考虑使用UDP。

(22) 最小TCP MTU的总长度是多少?包括TCP和IP的开销,但是不包括数据链路层的开销。

Sol.

MTU是最大传输单元,最小的TCP MTU可以设置,如果一台主机不设置的话默认是536+20=556字节的TCP段。Internet要求每台主机至少能够处理556字节的段。

(23) RTP is used to transmit CD-quality audio, which makes a pair of 16-bit samples 44,100 times/sec, one sample for each of the stereo channels. How many packets per second must RTP transmit?

Sol.

Each sample occupies 4 bytes. This gives a total of 256 samples per packet. There are 44,100 samples/sec, so with 256 samples/packet, it takes 44100/256 or 172 packets to transmit one second’s worth of music.

按照标准答案的理解,RTP的一个packet只能传输1024字节,不清楚这个规定是在哪里。

(25) Would it be possible to place the RTP code in the operating system kernel, along with the UDP code? Explain your answer.

Sol.

应该要分开,RTP是基于UDP的协议,其他应用程序也要调用UDP,因此最好可以把两段代码分开。

错了!

Sure. The caller would have to provide all the needed information, but there is no reason RTP could not be in the kernel, just as UDP is.

(26) 主机1的端口p和主机2的端口q之间可能存在多个TCP连接吗?

Sol.

不可能,一对端口之间只能有一个TCP连接。一个进程可以有多个TCP连接。

(27) ACK标志位有什么用?

Sol.

ACK标志位用来表示ACK字段是否有意义。其实在连接已经建立起来之后ACK标志位已经没有意义了,因为ACK是必须的。而在连接建立的过程中,这是非常重要的。

(28) 为什么TCP段的有效载荷是65495字节?

Sol.

因为TCP长度为16位标示,所以最多65535字节,然后去掉TCP头20字节,去掉IP头20字节。剩下65495字节。

(30) Consider the effect of using slow start on a line with a 10-msec round-trip time and no congestion. The receive window is 24 KB and the maximum segment size is 2 KB. How
long does it take before the first full window can be sent?

Sol.

慢速启动是TCP协议中拥塞控制的一个算法,略看。The first bursts contain 2K, 4K, 8K, and 16K bytes, respectively. The next one is 24 KB and occurs after 40 msec.

(31) Suppose that the TCP congestion window is set to 18 KB and a timeout occurs. How big
will the window be if the next four transmission bursts are all successful? Assume that
the maximum segment size is 1 KB.

Sol.

也是拥塞控制的算法,TCP维护一个拥塞窗口,略看。The next transmission will be 1 maximum segment size. Then 2, 4, and 8. So after four successes, it will be 8 KB.

(33) A TCP machine is sending full windows of 65,535 bytes over a 1-Gbps channel that has
a 10-msec one-way delay. What is the maximum throughput achievable? What is the line efficiency?

Sol.

One window can be sent every 20 msec. This gives 50 windows/sec, for a maximum data rate of about 3.3 million bytes/sec. The line efficiency is then 26.4 Mbps/1000 Mbps or 2.6 percent.

因此有延迟的网络传输效率和窗口大小,延迟有很大关系。

(34) What is the fastest line speed at which a host can blast out 1500-byte TCP payloads with a 120-sec maximum packet lifetime without having the sequence numbers wrap around? Take TCP, IP, and Ethernet overhead into consideration. Assume that Ethernet frames may be sent continuously.

Sol.

TCP中每个Byte会占用一个序号,而TCP的sequence number是32位的,所以可以每120s发送2^32Bytes信息,然而1500B信息需要1500+20+20+26(以太网)的段帧来发送因此需要的带宽为2 ^ 32 * 8 * 1566 / 1500 / 120 = 299Mbps。

The goal is to send 2^32 bytes in 120 sec or 35,791,394 payload bytes/sec. This is 23,860 1500-byte frames/sec. The TCP overhead is 20 bytes. The IP overhead is 20 bytes. The Ethernet overhead is 26 bytes. This means that for 1500 bytes of payload, 1566 bytes must be sent. If we are to send 23,860 frames of 1566 bytes every second, we need a line of 299 Mbps. With any- thing faster than this we run the risk of two different TCP segments having the same sequence number at the same time.

(35) 为什么那么多人在为了ipv4的局限性做努力,而对TCP的局限性却没有人这样做。

Sol.

根本原因是IP协议运行在所有路由器上。

IP is a network level protocol while TCP is an end-to-end transport level protocol. Any change in the protocol specification of IP must be incorporated on all routers in the Internet. On the other hand, TCP can works fine as long as the two end points are running compatible versions. Thus, it is possible to have many different versions of TCP running at the same time on different hosts, but not this is not the case with IP.

(36) In a network that has a maximum TPDU size of 128 bytes, a maximum TPDU lifetime of 30 sec, and an 8-bit sequence number, what is the maximum data rate per connection?

Sol.

TPDU:Transport Protocol Data Unit 协议数据单元。

2 ^ 8 * 128 * 8 / 30 = 8.7kbps

(37) Suppose that you are measuring the time to receive a TPDU. When an interrupt occurs,
you read out the system clock in milliseconds. When the TPDU is fully processed, you read out the clock again. You measure 0 msec 270,000 times and 1 msec 730,000 times. How long does it take to receive a TPDU?

Sol.

27次是0ms,73次是1ms,那么平均是730us。

(38) A CPU executes instructions at the rate of 1000 MIPS. Data can be copied 64 bits at a time, with each word copied costing 10 instructions. If an coming packet has to be copied four times, can this system handle a 1-Gbps line? For simplicity, assume that all instructions, even those instructions that read or write memory, run at the full 1000- MIPS rate.

Sol.

1000M * 64 /10 / 4 = 1.6 Gbps > 1Gbps 可以.

(41) For a 1-Gbps network operating over 4000 km, the delay is the limiting factor, not the bandwidth. Consider a MAN with the average source and destination 20 km apart. At what data rate does the round-trip delay due to the speed of light equal the transmission delay for a 1-KB packet?

Sol.

20 * 2 / 200000 = 200us延迟 发送1KB要200us的话,带宽至少要1024 * 8 * 1 / 200u = 40 Mbps

(43) What is the bandwidth-delay product for a 50-Mbps channel on a geostationary satellite? If the packets are all 1500 bytes (including overhead), how big should the window be in packets?

Sol.

The round-trip delay is about 540 msec, so with a 50-Mbps channel the bandwidth-product delay is 27 megabits or 3,375,000 bytes. With packets of 1500 bytes, it takes 2250 packets to fill the pipe, so the window should be at least 2250 packets.

参考教程

Windows环境下的安装gcc(c语言环境)

1
https://www.cnblogs.com/fps2tao/p/11539712.html

为什么有些网站PING不通但又能访问.

1
https://www.cnblogs.com/kunlunmountain/p/5945756.html

iOS - Vision Framework 文字识别

1
https://www.jianshu.com/p/4cea25704191

Xcode折叠函数设置 及快捷键

1
https://blog.csdn.net/liufangbaishi2014/article/details/51602208

笔记:通过storyboard来创建UICollectionView

1
https://blog.csdn.net/shenjie_xsj/article/details/79679760

Swift 5 UICollectionView中cell的对齐方法(重写flowlayout)

1
https://www.jianshu.com/p/e1d8b51fc2b9

iOS中UICollectionView设置cell大小以及间距

1
https://www.liuandy.cn/ios/2017/12/07/1982.html#.YLzEZJMzYYF

swift:如何在单元格中的按钮被点击时获取indexpath.row?

1
https://www.itranslater.com/qa/details/2135067127244653568

iOS AppDelegate方法,监听进程在后台、被杀死事件

1
https://cloud.tencent.com/developer/article/1174777

Swift – 获取点击的坐标位置

1
https://www.jianshu.com/p/21de7f5d6591

Modifications to the > layout engine must not be performed from a background thread after it has been accessed from the main thread

1
https://stackoverflow.com/questions/58087536/modifications-to-the-layout-engine-must-not-be-performed-from-a-background-thr

Push to ViewController without back button

1
https://stackoverflow.com/questions/22301647/push-to-viewcontroller-without-back-button/22301820

ios - 如何从uiviewcontainer控制器中获取父控制器

1
https://kb.kaifa99.com/ios/post_6230237

How to use UIColorPickerViewController in Swift?

1
https://www.swiftpal.io/articles/how-to-use-uicolorpickerviewcontroller-in-swift

为UIImageView添加响应点击事件(Swift)

1
https://blog.csdn.net/feosun/article/details/77942049

ios 子视图获取父视图的视图控制器的方法(oc 和 swift)

1
https://blog.csdn.net/qq_30963589/article/details/82967301

如何以编程方式为UIButton添加系统图标?

1
https://www.thinbug.com/q/37772411

Swift(十)UIView

1
https://www.jianshu.com/p/ff7ffc8129a6

如何优雅的做一个小说阅读功能

1
https://syfh.github.io/2020/07/06/%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E7%9A%84%E5%81%9A%E4%B8%80%E4%B8%AA%E5%B0%8F%E8%AF%B4%E9%98%85%E8%AF%BB%E5%8A%9F%E8%83%BD/

Swift 3 分词

1
https://hicc.me/swift-3-tokenize/

Swift 给一段话分句,或将一句话关键词分组

1
https://www.jianshu.com/p/ef05fc1371b2

Three Ways to Enumerate the Words In a String Using Swift

1
https://medium.com/@sorenlind/three-ways-to-enumerate-the-words-in-a-string-using-swift-7da5504f0062

swift - 使用Swift将NSAttributedString转换为NSString

1
https://www.coder.work/article/251231

iOS:利用代码UIBarButtonItem如何响应事件?

1
https://blog.csdn.net/dchma20242/article/details/101445681

How to let users choose a font with UIFontPickerViewController

1
https://www.hackingwithswift.com/example-code/uikit/how-to-let-users-choose-a-font-with-uifontpickerviewcontroller

SwiftUI UIFontPickerViewController 基础教程

1
https://www.codenong.com/js0c37dafc3eca/

Swift — 為你的 APP 添加自定義字體

1
https://medium.com/jeremy-xue-s-blog/swift-%E7%82%BA%E4%BD%A0%E7%9A%84-app-%E6%B7%BB%E5%8A%A0%E8%87%AA%E5%AE%9A%E7%BE%A9%E5%AD%97%E9%AB%94-1063a7fd30a4

如何在Swift中从数组中删除元素

1
https://qastack.cn/programming/24051633/how-to-remove-an-element-from-an-array-in-swift

iOS 关于LaunchScreen不显示图片的问题

1
https://blog.csdn.net/RollingPin/article/details/103817911

[Swift]LaunchScreen.storyboard设置启动页!UILaunchImages已被iOS弃用,请使用LaunchScreen.storyboard。 - 山青咏芝 - 博客园

1
https://www.cnblogs.com/strengthen/p/10636993.html

具有完全不同布局的通用應用程序

1
https://zh.stackoom.com/question/1qFDT/%E5%85%B7%E6%9C%89%E5%AE%8C%E5%85%A8%E4%B8%8D%E5%90%8C%E5%B8%83%E5%B1%80%E7%9A%84%E9%80%9A%E7%94%A8%E6%87%89%E7%94%A8%E7%A8%8B%E5%BA%8F

快速添加圆角和描边

1
https://vinefiner.github.io/2016/12/01/%E5%BF%AB%E9%80%9F%E6%B7%BB%E5%8A%A0%E5%9C%86%E8%A7%92%E5%92%8C%E6%8F%8F%E8%BE%B9/

iOS 文本转语音(TTS)详解:Swift

1
https://www.geek-share.com/detail/2794338024.html

Swift-视图阴影篇

1
https://www.jianshu.com/p/c47c52e9a8e3

collectionViewCell添加阴影

1
https://www.jianshu.com/p/74894eb7ec3d

在 iPhone & iPad 上顯示 popover 彈出視窗

1
https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E5%95%8F%E9%A1%8C%E8%A7%A3%E7%AD%94%E9%9B%86/%E5%9C%A8-iphone-ipad-%E4%B8%8A%E9%A1%AF%E7%A4%BA-popover-%E5%BD%88%E5%87%BA%E8%A6%96%E7%AA%97-ac196732e557

ykying/UnityInSwift

1
https://github.com/ykying/UnityInSwift

Integrating Unity into native iOS applications

1
https://docs.unity3d.com/Manual/UnityasaLibrary-iOS.html

Reason: image not found

1
https://www.jianshu.com/p/cadd1dc95cf5

How to embed a Unity game into an iOS native Swift App

1
https://medium.com/@IronEqual/how-to-embed-a-unity-game-into-an-ios-native-swift-app-772a0b65c82

swift - 在 Swift 中仅颜色下划线

1
https://www.coder.work/article/7052075

Swift 4.2 自定义相机

1
https://www.jianshu.com/p/4de39664adfa

ios - 镜像(翻转)相机预览层

1
https://www.coder.work/article/443646

swift 获取子视图在父视图的坐标

1
https://blog.csdn.net/wm9028/article/details/81301064

Swift - 触摸事件(点击,移动,抬起等)说明及用例

1
https://www.hangge.com/blog/cache/detail_674.html

iOS开发系列–触摸事件、手势识别、摇晃事件、耳机线控

1
2
https://www.cnblogs.com/kenshincui/p/3950646.html
https://www.cnblogs.com/xjf125/p/4862386.html

在UIView中添加点击事件oc及swift

1
https://blog.csdn.net/timtian008/article/details/51857852

CoderJTao/JTShapedButton

1
https://github.com/CoderJTao/JTShapedButton

How to play a local video with Swift?

1
https://stackoverflow.com/questions/25348877/how-to-play-a-local-video-with-swift

Swift 基本语法03-“if let”和”guard let”

1
https://www.jianshu.com/p/e1fe08c5db1a

解析 View Controller 生命週期:使用 viewDidLayoutSubviews 的時機

1
https://www.appcoda.com.tw/view-controller-lifecycle/

『簡易說明Xcode』顯示下一個畫面方法(由程式觸發的方式 — push)

1
https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E6%95%99%E5%AE%A4/%E7%B0%A1%E6%98%93%E8%AA%AA%E6%98%8Excode%E4%B8%AD%E7%9A%84%E9%A1%AF%E7%A4%BA%E4%B8%8B%E4%B8%80%E5%80%8B%E7%95%AB%E9%9D%A2%E6%96%B9%E6%B3%95-%E7%94%B1%E7%A8%8B%E5%BC%8F%E8%A7%B8%E7%99%BC%E7%9A%84%E6%96%B9%E5%BC%8F-push-e0da619641f7

iOS-如何优雅的隐藏主页面的导航栏,而只展示详细页面的导航栏(UINavigationBar)

1
https://juejin.cn/post/6844903955051315208

【Swift】Swift入門 ~ UIPageViewControllerを使ってみる ~

1
https://swallow-incubate.com/archives/blog/20200313/#basic1

Swift - 页视图控制器(UIPageViewController)的使用

1
2
https://www.hangge.com/blog/cache/detail_1282.html
https://www.hangge.com/blog/cache/detail_1283.html

Increase the size of the indicator in UIPageViewController’s UIPageControl

1
https://stackoverflow.com/questions/42432731/increase-the-size-of-the-indicator-in-uipageviewcontrollers-uipagecontrol

动画UIButton放大和缩小点击

1
http://cn.voidcc.com/question/p-ejgrgomn-pd.html

IOS UIButton详解 & Button缩放旋转位移实例

1
https://my.oschina.net/wolx/blog/359398

subView的添加与移除

1
https://blog.csdn.net/zhuiyi316/article/details/8308858

franobarrio/animation-transition-viewcontroller-easy

1
https://github.com/franobarrio/animation-transition-viewcontroller-easy

ViewController 轉場初階指南:簡單打造酷炫的轉場動畫

1
https://www.appcoda.com.tw/viewcontroller-transition-easy/

How do I cross dissolve when pushing views on a UINavigationController in iOS 7?

1
https://stackoverflow.com/questions/23530538/how-do-i-cross-dissolve-when-pushing-views-on-a-uinavigationcontroller-in-ios-7

CGAffineTransform

1
https://www.jianshu.com/p/1a2475af4378