Swift 相关面试题

本文复制 https://www.jianshu.com/p/23d99f434281

class 和 struct 的区别

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

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

扩展,全局函数

Set 独有的方法有哪些?

1
2
3
4
5
6
7
8
9
10
11
//定义一个set
let setA: Set<Int> = [1, 2, 3, 4, 4] //{1, 2, 3, 4} 顺序可能不一致,同一个元素只有一个值
set setB: Set<Int> = [1, 3, 5, 7, 9] //{1, 3, 5, 7, 9}
//取并集
let setUnion = setA.union(setB) //{1, 2, 3, 4, 5, 7, 9}
//取交集
let setIntersect = setA.intersection(setB) //{1, 3}
//取差集 A - B
let setRevers = setA.subtraction(setB). //{2, 4}
//取对称差集
let setXor = letA.symmentricDifference(setB) //{2, 4, 5, 7, 9}

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

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

map、filter、reduce 的作用
map 用于映射,可以将一个列表转换为另一个列表

1
2
[1, 2, 3].map{"\($0)"} //数字数组转换为字符串数组
["1", "2", "3"]

filter 用于过滤,可以筛选出想要的元素

1
2
[1, 2, 3].filter{$0 %2 == 0} //筛选偶数
//[2]

reduce 合并

1
2
[1, 2, 3].reduce(""){$0 + "\($1)"} //转换为字符串拼接
// "123"

map 与 flatmap 的区别
flatmap 有两个实现函数实现

1
2
3
4
5
6
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult ?) -> rethrows -> [ElementOfResult]
//这个方法,中间的函数返回值为一个可选类型,而flatmap会丢掉那些返回值为nil的值,例如
["1", "@", "2", "3", "a"].flatMap{Int($0)}
// [1, 2, 3]
["1", "@", "2", "3", "a"].map{Int($0) ?? -1}
//[Optional(1), nil, Optional(2), Optional(3), nil]

另一个实现

1
2
3
4
5
6
7
8
9
public func flatMap<SegmentOfResult>(_ transform: (Element) throws ->SegmentOfResult) rethrows -> [SegmentOfResult.Iterator.Element] where SegmentOfResult : Sequence
//中间的函数,返回值为一个数组,而这个flatmap返回的对象则是一个与自己元素类型相同的数组
func someFunc(_ array:[Int]) -> [Int] {
return array
}
[[1],[2,3],[4,5,6]].map(someFunc)
// [[1], [2, 3], [4, 5, 6]]
[[1], [2, 3], [4, 5, 6]].flatMap(someFunc)
// [1, 2, 3, 4, 5, 6]

什么事copy on write时候

写时复制,指的是swift中的值类型,并不会一开始赋值的时候就去复制,只有再需要修改的时候,才去复制。
这里有相关说明
http://www.jianshu.com/p/7e8ba0659646

如何获取当前代码的函数名和行号

#file 用于获取当前文件文件名
#line 用于获取当前行号
#column 用于获取当前编号
function 用于获取当前函数名
以上这些都是特殊的字面量,多用于调试输出日志。
具体app文档
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html
这里有中文翻译
http://wiki.jikexueyuan.com/project/swift/chapter3/04_Expressions.html

如何声明一个只能被类conform的protocol
申明协议的时候,加一个class即可:如

1
2
3
protocol SomeClassProtocl : class {
func someFunction()
}

guard使用场景
guard和if类似,不同的是,guard总是有一个else语句,如果表达是假或绑定失败的时候,就会执行else语句,而在else语句中一定要停止函数调用

1
2
3
4
guard 1+1 == 2 ? else {
fatalError("something wrong")
}
//常使用场景为,用户登录的时候,验证用户是否有输入用户名密码等

defer使用场景

defer语句块使用中的代码,会在当前作用域结束前调用,常用场景如异常退出后,关闭数据库连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func someQuery() -> ([Result], [Result]){
let db = DBOpen("xxx")
defer{
db.close()
}
guard results1 = db.query("query1") else{
return nil
}
guard results2 = db.query("query2") elee{
return nil
}
return (results1, results2)
}
**注意,如果有多个defer,那么后加入的先执行**

String 与 NSString 的关系与区别

String 是结构体,值类型,NSStirng 是类,引用类型
NSString 与 String之间可以随意转换

1
2
3
4
let someString = "123"
let someNSSting = NSString(string: "123")
let stirngToNSString = someString as NSString
let nsstringToString = someNSSting as String

怎么获取一个 String 的长度

不考虑编码,只是想知道字符的数量,用 characters.count

1
2
3
"hello".count
//如果想知道在某个编码下占多少字节,可以用
"hello".lengthOfBytes(using: .utf8)

如何截取 String 的某段字符串

swift中,有三个取字符串函数
substring:to, substring:from, substring:with

1
2
3
4
5
6
7
let simpleString = "Hello,world"
simpleString.substring(to: simpleString.index(simpleString.startIndex, offsetBy:5))
// hello
simpleString.substring(from: simpleString.endIndex, offsetBy:-5)
// world
simpleString.substring(with: simpleString.index(simpleString.startIndex, ofsetBy: 5) ..< simpleString.index(simpleString.endIndex, ofsetBy: -5))
// ,

throws 和 rethrows 的用法与作用

throws 用在函数上,表示这个函数会抛出异常
有两种情况下会抛出错误,一种是直接使用throw抛出,另一种是调用其他抛出异常的函数时,直接使用try xx没有处理异常。如

1
2
3
4
5
6
7
8
9
10
11
12
13
enum DivideError: Error {
case EqualZeroError
}
func divide(_ a: Double, _ b: Double) throws -> Double{
guard b! = Double(0) else {
//throw 抛出一定要实现Error 协议
throw DivideError.EqualZeroError
}
return a / b
}
func split(pieces: Int) throws -> Double {
retrun try divide(1, Double(pieces))
}

rethrows 与 throws 类似,不过只适用于参数中有函数,且函数会抛出异常的情况下,rethrows 可以用throws替换,反过来不行.如

1
2
3
func processNumber(a: Double, b: Double, function: (Double, Double) throws -> Double) rethrows -> Double {
return try function(a, b)
}

try? 和 try! 是什么意思

这两个都用于处理可抛出异常的函数,使用这两个关键字不用写 do catch,区别在于 try?在用于处理可抛出异常函数时,如果函数抛出异常,则可以返回nil,否则返回函数值的可选值,如:

1
2
3
4
print(try? divide(2, 1))
// Optional(2.0)
print(try? divide(2, 0))
// nil

而try! 则在函数抛出异常的时候奔溃。否则则返回函数的返回值。如

1
2
3
4
print(try! divide(2, 1))
// 2.0
print(try! divide(2, 0))
// 奔溃

associatedtype 的作用

简单来说就是protocol 使用的泛型

例如定义一个列表协议

1
2
3
4
5
6
protocol ListProtocol{
associatedtype Element
func push( _ element: Element)
func pop(_ element: Element) -> Element?
}
被associatedtype关键字修饰的变量,相当于一个占位符,而不能表示具体的类型。具体的类型需要让实现的类来指定。

实现协议的时候,可以使用typealias 指定特定的类型,也可以自动推断,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class IntList: ListProtocol {
typealias Element = Int //使用 typealias 指定为 Int
var list = [Element]()
func push (_ element: Element) {
self.list.append(element)
}
func pop(_ element: Int) -> Int? {
return self.list.popLast()
}
}

class DoubleList : ListProtocol {
var list = [Double]()
func push (_ element: Double) {//自动推断
self.list.append(element)
}
func pop(_ element: Double) -> Double? {
return self.list.popLast()
}
}

使用泛型也可以

1
2
3
4
5
6
7
8
9
class AnyList<T>: ListProtocol{
var list = [T]()
func push(_ element: T){
self.list.append(element)
}
func pop(_ element: T) -> T? {
return self.list.popLast()
}
}

可以使用where字句限定Element类型,如:

1
2
3
4
5
extension ListProtocol where Element == Int {
func isInt() -> Bool {
return true
}
}

什么时候使用 final

final用于限制继承和重写,如果只是需要在某一个属性前加一个final。如果需要限制整个类无法被继承,那么可用用类名之前加一个final

public 和 open 的区别

这两个都用于模块声明需要对外界暴露的函数,区别在于,public 修饰的类,在模块外无法被继承,而open则可以任意继承,公开度来说,open>public

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

1
2
3
4
typealias SomeClosuerType = (Stirng) -> ()
let somClosuer: SomeClosuerType = { (name: String) in
print("hello," name)
}

Self 的使用场景

self 通常在协议中使用,用来表明实现者或者实现者的子类类型

1
2
3
protocol CopyProtocol {
func copy() -> Self
}

如果是结构体去实现,要将Self转换为具体类型

1
2
3
4
5
6
struct SomeStruct : CopyProtocol{
let value: Int
func copy() -> SomeStruct{
return SomeStruct(value: self.value)
}
}

如果是类去实现,则有点复杂,需要有一个required初始化方法,具体可以看这里

1
2
3
4
5
6
class SomeCopyableClass : CopyProtocol {
func copySelf() -> Self {
return type(of: self).init()
}
required init(){}
}

dynamic的作用

由于swift是一个静态语言,所以没有Objective-C中的消息发送这些动态机制,dynamic的作用就是让swift代码也能有Object-C中的动态机制,常用的方法就是KVO了,如果要监控这一个属性,则必须要标记dynamic。可以参考这篇文字http://www.jianshu.com/p/ae26100b9edf

什么时候使用 @objc

@objc 用途是为了在Objective-C 和Swift混编的时候,能够正常的调用Swift代码,可以修饰类,协议,方法,属性。

常用的地方是在定义deleagte 协议中,会将协议中部分方法申明为可选方法,需要用到 @objc

1
2
3
4
5
6
7
8
9
10
11
@objc protocol OptionalProtocol {
@objc optional func optionalFunc()
func normalFunc()
}

class OptionProtocolClass : OptionalProtocol {
func normalFunc(){
}
}
let someOptionalDeleagte: OptionalProtocol = OptionProtocolClass()
someOptionalDeleagte.optionalFunc?()

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

Optional 是一个泛型枚举,大致定义如下

1
2
3
4
enum Optional<Wrapped> {
cass none
case some(Wrapped)
}

如何自定义下标获取

实现subscript即可,如

1
2
3
4
5
6
7
8
9
10
11
12
13
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]
}
}
除了索引数字外,其他类型也是可以的

?? 的作用

可选类型的默认值,但可选类型为nil的时候,会返回后面的值,如
let someValue = optional1 ?? 0

lazy 的作用

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class LazyClass {
lazy var someLazyValue: Int = {
print("lazy init value")
return 1
}()

var someNormalValue : Int = {
print("normal inti value")
return 2
}()
}
let lazyInstance = LazyClass()
print(lazyInstance.someNormalValue)
print(lazyInstance.someLazyValue)
// 打印输出
// normal init value
// 2
// lazy init value
// 1

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

需要实现自OptionSet,一般使用struct实现,由于OptionSet要求有一不可失败的构造器init(rawValue:),而枚举无法做到这一点(枚举的原始值构造器是失败的,而且有些组合值,是没有办法用一个枚举值表示的)

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]

inout 的作用

输入输出参数

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

其实直接转换就可以,例如:SomeError.someError as NSError 但是这样没有错误码,描述等等,如果想和NSError一样有这些东西,只需要实现LocalizedError 和 CustomNSError协议,有些方法有默认实现,可以略过。如:

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
enum SomeError : Error , LocalizedError, CustomNSError {
case error1, error2
public var errorDescription: String? {
switch self {
case .error1:
return "error description error1"
case .error2:
return "error description error2"
}
}

var errorCode: Int {
switch self{
case .error1:
return 1
case .error2:
return 2
}
}
public static var errorDomain: String {
return "error domain SomeError"
}
public var errorUserInfo: [String : Any] {
switch self{
case .error1:
return ["info" :"error1"]
case .error2:
return ["info" :"error2"]
}
}
}

print(SomeError.error1 as NSError)
//Error Domain=error domain SomeError Code=1 "error description error1" UserInfo={info=error1}

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

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

[1, 2, 3]使用了Array实现的ExpressibleByArrayLiteral 协议,用于接收数组的字面值。

map{***} 使用了闭包作为最后一个参数时,可以直接写在调用后面,而且,如果是唯一参数的话,圆括号也可以省略。

闭包没有声明函数参数,返回值类型,数量,依靠的是闭包类型的自动推断。

闭包中语句只有一句时,自动将这一句的结果作为返回值

$0 没有声明参数列表的时候,第一个参数名称为$0,后续参数以此类推

什么是高阶函数

一个函数如果可以以某一个函数作为参数,或者返回值,那么这个函数就称之为高阶函数,如 map, reduce, filter

如何解决引用循环

  1. 转换为值类型,只有类会存在引用循环,所以如果能不用类,是可以解决引用循环的。
  2. deleagte 使用 weak属性
  3. 闭包中,对有可能发生循环引用的对象,使用weak或者unowned修饰

下面的代码会不会崩溃,说出原因

1
2
3
4
var mutableArray = [1, 2, 3]
for _ in mutalbeArray {
mutalbeArray.removeLast()
}

不会,原理不清楚,就算是把removeLast(),换成removeAll(),这个循环也会执行三次,估计是一开始,for in就对mutalbeArray进行了一次值捕获,而Array是一个值类型,removeLast() 并不能修改捕获值

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

使用where子句,限制 Element为String

1
2
3
4
5
6
指定Array集合元素类型为String的扩展。如果Array集合的元素非String类型,扩展里的方法就不能使用
extension Array where Element == Stirng {
var isStringElement: :Bool {
return true;
}
}

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

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

1
2
3
4
5
6
7
8
class AnotherClass {
static func staticMethod(){}
class func classMethod(){}
}

class ChildOfAnotherClass : AnotherClass{
ovveride class func classMethod(){}
}

一个Sequence的索引是不是一定从0开始
不一定,两个for in 并不能保证都是从0开始,且输出结果一致,官方文档如下

Repeated Access

The Sequence protocol makes no requirement on conforming types regarding
whether they will be destructively consumed by iteration. As a
consequence, don’t assume that multiple for-in loops on a sequence
will either resume iteration or restart from the beginning:

1
2
3
4
5
6
7
8
> for element in sequence {
> if ... some condition { break }
> }
>
> for element in sequence {
> // No defined behavior
> }
>

举例

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 Countdown: Sequence, InteratorProtocol {
var count: Int
init(count: Int){
self.count = count
}
func next() -> Int?{
if count == 0 {
return nil
}else{
differ {
count -=1
}
return count;
}
}
}
var countDown = Countdown(count:5)
print("begin for in 1")
for c in countDown {
print(c)
}
print("end for in 1")
print("begin for in 2")
for c in countDown {
print(c)
}
print("end for in 2")

最后输出结果是

1
2
3
4
5
6
7
8
9
begin for in 1
5
4
3
2
1
end for in 1
begin for in 2
end for in 2

很明显,第二次没有输出任何结果,原因就是第二次for in的时候,并没有将 count 重值。

数组都实现了哪些协议

mutableCollection ,实现了可修改的数组,如 a[1] = 2
ExpressibleByArrayLiteral 实现了数组可一次从[1,2,3]这种字面初始化的能力

如何自定义模式匹配

这部分不太懂,贴个链接吧 http://swifter.tips/pattern-match/

autoclosure 的作用

自动闭包,会自动将某一个表达式封装为闭包,如

1
2
3
4
func autoClosureFunction(_ closure: @autoclosure () -> Int){
closure()
}
autoClosureFunction(1)

详细可参考http://swifter.tips/autoclosure/

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

编译器可以跨文件优化编译代码,不局限于一个文件
http://www.jianshu.com/p/8dbf2bb05a1c

下面代码中mutating的作用是什么

1
2
3
4
5
6
7
struct Person{
var name: String{
mutating get {
return store
}
}
}

让不可变对象无法访问属性name

如何让自定义对象支持字面量初始化
有几个协议,分别是
ExpresssibleByArrayLiteral 可以由数组形式初始化
ExpressibleByDictionaryLiteral可以由字典形式初始化
ExpressibleByNilLiteral 可以由nil值初始化
ExpressibleByIntegerLiteral 可以由整数型初始化
ExpressibleByFloatLiteral 可以由浮动数初始化
ExpressibleByBooleanLiteral 可以由布尔值初始化

dynamic framework 和 static framework 的区别是什么

静态库是每一个程序单独打包一份,而动态库则是多个程序之间共享

0%