答 “卓同学的 Swift 面试题”-- 上篇

1,708 阅读7分钟
原文链接: www.jianshu.com

这是卓同学的 Swift 面试题 强行来答一波,如有歧义、错误、不全,欢迎指(来)正(喷)。
面试题基础篇有36题,高级篇8题,哲学篇2题。先就基础篇前11题进行回答。也为接下来的面试做些准备 🤔

1. class 和 struct 的区别
2. 不通过继承,代码复用(共享)的方式有哪些
3. Set 独有的方法有哪些?
4. 实现一个 min 函数,返回两个元素较小的元素
5. map、filter、reduce 的作用
6. map 与 flatmap 的区别
7. 什么是 copy on write
8. 如何获取当前代码的函数名和行号
9. 如何声明一个只能被类 conform 的 protocol
10. guard 使用场景
11. defer 使用场景
1. class 和 struct 的区别

此题可参见 The Swift Programming Language,有专门的一节是用来介绍class 和 struct 的。我也是从中总(搬)结(运)来的。
在 Swift 中,class 和 struct 的关系可以说是非常之亲密。
相同点: 我们可以使用完全使用相同的语法规则来为 class 和 struct 定义属性、方法、下标操作、构造器,也可以通过extension 和 protocol 来提供某种功能。
不同点:
1)与 struct 相比,class 还有如下功能:

  • 继承允许一个类继承另一个类的特性
  • 类型转换允许在运行时检查和解释一个类实例的类型
  • 析构器允许一个类实例释放任何其所被分配的资源
  • 引用计数允许对一个类的多次引用

2) Value Types & Reference Types:

  • struct 是值类型 (Value Types) 即:通过被复制的方式在代码中传递,不使用引用计数
  • class 是引用类型(Reference Types) 即:引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝

更多关于值类型和引用类型的知识,请参见Value Types & Reference Types

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

我们都知道代码复用的好处:可以降低开发成本、增加代码的可靠性并提高它们的一致性。
在Swift中,除了通过继承,还可以通过 扩展、协议 来实现代码复用。

扩展 --- Extensions

扩展 就是为一个已有的类、结构体、枚举类型或者协议类型添加新功能。这包括在没有权限获取原始源代码的情况下扩展类型的能力(即 逆向建模 )。扩展和 Objective-C 中的分类类似。

Swift 中的扩展可以:

  • 添加计算型属性和计算型类型属性
  • 定义实例方法和类型方法
  • 提供新的构造器
  • 定义下标
  • 定义和使用新的嵌套类型
  • 使一个已有类型符合某个协议

更多扩展知识,请移步:Extensions

协议 --- Protocols

协议 规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以遵循协议,并为协议定义的这些要求提供具体实现
另外,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以遵循协议,并为协议定义的这些要求提供具体实现

这里只是简单叙述下,更多协议知识,请移步:Protocols

3. Set 独有的方法有哪些
intersect(_:)// 根据两个集合中都包含的值创建的一个新的集合
exclusiveOr(_:) // 根据只在一个集合中但不在两个集合中的值创建一个新的集合
union(_:) // 根据两个集合的值创建一个新的集合
subtract(_:) //根据不在该集合中的值创建一个新的集合
isSubsetOf(_:) //判断一个集合中的值是否也被包含在另外一个集合中
isSupersetOf(_:) //判断一个集合中包含的值是否含有另一个集合中所有的值
isStrictSubsetOf(:) isStrictSupersetOf(:) //判断一个集合是否是另外一个集合的子集合或者父集合并且和特定集合不相等
isDisjointWith(_:) //判断两个集合是否不含有相同的值

let houseAnimals : Set = ["?","?"]
let farmAnimals: Set = ["?","?","?","?","?"]
let cityAnimals: Set = ["?","?"]
houseAnimals. isSubsetOf(farmAnimals)
//true
farmAnimals. isSupersetOf(houseAnimals)
//true
farmAnimals. isDisjointWith(cityAnimals)
//true

集合的基本操作
4.实现一个 min 函数,返回两个元素较小的元素
func min<T: Comparable>(_ a: T, _ b: T) -> T {
    return a < b ? a: b
}
//这里一定要遵守 Comparable 协议,因为并不是所有的类型都具有“可比性”
5.map、filter、reduce 的作用
  • map 是Array类的一个方法,我们可以使用它来对数组的每个元素进行转换
    let intArray = [1, 3, 5]
    let stringArr = intArray.map {
              return "\($0)"
          }
    // ["1", "3", "5"]
  • filter 用于选择数组元素中满足某种条件的元素

    let filterArr = intArray.filter {
      return $0 > 1
    }
    //[3, 5]
  • reduce 把数组元素组合计算为一个值

    let result = intArray.reduce(0) {
      return $0 + $1
    }
    //9
6.map 与 flatmap 的区别
  • map 可以对一个集合类型的所有元素做一个映射操作
  • 和map 不同,flatmap 有两个定义,分别是:
    func flatMap(transform: (Self.Generator.Element) throws -> T?) -> [T]
    func flatMap(transform: (Self.Generator.Element) -> S) -> [S.Generator.Element]
let intArray = [1, 2, 3, 4]
result = intArray.flatMap { $0 + 2 }
// [3,4,5,6]
对数组进行flatmap操作,和map是没有区别的

1) 第一种情况返回值类型是 T?, 实际应用中,可以用来过滤元素为nil的情况,(内部使用了 if-let 来对nil值进行了过滤) 例如:

let optionalArray: [String?] = ["AA", nil, "BB", "CC"];
var optionalResult = optionalArray.flatMap{ $0 }
// ["AA", "BB", "CC"]
操作前是[String?], 操作后会变成[String]

2) 第二种情况可以进行“降维”操作

let numbersCompound = [[1,2,3],[4,5,6]];
var res = numbersCompound.map { $0.map{ $0 + 2 } }
// [[3, 4, 5], [6, 7, 8]]
var flatRes = numbersCompound.flatMap { $0.map{ $0 + 2 } }
// [3, 4, 5, 6, 7, 8]

$0.map{ $0 + 2 } 会得到[3, 4, 5], [6, 7, 8], 然后遍历这两个数组,将遍历的元素拼接到一个新的数组内,最终并返回就得到了[3, 4, 5, 6, 7, 8]

7.什么是 copy on write

copy on write, 写时复制,简称COW,它通过浅拷贝(shallow copy)只复制引用而避免复制值;当的确需要进行写入操作时,首先进行值拷贝,在对拷贝后的值执行写入操作,这样减少了无谓的复制耗时。

应用场景 :

写时复制最擅长的是并发读取场景,即多个线程/进程可以通过对一份相同快照,去处理实效性要求不是很高但是仍然要做的业务(比如实现
FS\DB备份、日志、分析)
适用于对象空间占用大,修改次数少,而且对数据实效性要求不高的场景

参考:Copy-on-write的介绍与应用

8.如何获取当前代码的函数名和行号
  • 获取函数名: #function
  • 获取行号:#line
  • 获取文件名: #file
  • 获取列:#column
9.如何声明一个只能被类 conform 的 protocol

协议的继承列表中,通过添加 class 关键字来限制协议只能被类类型遵循,而结构体或枚举不能遵循该协议。class 关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前:

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    // 这里是类类型专属协议的定义部分
}
10.guard 使用场景

使用 guard 来表达 “提前退出”的意图,有以下 使用场景

  • 在验证入口条件时
  • 在成功路径上提前退出
  • 在可选值解包时(拍扁 if let..else 金字塔)
  • return 和 throw 中
  • 日志、崩溃和断言中
    而下面则是尽量 避免使用 的场景:
  • 不要用 guard :替代琐碎的 if..else 语句
  • 不要用 guard :作为 if 的相反情况
  • 不要:在 guard 的 else 语句中放入复杂代码
    具体参见 使用 guard 的正确姿势
11.defer 使用场景

defer 语句用于在退出当前作用域之前执行代码.例如:
手动管理资源时,比如 关闭文件描述符,或者即使抛出了错误也需要执行一些操作时,就可以使用 defer 语句。
如果多个 defer 语句出现在同一作用域内,那么它们执行的顺序与出现的顺序相反

func f() {
    defer { print("First") }
    defer { print("Second") }
    defer { print("Third") }
}
f()
// 打印 “Third”
// 打印 “Second”
// 打印 “First”