├── swift面试题.md └── README.md /swift面试题.md: -------------------------------------------------------------------------------- 1 | * 总结关于swift的面试题------持续更新 2 | * 来源于网上、书籍等 3 | * [侵权即删-联系我](741136856@qq.com) 4 | 5 | #### 1.Class 和 Struct 的区别 6 | * 类是引用类型, 结构体为值类型 7 | 8 | * 结构体不可以继承 9 | * 值类型被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝 10 | * 引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝 11 | 12 | #### 2.理解Swift值类型的写时复制 13 | * 只有当一个结构体发生了写入行为时才会有复制行为。 14 | 15 | * 在结构体内部用一个引用类型来存储实际的数据,在不进行写入操作的普通传递过程中,都是将内部的reference的应用计数+1,在进行写入操作时,对内部的reference做一次copy操作用来存储新的数据,防止和之前的reference产生意外的数据共享。 16 | 17 | * swift中提供该[isKnownUniquelyReferenced]函数,他能检查一个类的实例是不是唯一的引用,如果是,我们就不需要对结构体实例进行复制,如果不是,说明对象被不同的结构体共享,这时对它进行更改就需要进行复制。 18 | 19 | #### 3.defer的用法 20 | * 使用defer代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。 21 | 22 | * defer 语句块中的代码, 会在当前作用域结束前调用。每当一个作用域结束就进行该作用域defer执行。 23 | 24 | ```swift 25 | func doSomethingFile{ 26 | openDirectory() 27 | defer{ 28 | closeDirectory() 29 | } 30 | openFile() 31 | defer{ 32 | closeFile() 33 | } 34 | // do other things 35 | } 36 | ``` 37 | 38 | #### 4.inout 输入输出参数 39 | * 函数参数默认为常量。试图从函数主体内部更改函数参数的值会导致编译时错误。这意味着您不能错误地更改参数的值。如果您希望函数修改参数的值,并且希望这些更改在函数调用结束后仍然存在,请将该参数定义为输入输出参数。 40 | 41 | * 您可以通过将inout关键字放在参数类型的前面来编写输入/输出参数。一个在出参数具有传递的值中,由函数修改的功能,并将该部分送回出的功能来代替原来的值。有关输入输出参数的行为以及相关的编译器优化的详细讨论,请参见输入输出参数。 42 | 43 | * 您只能将变量作为输入输出参数的参数传递。您不能将常量或文字值作为参数传递,因为无法修改常量和文字。当您将一个与号(&)作为变量传入in-out参数时,将它放在变量名的前面,以表明该变量可以被函数修改。 44 | 45 | * 注意:输入输出参数不能具有默认值,并且可变参数不能标记为inout。 46 | 47 | ```swift 48 | let temporaryA = a 49 | a = b 50 | b = temporaryA 51 | } 52 | // 参数a本身定义是常量,inout修饰,可以修改a的值 53 | var someInt = 3 54 | var anotherInt = 107 55 | swapTwoInts(&someInt, &anotherInt) 56 | print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") 57 | // Prints "someInt is now 107, and anotherInt is now 3" 58 | ``` 59 | #### 5.什么是高阶函数 60 | * 一个函数如果可以以某一个函数作为参数, 或者是返回值, 那么这个函数就称之为高阶函数 61 | 62 | #### 6. static和class的区别 63 | * 在Swift中static和class都表示“类型范围作用域”的关键字。在所有类型中(class、static、enum)中,我们可以使用static来描述类型作用域。class是专门用于修饰class类型的。 64 | * 1.static可以修饰属性和方法 65 | - 所修饰的属性和方法不能够被重写。 66 | - static修饰的类方法和属性包含了final关键字的特性,重写会报错 67 | * 2.class修饰方法和计算属性 68 | - 我们同样可以使用class修饰方法和计算属性,但是不能够修饰存储属性。 69 | - 类方法和计算属性是可以被重写的,可以使用class关键字也可以是static 70 | 71 | #### 7.自定义模式匹配模式 72 | * 可参考 [swift模式和模式匹配](https://www.cnblogs.com/wjw-blog/p/11674857.html) 73 | * 模式: 74 | 代表单个或者复合值得结构,也就是说模式不是一个特定的值,它是一种抽象的结构,【一句话,不是特指,是泛指】。这样就可以用模式来匹配各种各样的值。 75 | - 例如:(x,y)可以匹配元祖(1.2),以及任何包含两个元素的元组。 76 | 除了利用模式匹配一个值以外,你可以从复合值中提取出部分或全部值,然后把各个部分的值和一个常量或变量绑定起来。 77 | 78 | * swift中的模式分为两类: 79 | - 一种能匹配任何类型的值,另一种在运行时匹配某个特定的值,可能会失败。 80 | - 第一种模式用于结构简单变量,常量和可选绑定中的值。此类模式包括通配符模式,标识符模式,以及包含前两种模式的值绑定模式和元组模式。你可以为这类模式指定一个类型标注,从而限制它们只能匹配某种特定类型的值。 81 | - 第二种模式用于全局模式匹配。这种情况下,你试图匹配的值在运行时可能不存在。此类模式包括枚举用例模式,可选模式,表达式模式和类型转换模式。你在switch语句的case标签中,do语句的catch 子句中,或者再if,while,guard,for-in语句的case条件语句中使用这类模式。 82 | 83 | * 重载 ~= 该运算符 84 | 85 | ```swift 86 | switch 80 { 87 | case "eighty": 88 | //编译通过并且匹配 89 | case "not eighty": 90 | default: 91 | break 92 | } 93 | //以上代码编译直接失败失败 94 | //重载 ~= 函数 95 | func ~= (pattern: String, value: Int) -> Bool { 96 | if pattern == "eighty" { 97 | return value == 80 98 | } else if pattern == "not eighty" { 99 | return value != 80 100 | } else { 101 | return false 102 | } 103 | } 104 | 105 | switch 80 { 106 | case "eighty": 107 | //编译通过并且匹配 108 | case "not eighty": 109 | default: 110 | break 111 | } 112 | 该switch编译通过 113 | ``` 114 | 115 | #### 8.dynamic framework 和 static framework 的区别是什么 116 | * 可参考[该文章](https://www.cnblogs.com/junhuawang/p/7598236.html) 117 | 118 | * 静态库和动态库, 静态库是每一个程序单独打包一份, 而动态库则是多个程序之间共享 119 | 120 | * 静态库和动态库是相对编译期和运行期的:静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要改静态库;而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在。 121 | 122 | * 静态库 好处: 123 | - 模块化,分工合作,提高了代码的复用及核心技术的保密程度 124 | - 避免少量改动经常导致大量的重复编译连接 125 | - 也可以重用,注意不是共享使用 126 | * 动态库 好处: 127 | - 使用动态库,可以将最终可执行文件体积缩小,将整个应用程序分模块,团队合作,进行分工,影响比较小 128 | - 使用动态库,多个应用程序共享内存中得同一份库文件,节省资源 129 | - 使用动态库,可以不重新编译连接可执行程序的前提下,更新动态库文件达到更新应用程序的目的。 130 | 131 | * 不同点: 132 | - 静态库在链接时,会被完整的复制到可执行文件中,如果多个App都使用了同一个静态库,那么每个App都会拷贝一份,缺点是浪费内存。类似于定义一个基本变量,使用该基本变量是是新复制了一份数据,而不是原来定义的; 133 | - 动态库不会复制,只有一份,程序运行时动态加载到内存中,系统只会加载一次,多个程序共用一份,节约了内存。类似于使用变量的内存地址一样,使用的是同一个变量; 134 | 135 | * 共同点: 136 | - 静态库和动态库都是闭源库,只能拿来满足某个功能的使用,不会暴露内部具体的代码信息 137 | 138 | #### 9. Swift 与 Objective-C 的联系与区别? 139 | * Swift和Objective-C 共用一套运行时环境,Swift 的类型可以桥接到Objective-C(下面我简称OC),反之亦然。两者可以互相引用混合编程。 140 | 其次就是,OC 之前积累的很多类库,在 Swift 中大部分依然可以直接使用,当然,Swift3之后,一些语法改变了很多,不过还是有迹可循的。OC出现过的绝大多数概念,比如引用计数、ARC、属性、协议、接口、初始化、扩展类、命名参数、匿名函数等,在Swift中继续有效(可能最多换个术语)。Swift大多数概念与OC一样。当然Swift也多出了一些新兴概念,这些在OC中是没有的,比如范型、元组等。 141 | 142 | #### 10. Swift 比 Objective-C 有什么优势? 143 | - Swift 容易阅读,语法和文件结构简易化。 144 | - Swift 更易于维护,文件分离后结构更清晰。 145 | - Swift 更加安全,它是类型安全的语言。 146 | - Swift 代码更少,简洁的语法,可以省去大量冗余代码。 147 | - Swift 速度更快,运算性能更高。 148 | 149 | #### 11.Swift 是面向对象还是函数式的编程语言? 150 | * Swift 既是面向对象的,又是函数式的编程语言。 151 | 说 Swift 是面向对象的语言,是因为 Swift 支持类的封装、继承、和多态,从这点上来看与 Java 这类纯面向对象的语言几乎毫无差别。 152 | 153 | * 说 Swift 是函数式编程语言,是因为 Swift 支持 map, reduce, filter, flatmap 这类去除中间状态、数学函数式的方法,更加强调运算结果而不是中间过程。 154 | 155 | #### 12.请说明并比较以下关键词:Open, Public, Internal, File-private, Private 156 | * Swift 有五个级别的访问控制权限,从高到底依次为比如 Open, Public, Internal, File-private, Private。 157 | 158 | * 他们遵循的基本原则是:高级别的变量不允许被定义为低级别变量的成员变量。比如一个 private 的 class 中不能含有 public 的 String。反之,低级别的变量却可以定义在高级别的变量中。比如 public 的 class 中可以含有 private 的 Int。 159 | 160 | * Open 具备最高的访问权限。其修饰的类和方法可以在任意 Module 中被访问和重写;它是 Swift 3 中新添加的访问权限。 161 | * Public 的权限仅次于 Open。与 Open 唯一的区别在于它修饰的对象可以在任意 Module 中被访问,但不能重写。 162 | * Internal 是默认的权限。它表示只能在当前定义的 Module 中访问和重写,它可以被一个 Module 中的多个文件访问,但不可以被其他的 Module 中被访问。 163 | * File-private 也是 Swift 3 新添加的权限。其被修饰的对象只能在当前文件中被使用。例如它可以被一个文件中的 class,extension,struct 共同使用。 164 | * Private 是最低的访问权限。它的对象只能在定义的作用域内使用。离开了这个作用域,即使是同一个文件中的其他作用域,也无法访问。 165 | 166 | #### 13.请说明并比较以下关键词:strong, weak, unowned 167 | * Swift 的内存管理机制与 Objective-C一样为 ARC(Automatic Reference Counting)。它的基本原理是,一个对象在没有任何强引用指向它时,其占用的内存会被回收。反之,只要有任何一个强引用指向该对象,它就会一直存在于内存中。 168 | 169 | * strong 代表着强引用,是默认属性。当一个对象被声明为 strong 时,就表示父层级对该对象有一个强引用的指向。此时该对象的引用计数会增加1。 170 | * weak 代表着弱引用。当对象被声明为 weak 时,父层级对此对象没有指向,该对象的引用计数不会增加1。它在对象释放后弱引用也随即消失。继续访问该对象,程序会得到 nil,不亏崩溃 171 | * unowned 与弱引用本质上一样。唯一不同的是,对象在释放后,依然有一个无效的引用指向对象,它不是 Optional 也不指向 nil。如果继续访问该对象,程序就会崩溃。 172 | * 加分回答: 173 | 174 | - weak 和 unowned 的引入是为了解决由 strong 带来的循环引用问题。简单来说,就是当两个对象互相有一个强指向去指向对方,这样导致两个对象在内存中无法释放。 175 | - weak 和 unowned 的使用场景有如下差别: 176 | 177 | - 当访问对象时该对象可能已经被释放了,则用 weak。比如 delegate 的修饰。 178 | - 当访问对象确定不可能被释放,则用 unowned。比如 self 的引用。 179 | - 实际上为了安全起见,很多公司规定任何时候都使用 weak 去修饰。 180 | 181 | #### 14. 说说Swift为什么将String,Array,Dictionary设计成值类型? 182 | 183 | 要解答这个问题,就要和Objective-C中相同的数据结构设计进行比较。Objective-C中,字符串,数组,字典,皆被设计为引用类型。 184 | 185 | * 值类型相比引用类型,最大的优势在于内存使用的高效。值类型在栈上操作,引用类型在堆上操作。栈上的操作仅仅是单个指针的上下移动,而堆上的操作则牵涉到合并、移位、重新链接等。也就是说Swift这样设计,大幅减少了堆上的内存分配和回收的次数。同时copy-on-write又将值传递和复制的开销降到了最低。 186 | 187 | * String,Array,Dictionary设计成值类型,也是为了线程安全考虑。通过Swift的let设置,使得这些数据达到了真正意义上的“不变”,它也从根本上解决了多线程中内存访问和操作顺序的问题。 188 | 189 | * 设计成值类型还可以提升API的灵活度。例如通过实现Collection这样的协议,我们可以遍历String,使得整个开发更加灵活高效。 190 | 191 | #### 15. 闭包是引用类型吗? 192 | 193 | * 闭包是引用类型。如果一个闭包被分配给一个变量,这个变量复制给另一个变量,那么他们引用的是同一个闭包,他们的捕捉列表也会被复制。 194 | 195 | #### 16.Swift mutating关键字的使用? 196 | * 类是引用类型,而结构和枚举是值类型。默认情况下,不能在其实例方法中修改值类型的属性。为了修改值类型的属性,必须在实例方法中使用mutating关键字。使用此关键字,您的方法将能够更改属性的值,并在方法实现结束时将其写回到原始结构 197 | 198 | #### 17.Swift定义常量 和 OC定义常量的区别? 199 | ``` 200 | OC: 201 | const int price = 0; 202 | Swift: 203 | let price = 0 204 | ``` 205 | * OC中用 const 来表示常量,而 Swift 中用 let 来判断是不是常量 206 | * OC中 const 常量类型和数值是在编译时确定的 207 | * Swift 中 let 常量(只能赋值一次),其类型和值既可以是静态的,也可以是一个动态的计算方法,它们在运行时确定的。 208 | 209 | #### 18. 闭包 210 | * `闭包和函数是引用类型`,将函数或闭包赋值给一个常量还是变量,实际上都是将常量或变量的值设置为对应函数或闭包的引用。 211 | ```swift 212 | func makeInCount(count: Int) -> () -> Int { 213 | var total = 0 214 | func incrementer() -> Int { 215 | total += count 216 | return count 217 | } 218 | return incrementer 219 | } 220 | let incrementBySeven = makeInCount(count: 7) 221 | incrementBySeven() 222 | let alsoIncrementBySeven = incrementBySeven 223 | alsoIncrementBySeven() 224 | ``` 225 | * `逃逸闭包`,当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。 例如网络请求⬇️ 226 | ```swift 227 | func request(result:@escaping((String)->())){ 228 | DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 10) { 229 | result("数据结果") 230 | } 231 | } 232 | ``` 233 | 234 | * `非逃逸闭包`, 永远不会离开一个函数的局部作用域的闭包就是非逃逸闭包。 235 | ```swift 236 | func player(complete:(Bool)->()){ 237 | complete(true) 238 | } 239 | ``` 240 | * `自动闭包`,自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。当闭包作为参数传入 可用@autoclosure标记闭包参数 ,可将参数当函数调用而并非以闭包的形式。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包 241 | 242 | ```swift 243 | var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] 244 | print(customersInLine.count) 245 | // 打印出“5” 246 | 247 | let customerProvider = { customersInLine.remove(at: 0) } 248 | print(customersInLine.count) 249 | // 打印出“5” 250 | 251 | print("Now serving \(customerProvider())!") 252 | // 打印出“Now serving Chris!” 253 | 254 | print(customersInLine.count) 255 | // 打印出“4” 256 | 257 | // customersInLine is ["Ewa", "Barry", "Daniella"] 258 | func serve(customer customerProvider: @autoclosure () -> String) { 259 | print("Now serving \(customerProvider())!") 260 | } 261 | serve(customer: customersInLine.remove(at: 0)) 262 | // 打印“Now serving Ewa!” 263 | 264 | //不用 @autoclosure 修饰 265 | func serve(customer customerProvider: () -> String) { 266 | print("Now serving \(customerProvider())!") 267 | } 268 | serve(customer: { customersInLine.remove(at: 0) } ) 269 | //打印“Now serving Ewa!” 270 | ``` 271 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift-Interview-questions 2 | 总结关于swift的面试题------持续更新 3 | * 总结关于swift的面试题------持续更新 4 | * 来源于网上、书籍等 5 | * [侵权即删-联系我](741136856@qq.com) 6 | * [OC面试题](https://github.com/zlfyuan/Objective-C--Interview-questions) 7 | 8 | #### 1.Class 和 Struct 的区别 9 | * 类是引用类型, 结构体为值类型 10 | 11 | * 结构体不可以继承 12 | * 值类型被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝 13 | * 引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝 14 | 15 | #### 2.理解Swift值类型的写时复制 16 | * 只有当一个结构体发生了写入行为时才会有复制行为。 17 | 18 | * 在结构体内部用一个引用类型来存储实际的数据,在不进行写入操作的普通传递过程中,都是将内部的reference的应用计数+1,在进行写入操作时,对内部的reference做一次copy操作用来存储新的数据,防止和之前的reference产生意外的数据共享。 19 | 20 | * swift中提供该[isKnownUniquelyReferenced]函数,他能检查一个类的实例是不是唯一的引用,如果是,我们就不需要对结构体实例进行复制,如果不是,说明对象被不同的结构体共享,这时对它进行更改就需要进行复制。 21 | 22 | #### 3.defer的用法 23 | * 使用defer代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。 24 | 25 | * defer 语句块中的代码, 会在当前作用域结束前调用。每当一个作用域结束就进行该作用域defer执行。 26 | 27 | ```swift 28 | func doSomethingFile{ 29 | openDirectory() 30 | defer{ 31 | closeDirectory() 32 | } 33 | openFile() 34 | defer{ 35 | closeFile() 36 | } 37 | // do other things 38 | } 39 | ``` 40 | 41 | #### 4.inout 输入输出参数 42 | * 函数参数默认为常量。试图从函数主体内部更改函数参数的值会导致编译时错误。这意味着您不能错误地更改参数的值。如果您希望函数修改参数的值,并且希望这些更改在函数调用结束后仍然存在,请将该参数定义为输入输出参数。 43 | 44 | * 您可以通过将inout关键字放在参数类型的前面来编写输入/输出参数。一个在出参数具有传递的值中,由函数修改的功能,并将该部分送回出的功能来代替原来的值。有关输入输出参数的行为以及相关的编译器优化的详细讨论,请参见输入输出参数。 45 | 46 | * 您只能将变量作为输入输出参数的参数传递。您不能将常量或文字值作为参数传递,因为无法修改常量和文字。当您将一个与号(&)作为变量传入in-out参数时,将它放在变量名的前面,以表明该变量可以被函数修改。 47 | 48 | * 注意:输入输出参数不能具有默认值,并且可变参数不能标记为inout。 49 | 50 | ```swift 51 | let temporaryA = a 52 | a = b 53 | b = temporaryA 54 | } 55 | // 参数a本身定义是常量,inout修饰,可以修改a的值 56 | var someInt = 3 57 | var anotherInt = 107 58 | swapTwoInts(&someInt, &anotherInt) 59 | print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") 60 | // Prints "someInt is now 107, and anotherInt is now 3" 61 | ``` 62 | #### 5.什么是高阶函数 63 | * 一个函数如果可以以某一个函数作为参数, 或者是返回值, 那么这个函数就称之为高阶函数 64 | 65 | #### 6. static和class的区别 66 | * 在Swift中static和class都表示“类型范围作用域”的关键字。在所有类型中(class、static、enum)中,我们可以使用static来描述类型作用域。class是专门用于修饰class类型的。 67 | * 1.static可以修饰属性和方法 68 | - 所修饰的属性和方法不能够被重写。 69 | - static修饰的类方法和属性包含了final关键字的特性,重写会报错 70 | * 2.class修饰方法和计算属性 71 | - 我们同样可以使用class修饰方法和计算属性,但是不能够修饰存储属性。 72 | - 类方法和计算属性是可以被重写的,可以使用class关键字也可以是static 73 | 74 | #### 7.自定义模式匹配模式 75 | * 可参考 [swift模式和模式匹配](https://www.cnblogs.com/wjw-blog/p/11674857.html) 76 | * 模式: 77 | 代表单个或者复合值得结构,也就是说模式不是一个特定的值,它是一种抽象的结构,【一句话,不是特指,是泛指】。这样就可以用模式来匹配各种各样的值。 78 | - 例如:(x,y)可以匹配元祖(1.2),以及任何包含两个元素的元组。 79 | 除了利用模式匹配一个值以外,你可以从复合值中提取出部分或全部值,然后把各个部分的值和一个常量或变量绑定起来。 80 | 81 | * swift中的模式分为两类: 82 | - 一种能匹配任何类型的值,另一种在运行时匹配某个特定的值,可能会失败。 83 | - 第一种模式用于结构简单变量,常量和可选绑定中的值。此类模式包括通配符模式,标识符模式,以及包含前两种模式的值绑定模式和元组模式。你可以为这类模式指定一个类型标注,从而限制它们只能匹配某种特定类型的值。 84 | - 第二种模式用于全局模式匹配。这种情况下,你试图匹配的值在运行时可能不存在。此类模式包括枚举用例模式,可选模式,表达式模式和类型转换模式。你在switch语句的case标签中,do语句的catch 子句中,或者再if,while,guard,for-in语句的case条件语句中使用这类模式。 85 | 86 | * 重载 ~= 该运算符 87 | 88 | ```swift 89 | switch 80 { 90 | case "eighty": 91 | //编译通过并且匹配 92 | case "not eighty": 93 | default: 94 | break 95 | } 96 | //以上代码编译直接失败失败 97 | //重载 ~= 函数 98 | func ~= (pattern: String, value: Int) -> Bool { 99 | if pattern == "eighty" { 100 | return value == 80 101 | } else if pattern == "not eighty" { 102 | return value != 80 103 | } else { 104 | return false 105 | } 106 | } 107 | 108 | switch 80 { 109 | case "eighty": 110 | //编译通过并且匹配 111 | case "not eighty": 112 | default: 113 | break 114 | } 115 | 该switch编译通过 116 | ``` 117 | 118 | #### 8.dynamic framework 和 static framework 的区别是什么 119 | * 可参考[该文章](https://www.cnblogs.com/junhuawang/p/7598236.html) 120 | 121 | * 静态库和动态库, 静态库是每一个程序单独打包一份, 而动态库则是多个程序之间共享 122 | 123 | * 静态库和动态库是相对编译期和运行期的:静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要改静态库;而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在。 124 | 125 | * 静态库 好处: 126 | - 模块化,分工合作,提高了代码的复用及核心技术的保密程度 127 | - 避免少量改动经常导致大量的重复编译连接 128 | - 也可以重用,注意不是共享使用 129 | * 动态库 好处: 130 | - 使用动态库,可以将最终可执行文件体积缩小,将整个应用程序分模块,团队合作,进行分工,影响比较小 131 | - 使用动态库,多个应用程序共享内存中得同一份库文件,节省资源 132 | - 使用动态库,可以不重新编译连接可执行程序的前提下,更新动态库文件达到更新应用程序的目的。 133 | 134 | * 不同点: 135 | - 静态库在链接时,会被完整的复制到可执行文件中,如果多个App都使用了同一个静态库,那么每个App都会拷贝一份,缺点是浪费内存。类似于定义一个基本变量,使用该基本变量是是新复制了一份数据,而不是原来定义的; 136 | - 动态库不会复制,只有一份,程序运行时动态加载到内存中,系统只会加载一次,多个程序共用一份,节约了内存。类似于使用变量的内存地址一样,使用的是同一个变量; 137 | 138 | * 共同点: 139 | - 静态库和动态库都是闭源库,只能拿来满足某个功能的使用,不会暴露内部具体的代码信息 140 | 141 | #### 9. Swift 与 Objective-C 的联系与区别? 142 | * Swift和Objective-C 共用一套运行时环境,Swift 的类型可以桥接到Objective-C(下面我简称OC),反之亦然。两者可以互相引用混合编程。 143 | 其次就是,OC 之前积累的很多类库,在 Swift 中大部分依然可以直接使用,当然,Swift3之后,一些语法改变了很多,不过还是有迹可循的。OC出现过的绝大多数概念,比如引用计数、ARC、属性、协议、接口、初始化、扩展类、命名参数、匿名函数等,在Swift中继续有效(可能最多换个术语)。Swift大多数概念与OC一样。当然Swift也多出了一些新兴概念,这些在OC中是没有的,比如范型、元组等。 144 | 145 | #### 10. Swift 比 Objective-C 有什么优势? 146 | - Swift 容易阅读,语法和文件结构简易化。 147 | - Swift 更易于维护,文件分离后结构更清晰。 148 | - Swift 更加安全,它是类型安全的语言。 149 | - Swift 代码更少,简洁的语法,可以省去大量冗余代码。 150 | - Swift 速度更快,运算性能更高。 151 | 152 | #### 11.Swift 是面向对象还是函数式的编程语言? 153 | * Swift 既是面向对象的,又是函数式的编程语言。 154 | 说 Swift 是面向对象的语言,是因为 Swift 支持类的封装、继承、和多态,从这点上来看与 Java 这类纯面向对象的语言几乎毫无差别。 155 | 156 | * 说 Swift 是函数式编程语言,是因为 Swift 支持 map, reduce, filter, flatmap 这类去除中间状态、数学函数式的方法,更加强调运算结果而不是中间过程。 157 | 158 | #### 12.请说明并比较以下关键词:Open, Public, Internal, File-private, Private 159 | * Swift 有五个级别的访问控制权限,从高到底依次为比如 Open, Public, Internal, File-private, Private。 160 | 161 | * 他们遵循的基本原则是:高级别的变量不允许被定义为低级别变量的成员变量。比如一个 private 的 class 中不能含有 public 的 String。反之,低级别的变量却可以定义在高级别的变量中。比如 public 的 class 中可以含有 private 的 Int。 162 | 163 | * Open 具备最高的访问权限。其修饰的类和方法可以在任意 Module 中被访问和重写;它是 Swift 3 中新添加的访问权限。 164 | * Public 的权限仅次于 Open。与 Open 唯一的区别在于它修饰的对象可以在任意 Module 中被访问,但不能重写。 165 | * Internal 是默认的权限。它表示只能在当前定义的 Module 中访问和重写,它可以被一个 Module 中的多个文件访问,但不可以被其他的 Module 中被访问。 166 | * File-private 也是 Swift 3 新添加的权限。其被修饰的对象只能在当前文件中被使用。例如它可以被一个文件中的 class,extension,struct 共同使用。 167 | * Private 是最低的访问权限。它的对象只能在定义的作用域内使用。离开了这个作用域,即使是同一个文件中的其他作用域,也无法访问。 168 | 169 | #### 13.请说明并比较以下关键词:strong, weak, unowned 170 | * Swift 的内存管理机制与 Objective-C一样为 ARC(Automatic Reference Counting)。它的基本原理是,一个对象在没有任何强引用指向它时,其占用的内存会被回收。反之,只要有任何一个强引用指向该对象,它就会一直存在于内存中。 171 | 172 | * strong 代表着强引用,是默认属性。当一个对象被声明为 strong 时,就表示父层级对该对象有一个强引用的指向。此时该对象的引用计数会增加1。 173 | * weak 代表着弱引用。当对象被声明为 weak 时,父层级对此对象没有指向,该对象的引用计数不会增加1。它在对象释放后弱引用也随即消失。继续访问该对象,程序会得到 nil,不亏崩溃 174 | * unowned 与弱引用本质上一样。唯一不同的是,对象在释放后,依然有一个无效的引用指向对象,它不是 Optional 也不指向 nil。如果继续访问该对象,程序就会崩溃。 175 | * 加分回答: 176 | 177 | - weak 和 unowned 的引入是为了解决由 strong 带来的循环引用问题。简单来说,就是当两个对象互相有一个强指向去指向对方,这样导致两个对象在内存中无法释放。 178 | - weak 和 unowned 的使用场景有如下差别: 179 | 180 | - 当访问对象时该对象可能已经被释放了,则用 weak。比如 delegate 的修饰。 181 | - 当访问对象确定不可能被释放,则用 unowned。比如 self 的引用。 182 | - 实际上为了安全起见,很多公司规定任何时候都使用 weak 去修饰。 183 | 184 | #### 14. 说说Swift为什么将String,Array,Dictionary设计成值类型? 185 | 186 | 要解答这个问题,就要和Objective-C中相同的数据结构设计进行比较。Objective-C中,字符串,数组,字典,皆被设计为引用类型。 187 | 188 | * 值类型相比引用类型,最大的优势在于内存使用的高效。值类型在栈上操作,引用类型在堆上操作。栈上的操作仅仅是单个指针的上下移动,而堆上的操作则牵涉到合并、移位、重新链接等。也就是说Swift这样设计,大幅减少了堆上的内存分配和回收的次数。同时copy-on-write又将值传递和复制的开销降到了最低。 189 | 190 | * String,Array,Dictionary设计成值类型,也是为了线程安全考虑。通过Swift的let设置,使得这些数据达到了真正意义上的“不变”,它也从根本上解决了多线程中内存访问和操作顺序的问题。 191 | 192 | * 设计成值类型还可以提升API的灵活度。例如通过实现Collection这样的协议,我们可以遍历String,使得整个开发更加灵活高效。 193 | 194 | #### 15. 闭包是引用类型吗? 195 | 196 | * 闭包是引用类型。如果一个闭包被分配给一个变量,这个变量复制给另一个变量,那么他们引用的是同一个闭包,他们的捕捉列表也会被复制。 197 | 198 | #### 16.Swift mutating关键字的使用? 199 | * 类是引用类型,而结构和枚举是值类型。默认情况下,不能在其实例方法中修改值类型的属性。为了修改值类型的属性,必须在实例方法中使用mutating关键字。使用此关键字,您的方法将能够更改属性的值,并在方法实现结束时将其写回到原始结构 200 | 201 | #### 17.Swift定义常量 和 OC定义常量的区别? 202 | ``` 203 | OC: 204 | const int price = 0; 205 | Swift: 206 | let price = 0 207 | ``` 208 | * OC中用 const 来表示常量,而 Swift 中用 let 来判断是不是常量 209 | * OC中 const 常量类型和数值是在编译时确定的 210 | * Swift 中 let 常量(只能赋值一次),其类型和值既可以是静态的,也可以是一个动态的计算方法,它们在运行时确定的。 211 | 212 | #### 18. 闭包 213 | * `闭包和函数是引用类型`,将函数或闭包赋值给一个常量还是变量,实际上都是将常量或变量的值设置为对应函数或闭包的引用。 214 | ```swift 215 | func makeInCount(count: Int) -> () -> Int { 216 | var total = 0 217 | func incrementer() -> Int { 218 | total += count 219 | return count 220 | } 221 | return incrementer 222 | } 223 | let incrementBySeven = makeInCount(count: 7) 224 | incrementBySeven() 225 | let alsoIncrementBySeven = incrementBySeven 226 | alsoIncrementBySeven() 227 | ``` 228 | * `逃逸闭包`,当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。 例如网络请求⬇️ 229 | ```swift 230 | func request(result:@escaping((String)->())){ 231 | DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 10) { 232 | result("数据结果") 233 | } 234 | } 235 | ``` 236 | 237 | * `非逃逸闭包`, 永远不会离开一个函数的局部作用域的闭包就是非逃逸闭包。 238 | ```swift 239 | func player(complete:(Bool)->()){ 240 | complete(true) 241 | } 242 | ``` 243 | * `自动闭包`,自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。当闭包作为参数传入 可用@autoclosure标记闭包参数 ,可将参数当函数调用而并非以闭包的形式。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包 244 | 245 | ```swift 246 | var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] 247 | print(customersInLine.count) 248 | // 打印出“5” 249 | 250 | let customerProvider = { customersInLine.remove(at: 0) } 251 | print(customersInLine.count) 252 | // 打印出“5” 253 | 254 | print("Now serving \(customerProvider())!") 255 | // 打印出“Now serving Chris!” 256 | 257 | print(customersInLine.count) 258 | // 打印出“4” 259 | 260 | // customersInLine is ["Ewa", "Barry", "Daniella"] 261 | func serve(customer customerProvider: @autoclosure () -> String) { 262 | print("Now serving \(customerProvider())!") 263 | } 264 | serve(customer: customersInLine.remove(at: 0)) 265 | // 打印“Now serving Ewa!” 266 | 267 | //不用 @autoclosure 修饰 268 | func serve(customer customerProvider: () -> String) { 269 | print("Now serving \(customerProvider())!") 270 | } 271 | serve(customer: { customersInLine.remove(at: 0) } ) 272 | //打印“Now serving Ewa!” 273 | ``` 274 | --------------------------------------------------------------------------------