原文链接 卓同学的 Swift 面试题
class 和 struct 的区别
class 为类, struct 为结构体, 类是引用类型, 结构体为值类型, 结构体不可以继承
不通过继承,代码复用(共享)的方式有哪些
扩展, 全局函数
Set 独有的方法有哪些?
// 定义一个 set
let setA: Set<Int> = [0,1, 2, 3, 4, 4]// {1, 2, 3, 4}, 顺序可能不一致, 同一个元素只有一个值
let setB: Set<Int> = [1, 3, 5, 7, 9]// {1, 3, 5, 7, 9}
// 取并集 A | B
let setUnion = setA.union(setB)// {0,1, 2, 3, 4, 5, 7, 9}
// 取交集 A & B
let setIntersect = setA.intersection(setB)// {1, 3}
// 取差集 A - B
let setRevers = setA.subtracting(setB) // {0,2, 4}
// 取对称差集, A XOR B = A - B | B - A
let setXor = setA.symmetricDifference(setB) //{0,2, 4, 5, 7, 9}
实现一个 min 函数,返回两个元素较小的元素
func getMin<T: Comparable>(_ a: T, _ b: T) -> T {
return a < b ? a : b
}
getMin(1, 2)
map、filter、reduce 的作用 && map 与 flatmap 的区别
具体请参看我的博客 Swift 的高阶函数,map、flatMap、filter、reduce
map 函数 -- 对数组中的每一个对象做一次运算
let stringArray = ["Objective-C", "Swift", "Python", "HTML5", "C",""]
let resultAry = stringArray.map { (element) -> Int? in
if element.length > 0 {
return element.length
}else{
return nil
}
}
print(resultAry)
//[Optional(11), Optional(5), Optional(6), Optional(5), Optional(1), nil]
fiatMap 函数 -- 也是对每个对象做一次运算,但是有区别
区别1 不会返回 nil ,会把 optional 类型解包
let stringArray = ["Objective-C", "Swift", "Python", "HTML5", "C",""]
let resultAry = stringArray.flatMap { (element) -> Int? in
if element.length > 0 {
return element.length
}else{
return nil
}
}
print(resultAry)
//[11, 5, 6, 5, 1]
区别2 会把N 维数组变成成一个 1维 数组 返回
let stringArray = [["Objective-C", "Swift"], ["Python", "HTML5", "C",""]]
let resultAry = stringArray.map { $0 }
print(resultAry)
//[["Objective-C", "Swift"], ["Python", "HTML5", "C", ""]]
//flatMap
let stringArray = [["Objective-C", "Swift"], ["Python", "HTML5", "C",""]]
let resultAry = stringArray.flatMap { $0 }
print(resultAry)
//["Objective-C", "Swift", "Python", "HTML5", "C", ""]
filter 过滤,数组中的元素按照 闭包里面的规则过滤
let stringArray = ["Objective-C", "Swift", "Python", "HTML5", "C",""]
let resultAry = stringArray.filter { (element) -> Bool in
//元素长度大于5的 取出
return element.length >= 5
}
print(resultAry)
//["Objective-C", "Swift", "Python", "HTML5"]
reduce 计算,按顺序对数组中的元素进行操作,然后记录操作的值再进行下一步相同的操作,可以想象成累加。
let stringArray = ["Objective-C", "Swift", "Python", "HTML5", "C",""]
let resultStr = stringArray.reduce("Hi , I'm PierceDark,") { (element1, element2) -> String in
return element1 + " ," + element2
}
print(resultStr)
//Hi , I'm PierceDark, ,Objective-C ,Swift ,Python ,HTML5 ,C ,
什么是 copy on write
写时复制, 指的是 swift 中的值类型, 并不会在一开始赋值的时候就去复制, 只有在需要修改的时候, 才去复制.
这里有详细的说明
《Advanced Swift》笔记:在Swift结构体中实现写时复制
如何获取当前代码的函数名和行号
#file
用于获取当前文件文件名
#line
用于获取当前行号
#column
用于获取当前列编号
#function
用于获取当前函数名
如何声明一个只能被类 conform 的 protocol
加一个 class
protocol SomeClassProtocl: class {
func someFunction()
}
guard 使用场景
提前判断,如果表达式是假或者值绑定失败的时候, 会执行 else
语句, 且在 else
语句中一定要停止函数调用
使用场景如登录判断 账号密码 长度
defer 使用场景
defer
语句块中的代码, 会在当前作用域结束前调用, 常用场景如异常退出后, 关闭数据库连接 ,而且 defer
是先加入后执行
func someQuery() -> ([Result], [Result]){
let db = DBOpen("xxx")
defer {
db.close()
}
guard results1 = db.query("query1") else {
return nil
}
guard results2 = db.query("query2") else {
return nil
}
return (results1, results2)
}
String 与 NSString 的关系与区别
String
是结构体,值类型,NSString
是类,引用类型。可以相互转换
怎么获取一个 String 的长度
"test".count
如何截取 String 的某段字符串
PS:Swift
截取子串真的不好用= = ,Swift4
不用substring:to
, substring:from
, substring:with
了,但还是很难用 .还不如转成NSString
再去截取。。
来自Stack Overflow
let newStr = str.substring(to: index) // Swift 3
let newStr = String(str[..<index]) // Swift 4
let newStr = str.substring(from: index) // Swift 3
let newStr = String(str[index...]) // Swift 4
let range = firstIndex..<secondIndex // If you have a range
let newStr = = str.substring(with: range) // Swift 3
let newStr = String(str[range]) // Swift 4
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)
}
try? 和 try!是什么意思
这两个都用于处理可抛出异常的函数, 使用这两个关键字可以不用写 do catch.
区别在于, try? 在用于处理可抛出异常函数时, 如果函数抛出异常, 则返回 nil, 否则返回函数返回值的可选值
例子
print(try? divide(3, 1))
// Optional(3.0)
print(try? divide(3, 0))
// nil
而 try! 则在函数抛出异常的时候崩溃, 否则则返会函数返回值, 相当于(try? xxx)!, 如:
print(try! divide(3, 1))
// 2.0
print(try! divide(3, 0))
// 崩溃
associatedtype 的作用
这里来自:yww的这篇回答
protocol
使用的泛型
protocol ListProtcol {
associatedtype Element
func push(_ element:Element)
func pop(_ element:Element) -> Element?
}
实现协议的时候, 可以使用 typealias 指定为特定的类型, 也可以自动推断, 如
class IntList: ListProtcol {
typealias Element = Int // 使用 typealias 指定为 Int
var list = [Element]()
func push(_ element: Element) {
self.list.append(element)
}
func pop(_ element: Element) -> Element? {
return self.list.popLast()
}
}
class DoubleList: ListProtcol {
var list = [Double]()
func push(_ element: Double) {// 自动推断
self.list.append(element)
}
func pop(_ element: Double) -> Double? {
return self.list.popLast()
}
}
使用泛型也可以
class AnyList<T>: ListProtcol {
var list = [T]()
func push(_ element: T) {
self.list.append(element)
}
func pop(_ element: T) -> T? {
return self.list.popLast()
}
}
可以使用 where 字句限定 Element 类型, 如:
extension ListProtcol where Element == Int {
func isInt() ->Bool {
return true
}
}
什么时候使用 final
不能继承和重写,可用到 属性
/函数
/类
public 和 open 的区别
这两个都用于在模块中声明需要对外界暴露的函数, 区别在于, public
修饰的类, 在模块外无法继承, 而 open
则可以任意继承, 公开度来说, public < open
声明一个只有一个参数没有返回值闭包的别名
typealias SomeClosuerType = (String) -> ()
let someClosuer: SomeClosuerType = { (name: String) in
print("hello,", name)
}
Self 的使用场景
这里来自:yww的这篇回答
Self 通常在协议中使用, 用来表示实现者或者实现者的子类类型
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(){}
}
dynamic 的作用
动态化。因为Swift
是一个静态语言,如果需要有和OC
一样的动态化机制就需要加上dynamic
。
应用场景有Runtime
相关。参看Swift Runtime分析:还像OC Runtime一样吗?
什么时候使用 @objc
Swift4
之后,继承NSObject
的Swift
类不会自动与 OC
交互了,属性前面需要加上@objc
另一个常用的地方为了在 Objective-C
和 Swift
混编的时候, 能够正常调用 Swift
代码. 可以用于修饰类, 协议, 方法, 属性.
常用的地方是在定义 delegate
协议中, 会将协议中的部分方法声明为可选方法, 需要用到
@objc protocol OptionalProtocol {
@objc optional func optionalFunc()
func normalFunc()
}
class OptionProtocolClass: OptionalProtocol {
func normalFunc() {
}
}
let someOptionalDelegate: OptionalProtocol = OptionProtocolClass()
someOptionalDelegate.optionalFunc?()
Optional(可选型) 是用什么实现的
枚举
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
如何自定义下标获取
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
时,输出后面给定的值
var test : String? = nil
print(test ?? "optional = nil")
//输出 optional = nil
lazy 的作用
用到的时候才初始化,懒加载
一个类型表示选项,可以同时表示有几个选项选中(类似 UIViewAnimationOptions ),用什么类型表示
这里来自:yww的这篇回答
需要实现自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]
inout 的作用
输入输出参数,改变函数参数的值
Error 如果要兼容 NSError 需要做什么操作
这里来自:yww的这篇回答
其实直接转换就可以, 例如 SomeError.someError as NSError 但是这样没有错误码, 描述等等, 如果想和 NSError 一样有这些东西, 只需要实现 LocalizedError 和 CustomNSError 协议, 有些方法有默认实现, 可以略过, 如:
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 }
- 快速创建数组
- 第一个参数用
$0
代替 - 闭包没有声明函数参数, 返回值类型, 数量, 依靠的是闭包类型的自动推断
- 闭包中语句只有一句时, 自动将这一句的结果作为返回值
什么是高阶函数
一个函数如果可以以某一个函数作为参数, 或者是返回值, 那么这个函数就称之为高阶函数, 如 map, reduce, filter
如何解决引用循环
- 转换为值类型
- 注意各个强引用
可以参考我的博客Swift 强引用的解决方案(unowned 、 weak 、隐式解析可选属性)
下面的代码会不会崩溃,说出原因
var mutableArray = [1,2,3]
for _ in mutableArray {
mutableArray.removeLast()
}
跑了一遍不会。。可能因为数组里面元素是连续的储蓄位置,然后删除掉之后不会置nil
? 或者for in
的时候复制了?
给集合中元素是字符串的类型增加一个扩展方法,应该怎么声明
extension Set where Element == String {
var isStringElement:Bool {
return true
}
}
定义静态方法时关键字 static 和 class 有什么区别
static
不能被继承 ,class
可以
一个 Sequence 的索引是不是一定从 0 开始?
记得不是。但是想不起来例子。。。
这里参考yww的这篇回答
class Countdown: Sequence, IteratorProtocol {
var count: Int
init(count: Int) {
self.count = count
}
func next() -> Int? {
if count == 0 {
return nil
} else {
defer { 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")
最后输出的结果是
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 的作用
自动闭包
http://swifter.tips/autoclosure/
编译选项 whole module optmization 优化了什么
编译器可以跨文件优化编译代码, 不局限于一个文件.
http://www.jianshu.com/p/8dbf2bb05a1c
下面代码中 mutating 的作用是什么
struct Person {
var name: String {
mutating get {
return store
}
}
}
mutating
表示有可能修改这个结构体,所以只有var
的对象才可以调用
如何让自定义对象支持字面量初始化
不太懂,参考yww的这篇回答
有几个协议, 分别是
ExpressibleByArrayLiteral
可以由数组形式初始化
ExpressibleByDictionaryLiteral
可以由字典形式初始化
ExpressibleByNilLiteral
可以由nil 值初始化
ExpressibleByIntegerLiteral
可以由整数值初始化
ExpressibleByFloatLiteral
可以由浮点数初始化
ExpressibleByBooleanLiteral
可以由布尔值初始化
ExpressibleByUnicodeScalarLiteral
ExpressibleByExtendedGraphemeClusterLiteral
ExpressibleByStringLiteral
这三种都是由字符串初始化, 上面两种包含有 Unicode 字符和特殊字符
dynamic framework 和 static framework 的区别是什么
静态库和动态库,
静态库是每一个程序单独一份, 动态库多个程序之间共享
个人只能打包静态库
为什么数组索引越界会崩溃,而字典用下标取值时 key 没有对应值的话返回的是 nil 不会崩溃。
数组的对象的储蓄地址是连续的,如果越界了,那取到的地址不一定可用,所以报错。毕竟还是需要有可以信任的部分的
一个函数的参数类型只要是数字(Int、Float)都可以,要怎么表示。
泛型?
func isNumber<T : SignedNumber>(number : T){
print(" it is a number")
}
网友评论
for _ in mutableArray {
mutableArray.removeLast()
}
这段代码不会崩溃的原因,我的理解是,for in 循环内部是一个Iterator的实现,array.makeIterator()返回了一个迭代器。所有for in 迭代的是一个迭代器,和原数组没有关系了,所以不会崩溃