答卓同学的 Swift 面试题

发布于:2017-03-15 18:09,阅读数:5928,点赞数:45


# 引言

看到卓同学于今日又发了一篇面试题。出于自己把大把精力投入在 Swift 上然而还是找不到工作的原因,来答一下题。答案是凭借自己的掌握答的,并没有一题一题去查资料,如有答错答不全的情况欢迎到简书中交流。

题目地址在这里:[「卓同学的 Swift 面试题」](http://www.jianshu.com/p/7c7f4b4e4efe)

本文首发在博客中,如需评论,简书评论地址在这里:[「答卓同学的 Swift 面试题」](http://www.jianshu.com/p/032ab437dd93)


# 正文

开始答题。

## 初级

### 1、class 和 struct 的区别

- 这个问题第一反应肯定是 class 能继承,而 struct 不能。
- 再之,在 swift 中 struct 是值,而 class 是引用。所谓值,struct 一旦生成是不能变的,如果有一个方法改变了自己的属性,这个方法需要标为 mutating,修改时会产生一个新值去替代旧值(这一步可能会被编译器优化,并不一定会真的产生一个新 struct)。而 class 无论怎么传递都是同一个对象。
- 值和引用的区别使 struct 不会有多线竞争的问题,class 会有。
- 有些 protocol 只能被类遵守。
- 总感觉还有但是一下子想不起来了。。。😅

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

- 第一点毫无疑问,用 extension 追加方法和计算属性。
- 运行时通过 runtime 添加方法。

### 3、Set 独有的方法有哪些?

- 集合类型取交集、并集、差集和补集的方法是独有的。


### 4、实现一个 min 函数,返回两个元素较小的元素

- 元素比较首先想到的是 Comparable 协议,实现 Comparable 协议即可:


```swift
struct A: Comparable, Equatable {
var value: Int
}

func ==(lhs: A, rhs: A) -> Bool {
return lhs.value == rhs.value
}
func <(lhs: A, rhs: A) -> Bool {
return lhs.value < rhs.value
}
func <=(lhs: A, rhs: A) -> Bool {
return lhs.value <= rhs.value
}
func >=(lhs: A, rhs: A) -> Bool {
return lhs.value >= rhs.value
}
func >(lhs: A, rhs: A) -> Bool {
return lhs.value > rhs.value
}

let a = A.init(value: 1)
let b = A.init(value: 2)

func myMin<T: Comparable>(_ a: T, _ b: T) -> T {
return a > b ? b : a
}

print(myMin(a, b))
```

### 5、map、filter、reduce 的作用

- 这三个是集合的实例方法,学函数式编程时最先学的函数,下面以数组为例。
- `map`: 传入一个函数,数组中每一个元素都执行这个函数,最后生成一个新数组。例子:模型列表到 JSON 列表的转换:

```swift
let objs = Array<MyObject>()
let jsonList = objs.map { $0.toJSON() }
```

- `filter`: 传入一个返回值是布尔类型的函数作为过滤器,最后生成一个能通过这个过滤器(即返回值是真)的数组。例子:过滤:

```swift
var objs = Array<MyObject>()
objs = objs.filter { $0.value > 10 }
```

- `reduce`: 传入一个初值,再传入一个函数,初值和数组中每一个元素都会被传入函数计算结果,最终返回一个值。例子:求和

```swift
let nums = [1, 2, 3]
let sum = nums.reduce(0) { $0 + $1 }
```

### 6、map 与 flatmap 的区别
- flatMap 主要用于展开多层数组,例如二维数组转成一维。
- 另一个区别是,flatMap 传入的函数可以返回 nil,如果返回 nil,它将不会出现在结果中,因此也可以用来过滤。


### 7、什么是 copy on write
- struct 是值类型,因此传递是深拷贝的值传递。但这个拷贝并不会立刻发生,只有在引用不唯一且正在修改这个结构体的时候才会发生。如果是单一引用,这个拷贝也有可能会被编译器优化。这点在 inout 关键字的用法中的拷贝同样适用。

### 8、如何获取当前代码的函数名和行号
- 函数名: 用`#function`
- 行号: 用`#line`

### 9、如何声明一个只能被类 conform 的 protocol

- 协议名后面加 class。

```swift
protocol Whatever: class { }
```


### 10、guard 使用场景
- 守护关键变量,如有异常直接退出,避免出现大量`if let`的缩进。

### 11、defer 使用场景
- 将代码块压如栈中,等程序执行完后弹出再执行。需要注意的是这个代码块不允许抛异常。以下两种情况用得比较多:

```swift
lock.lock()
defer {
lock.unlock()
}
```

```swift
let file = fopen(...)
defer {
fclose(...)
}
```

### 12、String 与 NSString 的关系与区别

- 从表面上看,是两个不同的东西,API 不一样。
- String 是 struct,值类型。NSString 是类,引用类型。
- 没有了。OC用的比较少,不太清楚 NSString。。😂

### 13、怎么获取一个 String 的长度
- Swift 3 还是哪个版本中 String 做了修改,引入了`String.CharacterView`类型来索引字符串的字符,第一种方法是直接去字符来做:

```swift
let count = str.characters.count
```

- 取C语言那种字符数来取长度,做过一阵子C语言混编,所以用过。不过转过去之后长度需要减一,最后有一位是`\0`,做过C语言的都懂的:

```swift
print(str.cString(using: .utf8)!.count - 1)
```

- 转成 Data 取长度(包括转成其他类型取长度的做法):

```swift
let str = "whatever"
let data = str.data(using: .utf8, allowLossyConversion: true)!
print(data.count)
```

### 14、如何截取 String 的某段字符串

- Swift 引入了`String.Index`来操作字符串,通过字符串开头和结尾的偏移量来索引字符。具体可以看我这篇文章下面关于截取 HTML 字符串中有用的 JSON 字符串的代码:[「使用 Swift 3.0 爬取“简书”的文章模型」](http://posts.enumsblog.com/posts/17002)


### 15、throws 和 rethrows 的用法与作用

- throws: 用于标记函数会抛出异常。
- retrhows: 似乎是用于标记闭包会抛出异常,不太确定。

### 16、try? 和 try!是什么意思

- try?: 表示忽略异常,如果后面的表达式有异常抛出,会忽略,并且返回 nil。
- try!: 断言这里不会抛异常。如果后面表达式有异常抛出,直接崩溃。

### 17、associatedtype 的作用

- 这个怎么形容呢。一个 protocol,里面有几个泛型方法,要求去具体实现这几个方法的泛型类型一样,可以用 associatedtype。


### 18、什么时候使用 final

- 类不能被继承时候。这个我没仔细了解,不知道 Swift 里还有没有别的用处。

### 19、public 和 open 的区别

- 像构造函数等,只能用 public 不能用 open。
- enum、protocol 的声明也只能用 public。

### 20、声明一个只有一个参数没有返回值闭包的别名

```swift
let t: (Any) -> Void = { _ in }
```

- 以上审题错误,别名看成变量了。正确答案是:

```swift
typealias t = (Any) -> Void
```


### 21、Self 的使用场景

- 这个在我的一个开源库里用到了过,直接上几句代码吧。下面代码中的`ret`的类型是`MyClass`而不是`Hello`。

```swift
protocol Hello {
func hello() -> Self
}

class MyClass: Hello {

func hello() -> Self {
return self
}
}

let ret = MyClass.init().hello()
```

### 22、dynamic 的作用

- 需要使用 runtime 动态性时标记,如果类继承于`NSObject`就不用了。

### 23、什么时候使用 @objc

- 同上。这两条我不是很清楚,似乎跟 CoreData 有关。

### 24、Optional(可选型) 是用什么实现的

- 可选类型是一个枚举,一个选项是`some`,一个选项是`nil`。
- 通关泛型来兼容所有类型。

### 25、如何自定义下标获取

- 加入`subscript`方法。

### 26、?? 的作用

- 设置默认值。如果前面的表达式是 nil,则使用后面的默认值。

### 27、lazy 的作用

- `lazy`: 懒加载,当变量在用的时候才加载变量。
- 需要注意的是,全局变量不用 lazy 也是懒加载的。

### 28、一个类型表示选项,可以同时表示有几个选项选中(类似 UIViewAnimationOptions ),用什么类型表示

- 这个我还真遇到过,用`OptionSet`。

### 29、inout 的作用

- 具体可以看我这篇文章中关于`inout`的内容:[「Swift 3.0 从 ++ 的实现到 inout 和 defer 的小细节」](http://posts.enumsblog.com/posts/17004)

### 30、Error 如果要兼容 NSError 需要做什么操作

- 印象中 Error 和 NSError 是两个不同的东西。
- <del>硬要兼容的话,我会尝试声明一个 enum 去携带 NSError。</del>
- 经指出,使用 extension `CustomNSError` 协议来继承 NSError 的一些参数即可。

### 31、下面的代码都用了哪些语法糖:[1, 2, 3].map{ $0 * 2 }

- 函数参数最后一个是闭包,则可以提到括号外面。
- 函数只有一个参数且是闭包,可以省略括号。
- $0、$1、$2 表示第一、二、三个参数。
- 以上说法来自斯坦福大学 iOS 开发公开课,2015年。
- 最后一条省略了`return`,直接返回,这个我不太清楚官方说法是什么。

### 32、什么是高阶函数

- 我的理解是,函数作为参数的函数,或者返回值是函数的函数。

### 33、如何解决引用循环

- 使用`week`和`unowned`

### 34、下面的代码会不会崩溃,说出原因
```swift
var mutableArray = [1,2,3]
for _ in mutableArray {
mutableArray.removeLast()
}
```

- 这个测了一下,发现不会崩溃。原因不是很清楚。猜测跟 Array 是值类型有关。

### 35、给集合中元素是字符串的类型增加一个扩展方法,应该怎么声明

- <del>使用扩展协议 + `where`关键字,我一直是这样做的,看上去比较傻,不知道有没有更快捷的。</del>

```swift
protocol IsString { }
extension String: IsString { }

extension Array where Element: IsString {
func whatever() { }
}
["", ""].whatever()
```
- 经过卓同学指出这样做真的太傻了,正确答案应该是:

```swift
extension Collection where Iterator.Element == String {
func whatever() { }
}
["", ""].whatever()
```



## 高级

### 1、一个 Sequence 的索引是不是一定从 0 开始?

- 不是。其实自己手写过 Sequence 的类的同学都知道,下标随便自己写的,爱从几开始都可以。

### 2、数组都实现了哪些协议

- 这个完全没去背,Sequence、Collection 啥的,具体去看文档就好。

### 3、如何自定义模式匹配

- 模式匹配只要实现`~=`这个运算符就可以了。

### 4、autoclosure 的作用

- 如果一个函数有个闭包参数用 autoclosure 标记了,貌似是可以简化语法,传值进去当结果的,像这样:

```swift

func f(_ foo: @autoclosure ()->Bool) { }

f(true)
```

### 5、编译选项 whole module optmization 优化了什么

- 区别于单文件优化,这个字面意思看是整个模块一起优化,多文件优化,具体不是很了解。

### 6、下面代码中 mutating 的作用是什么
```swift
struct Person {

var name: String {
mutating get {
return store
}
}
}
```

- 上面提到过,值类型的结构体在改变自身时需要用 mutating 标记。
- 但是计算属性的 getter 也需要用 mutating 不知道是什么原因,求大神解答。

### 7、如何让自定义对象支持字面量初始化

- 这个在分析`SwiftyJSON`时接触过。
- 有一吨的`ExpressibleByXXXXLiteral`协议。实现这些协议就好了。

### 8、dynamic framework 和 static framework 的区别是什么

- 我从 C++ 角度回答这个问题,不太清楚 iOS 这边有没有区别。
- 静态库: 链接时函数、数据等会被打包进程序。
- 动态库: 运行时加载库,供程序调用调用。

## 哲学
### 1、为什么数组索引越界会崩溃,而字典用下标取值时 key 没有对应值的话返回的是 nil 不会崩溃。

- 一款语言必须要有一些基础的可以完全信任的东西,否则就会陷入循环验证的悖论。所以第一题的答案就是,数组取下标被设计为可以完全信任的操作。

### 2、一个函数的参数类型只要是数字(Int、Float)都可以,要怎么表示。

- 投机取巧。

```swift
func foo(_ a: NSNumber) { }

foo(2)
foo(2.33)
foo(0xABC)
```

# 结语

题目量很大,做了两个多小时。

初级部分基本上是需要掌握的知识点,自己做完后起到了查漏补缺的作用。高级部分我答得比较水,希望大神们继续补充。


评论:0条


返回列表

返回归档

返回主页