2020 面试题汇总(Swift)
https://www.jianshu.com/p/bdaa49f9d1a4
1 Swift 比 Objective-C 有什么优势?
Swift 速度更快,运算性能更高。
Swift 语法简单易读、代码更少,更加清晰、易于维护
Swift 更加安全,它是类型安全的语言
Swift 泛型、结构体、枚举都很强大
Swift 便捷的函数式编程
Swift 类型判断
2 Swift 是面向对象还是函数式编程语言?
Swift 既是面向对象的,又是函数式的编程语言。
说 Swift 是面向对象的语言,是因为 Swift 支持类的封装、继承、和多态.
说 Swift 是函数式编程语言,是因为 Swift 支持 map, reduce, filter, flatmap 这类去除中间状态、数学函数式的方法,更加强调运算结果而不是中间过程。
3 dynamic framework 和 static framework 的区别是什么?
https://www.cnblogs.com/junhuawang/p/7598236.html
静态库和动态库, 静态库是每一个程序单独打包一份, 而动态库则是多个程序之间共享.
静态库和动态库是相对编译期和运行期的:静态库在程序编译时会被链接到目标代码中,程序运行时将不再更改静态库;而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入。
不同点:
静态库在链接时,会被完整的复制到可执行文件中,如果多个App都使用了同一个静态库,那么每个App都会拷贝一份,缺点是浪费内存.
动态库不会复制,只有一份,程序运行时动态加载到内存中,系统只会加载一次,多个程序共用一份,节约了内存.
共同点:
静态库和动态库都是闭源库,只能拿来满足某个功能的使用,不会暴露内部具体的代码信息.
4 Swift的静态派发
OC 中的方法都是动态派发(方法调用),Swift 中的方法分为静态派发和动态派发.
动态派发:指的是方法在运行时才找具体实现.Swift 中的动态派发和 OC 中的动态派发是一样的.
静态派发:静态派发是指在运行时调用方法不需要查表,直接跳转到方法的代码中执行.
静态派发的特点:
静态派发更高效,因为静态派发免去了查表操作.
静态派发的条件是方法内部的代码必须对编译器透明,且在运行时不能被更改,这样编译器才能帮助我们.
Swift 中的值类型不能被继承,也就是说值类型的方法实现不能被修改或者被复写,因此值类型的方法满足静态派发.
5 说说Swift为什么将String,Array,Dictionary设计成值类型?
值类型相比引用类型,最大的优势在于内存使用的高效.值类型在栈上操作,引用类型在堆上操作.栈上的操作仅仅是单个指针的上下移动,而堆上的操作则牵涉到合并、移位、重新链接等.也就是说Swift这样设计,大幅减少了堆上的内存分配和回收的次数.同时写时复制又将值传递和复制的开销降到了最低.
String,Array,Dictionary设计成值类型,也是为了线程安全考虑.通过Swift的let设置,使得这些数据达到了真正意义上的“不变”,它也从根本上解决了多线程中内存访问和操作顺序的问题.
设计成值类型还可以提升API的灵活度.
6 什么是函数式编程?
面向对象的编程思想, 我们将要解决的一个个问题, 抽象成一个个类, 通过给类定义属性和方法, 让类帮助我们解决需要处理的问题.(即命令式编程, 给对象下一个个命令).
函数式编程指的是数学意义上的函数,即映射关系(如:y = f(x),就是 y 和 x 的对应关系,可以理解为"像函数一样的编程").它的主要思想是把运算过程尽量写成一系列嵌套的函数调用.
例:
数学表达式
(1 + 2) * 3 - 4
传统编程
var a = 1 + 2
var b = a * 3
var c = b - 4
函数式编程
var result = subtract(multiply(add(1,2), 3), 4)
函数式编程的特点
1 函数是"第一等公民"
函数和其他数据类型一样,可以作为参数,可以赋值给其他变量,可以作为返回值.
例:
var print = function(i){
console.log(i)
}
[1,2,3].forEach(print)
2 高阶函数
高阶函数:接受至少一个函数作为参数;返回的结果是一个函数.
3 柯里化
所谓“柯里化” ,就是把一个多参数的函数 ,转换为单参数函数,并且这个函数的返回值也是一个函数.
例:
// 柯里化之前
function add(x, y) {
return x + y;
}
add(1, 2) // 3
// 柯里化之后
function addX(y) {
return function (x) {
return x + y;
};
}
addX(2)(1) // 3
4 没有"副作用"
所谓“副作用”,指的是函数内部与外部互动,产生运算以外的其他结果.
5 纯函数
纯函数编程和函数编程的区别在于:是否允许在函数内部执行一些非函数式的操作,同时这些操作是否会暴露给系统中的其他地方?也就是是否存在副作用,如果不存在副作用或者说可以不用在意这些副作用,那么就将其称为纯粹的函数式编程.
6 引用透明性
函数无论在何处何时调用,如果使用相同的输入总能持续地得到相同的结果,就具备了函数式的特征.这种不依赖外部变量或“状态”,只依赖输入的参数的特性就被称为引用透明性.
函数式编程的好处
代码简洁,开发迅速
接近自然语言,易于理解
更方便的代码管理
易于"并发编程"
代码的热升级
7 Swift mutating关键字的使用?
默认情况下,不能在实例方法中修改值类型的属性.若在实例方法中使用 mutating 关键字,不仅可以在实例方法中修改值类型的属性,而且会在方法实现结束时将其写回到原始结构.
8 autoclosure 的作用
自动闭包,将参数自动封装为闭包参数
9 swift中,如何阻止方法,属性,下标被子类改写?
在类的定义中使用 final 关键字声明类、属性、方法和下标。final 声明的类不能被继承,final 声明的属性、方法和下标不能被重写。
如果只是限制一个方法或属性被重写,只需要在该方法或者属性前加一个 final.
如果需要限制整个类无法被继承, 那么可以在类名之前加一个final。
10 Optional(可选型)是什么?Optional(可选型)解决方式?
Optional 是一个泛型枚举
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
Optional 类型表示: 有值 / 没有值
在Objective-C中并没有 Optional 类型, 只有nil,并且nil只能用于表示对象类型无值,并不能用于基础类型(int, float),枚举和结构体.基础类型需要返回类似 NSNotFound 的特殊值来表示无值,所以在Swift中定义了 Optinal 类型来表示各种类型的无值状态,并规定了nil不能用于非可选的常量和变量,只能用于Optinal类型.
解决方式:
强行打开 - 不安全
let a: String = x!
隐式解包变量声明 - 在许多情况下不安全
var a = x!
可选绑定 - 安全
if let a = x {
print("x was successfully unwrapped and is = \(a)")
}
可选链接 - 安全
let a = x?.count
无合并操作员 - 安全
let a = x ?? ""
警卫声明 - 安全
guard let a = x else {
return
}
可选模式 - 安全
if case let a? = x {
print(a)
}
11 inout 的作用
inout 输入输出参数,让输入参数可变类似__block 的作用。
1 函数参数默认为常量。试图从函数主体内部更改函数参数的值会导致编译时错误。这意味着您不能错误地更改参数的值。如果您希望函数修改参数的值,并且希望这些更改在函数调用结束后仍然存在,请将该参数定义为输入输出参数。
2 您可以通过将inout关键字放在参数类型的前面来编写输入/输出参数。一个在出参数具有传递的值中,由函数修改的功能,并将该部分送回出的功能来代替原来的值。
3 您只能将变量作为输入输出参数的参数传递。您不能将常量或文字值作为参数传递,因为无法修改常量和文字。当您将一个与号(&)作为变量传入in-out参数时,将它放在变量名的前面,以表明该变量可以被函数修改。
4 注意:输入输出参数不能具有默认值,并且可变参数不能标记为inout。
12 下面的功能特性都包含在Swift中吗?
1、泛型类
2、泛型结构体
3、泛型协议
答案: Swift 包含1和2特性。泛型可以在类、结构体、枚举、全局函数或者方法中使用。
3是通过 typealias 部分实现的。typealias 不是一个泛型类型,它只是一个占位符的名字。它通常是作为关联类型被引用,只有协议被一个类型引用的时候它才被定义。
13 Error 如果要兼容 NSError 需要做什么操作
Error是一个协议, swift中的Error 都是enum, 可以转 NSError.如果需要Error有NSError的功能,实现 LocalizedError CustomNSError 协议.
14 权限修饰符
open :修饰的属性或者方法在其他作用域既可以被访问也可以被继承或重载 override。
public :修饰的属性或者方法可以在其他作用域被访问,但不能在重载 override 中被访问,也不能在继承方法中的 Extension 中被访问。
internal:被修饰的属性和方法只能在模块内部可以访问,超出模块内部就不可被访问了。(默认)
fileprivate :其修饰的属性或者方法只能在当前的 Swift 源文件里可以访问。
private :只允许在当前类中调用,不包括 Extension ,用 private 修饰的方法不可以被代码域之外的地方访问。
从高到低排序如下:
open > public > interal > fileprivate > private
15 struct 与 class 的区别
1 struct是值类型,class是引用类型:
值类型的变量直接包含它们的数据,对于值类型都有它们自己的数据副本,因此对一个变量操作不可能影响另一个变量.值类型包括结构体 (数组和字典),枚举,基本数据类型 (boolean, integer, float等).
引用类型的变量存储对他们的数据引用,对一个变量操作可能影响另一个变量.
二者的本质区别:struct是深拷贝;class是浅拷贝。
2 property的初始化不同:
class 在初始化时不能直接把 property 放在默认的 constructor 的参数里,而是需要自己创建一个带参数的 constructor;而struct可以,把属性放在默认的 constructor 的参数里。
3 变量赋值方式不同:
struct是值拷贝;class是引用拷贝。
4 immutable变量:
swift的可变内容和不可变内容用var和let来甄别,如果初始为let的变量再去修改会发生编译错误。struct遵循这一特性;class不存在这样的问题。
5 mutating function:
struct 和 class 的差別是 struct 的 function 要去改变 property 的值的时候要加上 mutating,而 class 不用。
6 继承:
struct不可以继承,class可以继承。
7 struct比class更轻量:
struct分配在栈中,class分配在堆中。
16 举例swift中模式匹配的作用?
https://www.cnblogs.com/wjw-blog/p/11674857.html
模式匹配: 在switch中体现最明显
通配符模式: _
标识符模式:let i = 1
值绑定模式:case .Student(let name) 或者 case let .Student(name)
元祖模式:case (let code, _)
可选模式:if case let x? = someOptional { }
类型转换模式:case is Int: 或者 case let n as String:
表达式模式:范围匹配 case (0..<2) case(0...2, 2...4)
条件句中使用where: case (let age) where age > 30
if case let:if case let .Student(name) = xiaoming { }
for case let: for case let x in array where x > 10 {} 或者 for x in array where x > 10
17 swift中 closure 与OC中block的区别?
1、closure是匿名函数、block是一个结构体对象
2、closure通过逃逸闭包来在内部修改变量,block 通过 __block 修饰符
18 Swift 中的 KVC 和 KVO
KVC
要继承 NSObject
class KVCClass :NSObject{
var someValue: String = "123"
}
let kvc = KVCClass()
kvc.someValue // 123
kvc.setValue("456", forKey: "someValue")
kvc.someValue // 456
KVO
由于 Swift 为了效率, 默认禁用了动态派发, 因此 Swift 要实现 KVO, 除了要继承自 NSObject 外还要将观测的对象标记为 dynamic(让 swift 代码也能有 Objective-C 中的动态机制).
class KVOClass:NSObject {
dynamic var someValue: String = "123"
var someOtherValue: String = "abc"
}
class ObserverClass: NSObject {
func observer() {
let kvo = KVOClass()
kvo.addObserver(self, forKeyPath: "someValue", options: .new, context: nil)
kvo.addObserver(self, forKeyPath: "someOtherValue", options: .new, context: nil)
kvo.someValue = "456"
kvo.someOtherValue = "def"
kvo.removeObserver(self, forKeyPath: "someValue")
kvo.removeObserver(self, forKeyPath: "someOtherValue")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("\(keyPath!) change to \(change![.newKey] as! String)")
}
}
ObserverClass().observer()
//someValue change to 456
19 associatedtype 的作用
关联类型:为协议中的某个类型提供了一个别名,其代表的真实类型在实现者中定义.
//协议,使用关联类型
protocol TableViewCell {
associatedtype T
func updateCell(_ data: T)
}
//遵守TableViewCell
class MyTableViewCell: UITableViewCell, TableViewCell {
typealias T = Model
func updateCell(_ data: Model) {
// do something ...
}
}
20 什么是泛型,swift在哪些地方使用了泛型?
泛型(generic)可以使我们在程序代码中定义一些可变的部分,在运行的时候指定。使用泛型可以最大限度地重用代码、保护类型的安全以及提高性能。
21 map、filter、reduce 的作用
map 用于映射, 可以将一个列表转换为另一个列表
[1, 2, 3].map{"\($0)"}// 数字数组转换为字符串数组
["1", "2", "3"]
filter 用于过滤, 可以筛选出想要的元素
[1, 2, 3].filter{$0 % 2 == 0} // 筛选偶数
// [2]
reduce 合并
[1, 2, 3].reduce(""){$0 + "\($1)"}// 转换为字符串并拼接
// "123"
22 map 与 flatmap 的区别
1 map 可以对一个集合类型的所有元素做一个映射操作.
2 flatMap
第一个作用和map一样,对一个集合类型的所有元素做一个映射操作,且可以过滤为nil的情况.
例如:
let array = [1,2,5,6,7,nil]
let array_map = array.map { $0 }
//[Optional(1), Optional(2), Optional(5), Optional(6), Optional(7), nil]
let array_flatmap = array_map.flatMap { $0 }
//[1, 2, 5, 6, 7]
第二种情况可以进行“降维”操作
let array = [["1", "2"],["3", "4"]]
let array_map = array.map { $0 }
//[["1", "2"], ["3", "4"]]
let array_flatmap = array_map.flatMap { $0 }
//["1", "2", "3", "4"]
23 defer、guard的作用?
defer 语句块中的代码, 会在当前作用域结束前调用,无论函数是否会抛出错误。每当一个作用域结束就进行该作用域defer执行。 如果有多个 defer, 那么后加入的先执行.
guard :过滤器,拦截器
guard 和 if 类似, 不同的是, guard 总是有一个 else 语句, 如果表达式是假或者值绑定失败的时候, 会执行 else 语句, 且在 else 语句中一定要停止函数调用.
24 throws 和 rethrows 的用法与作用
throws 用在函数上, 表示这个函数会抛出错误.
有两种情况会抛出错误, 一种是直接使用 throw 抛出, 另一种是调用其他抛出异常的函数时, 直接使用 try XX 没有处理异常.
enum DivideError: Error {
case EqualZeroError;
}
func divide(_ a: Double, _ b: Double) throws -> Double {
guard b != Double(0) else {
throw DivideError.EqualZeroError
}
return a / b
}
func split(pieces: Int) throws -> Double {
return try divide(1, Double(pieces))
}
rethrows 与 throws 类似, 不过只适用于参数中有函数, 且函数会抛出异常的情况, rethrows 可以用 throws 替换, 反过来不行
func processNumber(a: Double, b: Double, function: (Double, Double) throws -> Double) rethrows -> Double {
return try function(a, b)
}
25 如何自定义下标获取
实现 subscript 即可, 如
extension AnyList {
subscript(index: Int) -> T{
return self.list[index]
}
subscript(indexString: String) -> T?{
guard let index = Int(indexString) else {
return nil
}
return self.list[index]
}
}
索引除了数字之外, 其他类型也是可以的
26 为什么数组索引越界会崩溃,而字典用下标取值时 key 没有对应值的话返回的是 nil 不会崩溃。
struct Array<Element> {
subscript(index: Int) -> Element
}
struct Dictionary<Key: Hashable, Value> {
subscript(key: Key) -> Value?
}
1 数组索引访问的是一段连续地址,越界访问也能访问到内存,但这段内存不一定可用,所以会引起Crash.
2 字典的key并没有对应确定的内存地址,所以是安全的.
27 给集合中元素是字符串的类型增加一个扩展方法,应该怎么声明?
使用 where 子句, 限制 Element 为 String
extension Array where Element == String {
var isStringElement:Bool {
return true
}
}
["1", "2"].isStringElement
//[1, 2].isStringElement// error
28 一个函数的参数类型只要是数字(Int、Float)都可以,要怎么表示。
Int、Float 都有一个协议
func myMethod<T>(_ value: T) where T: Numeric {
print(value + 1)
}
或者 ExpressibleByIntegerLiteral 协议也行
29 一个类型表示选项,可以同时表示有几个选项选中(类似 UIViewAnimationOptions ),用什么类型表示?
需要实现自 OptionSet, 一般使用 struct 实现. 由于 OptionSet 要求有一个不可失败的init(rawValue:) 构造器, 而枚举无法做到这一点(枚举的原始值构造器是可失败的, 而且有些组合值, 是没办法用一个枚举值表示的).
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]