学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

Swift面试基础题

Struct和Class的区别

  1. Struct不支持继承、Class支持继承
  2. Struct是值类型,Class是引用类型
  3. Struct无法修改自身属性值,函数需要添加mutating关键字
  4. Struct初始化方法是基于属性的
  5. Struct不需要deinit方法,因为值类型不关心引用计数,Class需要deinit方法。

结构体不可以继承

Swift值类型的写时复制

  • 只有当一个结构体发生了写入行为时才会有复制行为。

  • 在结构体内部用一个引用类型来存储实际的数据,在不进行写入操作的普通传递过程中,都是将内部的reference的应用计数+1,在进行写入操作时,对内部的reference做一次copy操作用来存储新的数据,防止和之前的reference产生意外的数据共享。

  • swift中提供该[isKnownUniquelyReferenced]函数,他能检查一个类的实例是不是唯一的引用,如果是,我们就不需要对结构体实例进行复制,如果不是,说明对象被不同的结构体共享,这时对它进行更改就需要进行复制。

defer的用法

使用defer代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。

defer 语句块中的代码, 会在当前作用域结束前调用。每当一个作用域结束就进行该作用域defer执行。

1
2
3
4
5
6
7
8
9
10
11
func doSomethingFile{
openDirectory()
defer{
closeDirectory()
}
openFile()
defer{
closeFile()
}
// do other things
}

static和class的区别

在Swift中static和class都表示“类型范围作用域”的关键字。
在所有类型中(class、static、enum)中,我们可以使用static来描述类型作用域。class是专门用于修饰class类型的

static可以修饰属性和方法

  • 所修饰的属性和方法不能够被重写。
  • static修饰的类方法和属性包含了final关键字的特性,重写会报错

class修饰方法和计算属性

  • 我们同样可以使用class修饰方法和计算属性,但是不能够修饰存储属性。
  • 类方法和计算属性是可以被重写的,可以使用class关键字也可以是static

mutating关键字的使用?

类是引用类型,而结构和枚举是值类型。默认情况下,不能在其实例方法中修改值类型的属性。为了修改值类型的属性,必须在实例方法中使用mutating关键字。使用此关键字,您的方法将能够更改属性的值,并在方法实现结束时将其写回到原始结构

闭包是引用类型吗?

闭包是引用类型。如果一个闭包被分配给一个变量,这个变量复制给另一个变量,那么他们引用的是同一个闭包,他们的捕捉列表也会被复制。

闭包和函数是引用类型,将函数或闭包赋值给一个常量还是变量,实际上都是将常量或变量的值设置为对应函数或闭包的引用。

逃逸闭包

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。

1
2
3
4
5
func request(result:@escaping((String)->())){
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 10) {
result("数据结果")
}
}

逃逸闭包的生命周期是长于函数的。

逃逸闭包的生命周期:

  1. 闭包作为参数传递给函数;
  2. 退出函数;
  3. 闭包被调用,闭包生命周期结束。

非逃逸闭包, 永远不会离开一个函数的局部作用域的闭包就是非逃逸闭包。

1
2
3
func player(complete:(Bool)->()){
complete(true)
}

非逃逸闭包的生命周期:

  1. 闭包作为参数传给函数;
  2. 函数中运行改闭包;
  3. 退出函数。

为什么要分逃逸闭包和非逃逸闭包

为了管理内存,闭包会强引用它捕获的所有对象,比如你在闭包中访问了当前控制器的属性、函数,编译器会要求你在闭包中显示 self 的引用,这样闭包会持有当前对象,容易导致循环引用。
而对于非逃逸闭包:

非逃逸闭包不会产生循环引用,它会在函数作用域内释放,编译器可以保证在函数结束时闭包会释放它捕获的所有对象。
使用非逃逸闭包可以使编译器应用更多强有力的性能优化,例如,当明确了一个闭包的生命周期的话,就可以省去一些保留(retain)和释放(release)的调用。
非逃逸闭包它的上下文的内存可以保存在栈上而不是堆上。

String 与 NSString 区别?

根本区别: String 是结构体, NSString 是类

怎么获取一个 String 的长度?

Objc 中读取 NSString 长度使用的是 .length,length返回的是基于 UTF-16 的长度
在 Swift 中读取 String 的长度,通常使用的是 count,而 count 本身返回的是 characters.count,只是 Unicode 字符个数。这两者的区别在纯文本中看不出来,但是包含 Emoji 的时候就十分明显了

举个例子:“😆😆😆😆😆😆”,用 Objc 的 length 读取返回的是12,而用 Swift 的 count 读取返回的是6,这在做一些富文本插入操作时,得到的结果绝不会是你想要的。

Swift 有专门的 utf16.count 来对应 NSString 的 .length,在使用的时候只需要 string.utf16.count就可以得到与 Objc 中 length 相同的结果

Swift中的空字符串

如何截取 String 的某段字符串

需要使用 String.Index 来确定要截取的范围,substring:to , substring:from, substring:with.

1
2
3
4
5
6
7
8
9
10

let simpleString = "Hello, world"
simpleString.substring(to: simpleString.index(simpleString.startIndex, offsetBy: 5))

// hello
simpleString.substring(from: simpleString.index(simpleString.endIndex, offsetBy: -5))

// world
simpleString.substring(with: simpleString.index(simpleString.startIndex, offsetBy: 5) ..< simpleString.index(simpleString.endIndex, offsetBy: -5))
// ,

throws 和 rethrows 的用法与作用

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum DivideError: Error {
case EqualZeroError;
}
func divide(_ a: Double, _ b: Double) throws -> Double {
guard b != Double(0) else {
//1. 方法内部直接抛出
throw DivideError.EqualZeroError
}
return a / b
}
func split(pieces: Int) throws -> Double {
//2. Try 调用的内部方法抛出
return try divide(1, Double(pieces))
}

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

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

try try? try! 的区别

这两个都用于处理可抛出异常的函数, 使用这两个关键字可以不用写 do catch

  • try 出现异常处理异常
  • try? 不处理异常,返回一个可选值类型,出现异常返回nil
  • try! 不让异常继续传播,一旦出现异常程序停止,类似NSAssert()

associatedtype的作用:

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

什么时候使用 final?

final关键字可以用在class ,func和var前面进行修饰,表示不允许对内容进行继承或者重写操作。 给一段代码加上final 就意味着你告诉编译器这段代码不会再被修改。

public 和 open 的区别

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

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

没有返回值也就是返回值为 Void

1
2
3
4
5
6
typealias SomeClosuerType = (String) -> (Void)
let someClosuer: SomeClosuerType = { (name: String) in
print("hello,", name)
}
someClosuer("world")
// hello, world

Self 的使用场景

Self 通常在协议中使用, 用来表示实现者或者实现者的子类类型.

如, 定义一个复制的协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protocol CopyProtocol {
func copy() -> Self
}
//如果是结构体去实现, 要将Self 换为具体的类型
struct SomeStruct: CopyProtocol {
let value: Int
func copySelf() -> SomeStruct {
return SomeStruct(value: self.value)
}
}
//如果是类去实现, 则有点复杂, 需要有一个 required 初始化方法
class SomeCopyableClass: CopyProtocol {
func copySelf() -> Self {
return type(of: self).init()
}
required init(){}
}

type(of:) vs .self

通过 type(of:) 和 .self都可以获得元类型的值。那么这两种方式的区别是什么呢?

1
2
3
4
5
let instanceMetaType: String.Type = type(of: "string")
let staicMetaType: String.Type = String.self

let myNum: Any = 1
type(of: myNum) // Int.type

self 取到的是静态的元类型,声明的时候是什么类型就是什么类型。type(of:) 取的是运行时候的元类型,也就是这个实例 的类型。

dynamic 的作用

由于 swift 是一个静态语言, 所以没有 Objective-C 中的消息发送这些动态机制, dynamic 的作用就是让 swift 代码也能有 Objective-C 中的动态机制, 常用的地方就是 KVO 了, 如果要监控一个属性, 则必须要标记为 dynamic

什么时候使用 @objc

@objc 用途是为了在 Objective-C 和 Swift 混编的时候, 能够正常调用 Swift 代码. 可以用于修饰类, 协议, 方法, 属性。
常用的地方是在定义 delegate 协议中, 会将协议中的部分方法声明为可选方法, 需要用到@objc

1
2
3
4
5
6
7
8
9
10
@objc protocol OptionalProtocol {
@objc optional func optionalFunc()
func normalFunc()
}
class OptionProtocolClass: OptionalProtocol {
func normalFunc() {
}
}
let someOptionalDelegate: OptionalProtocol = OptionProtocolClass()
someOptionalDelegate.optionalFunc?()

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

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

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

除了使用 let someValue: Int? = nil 之外, 还可以使用let optional1: Optional = nil 来定义

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

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

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

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

使用 where 子句, 限制 Element 为 String

1
2
3
4
5
6
7
extension Array where Element == String {
var isStringElement:Bool {
return true
}
}
["1", "2"].isStringElement
//[1, 2].isStringElement// error

参考

Swift 基础题