本文整理了 Swift 开发面试中常见的问题与解答,帮助开发者巩固基础知识,顺利通过技术面试。

前提

  • git 使用
  • 三方库使用
  • swift 调用oc
  • 解决问题方式

frame 和 bounds 有什么不同?

1
2
frame指的是:该view在父view坐标系统中的位置和大小。(参照点是父view的坐标系统)
bounds指的是:该view在本身坐标系统中的位置和大小。(参照点是屏幕坐标系统)

ViewController生命周期

按照执行顺序排列:

  1. initWithCoder:通过nib文件初始化时触发。
  2. awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。
  3. loadView:开始加载视图控制器自带的view。
  4. viewDidLoad:视图控制器的view被加载完成。
  5. viewWillAppear:视图控制器的view将要显示在window上。
  6. updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
  7. viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
  8. viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
  9. viewDidAppear:视图控制器的view已经展示到window上。
  10. viewWillDisappear:视图控制器的view将要从window上消失。
  11. viewDidDisappear:视图控制器的view已经从window上消失。

开发项目时你是怎么检查内存泄露?

  1. 静态分析 analyze。
  2. instruments工具里面有个leak可以动态分析。

如何解决引用循环

  1. 转换为值类型, 只有类会存在引用循环, 所以如果能不用类, 是可以解引用循环的,
  2. delegate 使用 weak 属性.
  3. 闭包中 如果确认一定不为nil,[unowned self] 修饰,否则使用 [weak self] 修饰
  4. 少用单例,减少内存占用
  5. 闭包内不要调用外部局部变量,应该使用weak修饰后调用,或者[weak self] 调用

delegate 和 notification 的区别

  1. 二者都用于传递消息,不同之处主要在于一个是一对一的,另一个是一对多的。
  2. notification通过维护一个array,实现一对多消息的转发。
  3. delegate需要两者之间必须建立联系,不然没法调用代理的方法;notification不需要两者之间有联系。

不用中间变量,用两种方法交换A和B的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1.中间变量
void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}

// 2.加法
void swap(int a, int b) {
a = a + b;
b = a - b;
a = a - b;
}

// 3.异或(相同为0,不同为1. 可以理解为不进位加法)
void swap(int a, int b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
}

排序算法 选择排序、冒泡排序、插入排序三种排序算法

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
/** 
* 【选择排序】:最值出现在起始端
*
* 第1趟:在n个数中找到最小(大)数与第一个数交换位置
* 第2趟:在剩下n-1个数中找到最小(大)数与第二个数交换位置
* 重复这样的操作...依次与第三个、第四个...数交换位置
* 第n-1趟,最终可实现数据的升序(降序)排列。
*
*/
void selectSort(int *arr, int length) {
for (int i = 0; i < length - 1; i++) { //趟数
for (int j = i + 1; j < length; j++) { //比较次数
if (arr[i] > arr[j]) {
int temp = arr[i];
arr[i] = arr[j];

arr[j] = temp;
}
}
}
}
/**
* 【冒泡排序】:相邻元素两两比较,比较完一趟,最值出现在末尾
* 第1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n个元素位置
* 第2趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n-1个元素位置
* …… ……
* 第n-1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第2个元素位置
*/
void bublleSort(int *arr, int length) {
for(int i = 0; i < length - 1; i++) { //趟数
for(int j = 0; j < length - i - 1; j++) { //比较次数
if(arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
/**
* 折半查找:优化查找时间(不用遍历全部数据)
*
* 折半查找的原理:
* 1> 数组必须是有序的
* 2> 必须已知min和max(知道范围)
* 3> 动态计算mid的值,取出mid对应的值进行比较
* 4> 如果mid对应的值大于要查找的值,那么max要变小为mid-1
* 5> 如果mid对应的值小于要查找的值,那么min要变大为mid+1
*
*/

// 已知一个有序数组, 和一个key, 要求从数组中找到key对应的索引位置
int findKey(int *arr, int length, int key) {
int min = 0, max = length - 1, mid;
while (min <= max) {
mid = (min + max) / 2; //计算中间值
if (key > arr[mid]) {
min = mid + 1;
} else if (key < arr[mid]) {
max = mid - 1;
} else {
return mid;
}
}
return -1;
}

描述一种在Swift中出现循环引用的情况,并说明怎么解决。

  • 循环引用出现在当两个实例对象相互拥有强引用关系的时候,这会造成内存泄露,原因是这两个对象都不会被释放。只要一个对象被另一个对象强引用,那么该对象就不能被释放,由于强引用的存在,每个对象都会保持对方的存在。
  • 解决方式:用weak或者unowned引用代替其中一个的强引用,来打破循环引用。

class 和 struct 的区别

class 为类, struct 为结构体, 类是引用类型, 结构体为值类型, 结构体不可以继承

不通过继承,代码复用(共享)的方式有哪些

扩展, 全局函数

map、filter、reduce 的作用

1
2
(0 ..< 10).filter{$0 % 2 == 0}.map{"\($0)"}.reduce(""){$0 + $1}
// 02468

try, try? 和 try! 的区别

  • try 需要 do catch
  • try? 在用于处理可抛出异常函数时, 如果函数抛出异常, 则返回 nil, 否则返回函数返回值的可选值
  • try! 在函数抛出异常的时候崩溃, 否则则返会函数返回值

protocol associatedtype & typealias & optional function

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
protocol ListProtcol {
associatedtype Element

func push(_ element: Element)
func pop(_ element: Element) -> Element?

// to optional function
func insert(_ element: Element)
}

extension ListProtcol {
func insert(_ element: Element) {
print("Implemented in extension")
}
}

extension ListProtcol where Element == Int {
func isInt() -> Bool {
return true
}
}

class listClass: ListProtcol {
typealias Element = String

func push(_ element: Element) {

}
func pop(_ element: Element) -> Element? {
return nil
}
}

class list2Class<T>: ListProtcol {
func push(_ element: T) {

}
func pop(_ element: T) -> T? {
return nil
}
}

class list3Class: ListProtcol {
func push(_ element: Int) {
print(isInt())
}
func pop(_ element: Int) -> Int? {
return nil
}
}

如何在枚举中定义不同类型的枚举值,并使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Custom {
case none
case first(String)
case second(Double, Int)
}
func test() {
let custom = Custom.none
switch custom {
case .first(let first):
print(first)
case .second(let d, let i):
print(d, i)
default: break
}
}

lazy 的作用

懒加载, 当属性要使用的时候, 才去完成初始化

下面的代码都用了哪些语法糖

[1, 2, 3].map{ $0 * 2 }

  • [1, 2, 3] 使用了 Array 实现的ExpressibleByArrayLiteral 协议, 用于接收数组的字面值
  • map{xxx} 使用了尾随闭包(trailing closure)
  • 闭包没有声明函数参数, 返回值类型, 数量, 依靠的是闭包类型的自动推断
  • 闭包中语句只有一句时, 自动将这一句的结果作为返回值
  • $0 在没有声明参数列表的时候, 第一个参数名称为0, 后续参数以此类推

一个类型表示选项,可以同时表示有几个选项选中,用什么类型表示

1
2
3
4
5
6
7
struct SomeOption: OptionSet {
let rawValue: Int
static let option1 = SomeOption(rawValue: 1 << 0)
static let option2 = SomeOption(rawValue:1 << 1)
static let option3 = SomeOption(rawValue:1 << 2)
}
let options: SomeOption = [.option1, .option2]

定义静态方法时关键字 static 和 class 有什么区别

static 定义的方法不可以被子类继承, class 则可以

如何使用自定义的符号进行条件编译

1
2
3
4
5
#if CUSTOM_FLAG
// todo
#else
// todo
#endif

Build Settings->Swift Compiler - Custom Flags->Other Swift Flags, add -D CUSTOM_FLAG