【面试必备】Swift面试题及其答案

前端之家收集整理的这篇文章主要介绍了【面试必备】Swift面试题及其答案前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

  • 原文:Swift Interview Questions and Answers

  • 原作者:Antonio Bello

  • 原作者介绍: Antonio 拥有丰富的编程经验。他开始编程的时候,内存单位还是 byte 而不是 gigabyte,存储空间还是可选的附加物,最常用的语言还是BASIC。现在,Antonio 的兴趣在于 iOS 应用开发、node.js 后端开发,而且他从来不会放过学习新东西的机会。他认为 Swift 是一门很有表现力的语言,不过 Objective-C 仍然是伟大而不同寻常的。

  • 译者:本文由CocoaChina译者 浅夏@旧时光 翻译


虽然Swift出生才一年,但是它已经成为最流行的编程语言之一了。它的语法很简单,以至于当它发布的时候,JavaScript开发者感觉就像下图一样

事实上,Swift是一种复杂的语言。它包含面向对象和函数方法这两个方面,并且随着新版本的发布在一直进化。

Swift的知识浩如烟海——但是怎么测试你掌握了多少?在这篇文章中,我和这个raywenderlich.com网站的教学团队共同写了一个Swift面试问题列表。

你可以用这些问题来测试应聘者关于Swift方面的知识水平,或者测试一下你自己。如果你不知道答案,没关系,没一个问题下面都有答案供你学习。

这些问题包含两个方面:

  • 笔试问题:通过电子邮件做一个编程测试是极好的,因为这涉及到写大量的代码,从代码质量上可以看出一个人的水平。

  • 面试问题:电话面试或者面对面面试也是很好的,因为对面试者来说口头交流会更方面。

每个方面有分成三个等级:

  • 初级:适合读了一到两本有关Swift的书,并且已经开始用Swift开发应用程序的初学者。

  • 中级:适合那些对Swift语言的概念有深刻理解和强烈兴趣的,并且一直在阅读大量有关Swift的博客文章并进行实践的中级工程师。

  • 高级:适合那些以探索Swift语言知识为乐趣,挑战自己,使用前言技术的人们。

假如你想回答这些问题,我建议你在回答这些问题之前,打开Playground运行一下这些问题的代码。这些问题的答案都在Xcode 7.0 Beta 6 版本中测试过。

准备好了吗?系好安全带,现在就开始!

编者注:特别感谢raywenderlich.com教学团队成员Warren Burton,Greg Heo,Mikael Konutgan,Tim Mitra,Luke Parham,Rui Peres,和Ray Wenderlich,他们帮我相处了下面问题中得一些,并且测试区分难度级别。

笔试问题

初学者

问题1、(Swift 1.0及其之后的版本的问题)有什么更好的方法来写下面的for循环?

  1.  
  2. forvari=0;i<5;i++{
  3. print("Hello!")
  4. }

答案:

  1.  
  2. for_in0...4{
  3. print("Hello!")
  4. }

Swift 实现了两个数组运算符closed operator 和 half-operator.前者包含数组中得所有值。例如:下面的例子包含从0到4得所有整数:

  1.  
  2. 0...4

half-operator不包含数组中的最后一个元素,下面的例子会得到的结果和上面的一样:

  1.  
  2. 0..<5

问题2– Swift 1.0 or later

思考下面的问题:

  1.  
  2. structTutorial{
  3. vardifficulty:Int=1
  4. }
  5. vartutorial1=Tutorial()
  6. vartutorial2=tutorial1
  7. tutorial2.difficulty=2

tutorial1.difficulty 和 tutorial2.difficulty的值分别是多少?假如Tutorial是一个类,会有什么不同?并说明原因。

答案:tutorial1.difficulty 的值是1,然而tutorial2.difficulty的值是2.

在Swift中结构体是值类型,他们的值是复制的而不是引用的。下面的一行代码意思是复制了tutorial1的值并把它赋值给tutorial2:

  1.  
  2. vartutorial2=tutorial1

从这一行开始,tutorial2值得改变并不影响tutorial1的值。

假如Tutorial是一个类,tutorial1.difficulty和tutorial2.difficulty的值将都会是2.在Swift中类对象都是引用类型。tutorial1属性的任何改变将会反应到tutorial2上,反之亦然。

问题3 – Swift 1.0 or later

view1声明成var类型,view2声明let类型。这里有什么区别吗?下面的最后一行代码能编译吗?

  1.  
  2. importUIKit
  3. varview1=UIView()
  4. view1.alpha=0.5
  5. letview2=UIView()
  6. view2.alpha=0.5//Willthislinecompile?

答案:view1是个变量可以重新赋值给一个新的实例化的UIView对象。使用let你只赋值一次,所以下面的代码是不能编译的:

  1.  
  2. view2=view1//Error:view2isimmutable

但是UIView是一个引用类型的类,所以你可以改变view2的属性,也就是说最后一行代码是可以编译的:

  1.  
  2. letview2=UIView()
  3. view2.alpha=0.5//Yes!

问题4 – Swift 1.0 or later

下面的代码是把数组里面的名字按字母的顺序排序,看上去比较复杂。尽最大的可能简化闭包里的代码

  1.  
  2. letanimals=["fish","cat","chicken","dog"]
  3. letsortedAnimals=animals.sort{(one:String,two:String)->Boolin
  4. returnone<two
  5. }

答案:

第一个简化的是参数。系统的参数类型推断功能,可以计算出闭包里面参数的类型,所以你不必定义参数的类型:

  1.  
  2. letsortedAnimals=animals.sort{(one,two)->Boolinreturnone<two}

函数返回值也可以被推断出来,所以简化掉,代码变为:

  1. 这个$i 符号可以代替参数名字,代码进一步简化为:

  2. letsortedAnimals=animals.sort{return$0<$1}
  3. 在一个独立的闭包内,return这个关键字是可以省略的。最后声明的返回值就是闭包的返回值:

  4. letsortedAnimals=animals.sort{$0<$1}
  5. 这简化很多了,但是我们不能止步于此!

  6. 对于字符串,有一个定义如下的比较函数

  7. funcBool
  8. 这个简单的小函数可以使你的代码简洁如下:

  9. letsortedAnimals=animals.sort(<)
  10. 注意每一步的编译结果都相同,但是最后一步你的闭包里只有一个字符。

  11. 问题5 Swift 1.0 or later

  12. 下面的代码创建了两个类AddressPerson,并且创建了两个实例对象分别代表RayBrain.

  13. classAddress{
  14. varfullAddress:String
  15. varcity:String
  16. init(fullAddress:String,city:String){
  17. self.fullAddress=fullAddress
  18. self.city=city
  19. }
  20. }
  21. classPerson{
  22. varname:String
  23. varaddress:Address
  24. init(name:String,address:Address){
  25. self.name=name
  26. self.address=address
  27. }
  28. }
  29. varheadquarters=Address(fullAddress:"123TutorialStreet",city:"Appletown")
  30. varray=Person(name:"Ray",address:headquarters)
  31. varbrian=Person(name:"Brian",address:headquarters)
  32. 假设Brain搬家到街对面的建筑物里,那么你会这样更新他的地址:

  33. brian.address.fullAddress="148TutorialStreet"
  34. 这样做将会发生什么?错误出在什么地方呢?

  35. 答案:Ray同样会搬家到新的建筑物里面。Address是一个引用类型类,所以无论你是通过ray或者brain访问headquarters,访问都是同一个实例化对象。headquarters对象的变化也会引起raybrain的变化。你能想象如果Brain收到Ray邮件或者相反Ray收到Brain邮件,将会发生什么?解决方案是创建一个新的Address对象赋值给Brain或者把Address声明成为结构体而不是一个类。

  36. 中级

  37. 问题1 Swift 2.0 or later

  38. 思考下面的代码

  39. varoptional1:String?=nil
  40. varoptional2:String?=.None
  41. nil .None有什么不同?optional1optional2有什么不同?

  42. 答案:两者没有什么不同。Optional.None(简称.None)是optional变量值初始化的标准方法,而nil只是.None语法的一种修饰。事实上下面语句输出是正确的:

  43. nil==.None//OnSwift1.xthisdoesn'tcompile.YouneedOptional
  44. .None
  45. 记住枚举类型的Optional下的None:

  46. enumOptional{
  47. caseNone
  48. caseSome(T)
  49. }
  50. 问题2-Swift 1.0 or later

  51. 下面是thermometer作为类和结构体的例子:

  52. publicclassThermometerClass{
  53. private(set)vartemperature:Double=0.0
  54. publicfuncregisterTemperature(temperature:Double){
  55. self.temperature=temperature
  56. }
  57. }
  58. letthermometerClass=ThermometerClass()
  59. thermometerClass.registerTemperature(56.0)
  60. publicstructThermometerStruct{
  61. private(set)vartemperature:Double=0.0
  62. publicmutatingfuncregisterTemperature(temperature:Double){
  63. self.temperature=temperature
  64. }
  65. }
  66. letthermometerStruct=ThermometerStruct()
  67. thermometerStruct.registerTemperature(56.0)
  68. 但是这段代码编译失败了,请问哪里报错,出错的原因是什么。

  69. 建议:在使用Playground之前,认真阅读代码并思考。

  70. 答案:代码的最后一行不会被编译通过。ThermometerStruct结构体中正确的声明了一个mutating属性函数,它是用来改变结构体内部temperature属性的值的,但是编译器不通过的原因是,通过let创建的不可变的registerTemperature结构体调用registerTemperature函数

  71. 问题3 Swift 1.0 or later

  72. 下面的代码输出是什么?并说明理由。

  73. varthing="cars"
  74. letclosure={[thing]in
  75. print("Ilove\(thing)")
  76. }
  77. thing="airplanes"
  78. closure()
  79. 答案:输出的是:I love cars。当闭包被声明的时候,抓捕列表就复制一份thing变量,所以被捕捉的值并没有改变,即使你给thing赋了一个新值。

  80. 如果你要忽视闭包中捕捉列表的值,那么编译器引用那个值而不是复制。这种情况下,被引用变量的值的变化将会反映到闭包中,正如下面的代码所示:

  81. varthing="cars"
  82. letclosure={
  83. print("Ilove\(thing)")
  84. }
  85. thing="airplanes"
  86. closure()//Prints"Iloveairplanes"
  87. 问题4 Swift 2.0 or later

  88. 下面是一个全局函数,这个函数功能是计算数组中特殊值得个数。(待校验)

  89. funccountUniques(array:Array)->Int{
  90. letsorted=array.sort(<)
  91. letinitial:(T?,Int)=(.None,0)
  92. letreduced=sorted.reduce(initial){($1,$0.0==$1?$0.1:$0.1+1)}
  93. returnreduced.1
  94. }
  95. 它使用了< 和==运算符,他们限制着T(占位类型)的实际类型,也就是说T必须遵循Comparable协议。你可以这样使用它:

  96. countUniques([1,2,3,3])//resultis3
  97. 现在要求你重写上面的方法作为Array的扩展方法,然后你就可以这样写代码

  98. [1,3].countUniques()//shouldprint3
  99. 如何实现?

  100. 答案:在Swift 2.0 中,泛类型可以使用类型约束条件被强制扩展。但是假如这个泛类型不满足这个类型的约束条件,那么这个扩展方法既不可见也无法调用

  101. 所以countUniques全局函数可以作为Array的扩展方法被重写如下:

  102. extensionArraywhereElement:Comparable{
  103. funccountUniques()->Int{
  104. letsorted=sort(<)
  105. letinitial:(Element?,0)
  106. letreduced=sorted.reduce(initial){($1,$0.0==$1?$0.1:$0.1+1)}
  107. returnreduced.1
  108. }
  109. }
  110. 注意:只有元类型实现了Comparable协议新的方法才可以被使用。例如,如果你在全部是UIView对象的数组中调用countUniques,编译器将会报错。

  111. importUIKit
  112. leta=[UIView(),UIView()]
  113. a.countUniques()//compilererrorherebecauseUIViewdoesn'timplementComparable
  114. 问题5- Swift 2.0 or later

  115. 下面一个函数功能是计算两个doubleoptional)类型的数的相除的结果。在执行除法之前,必须提前满足三个条件:

  116. 被除数必须包含nil

  117. 除数必须为包含nil

  118. 除数不能为零

  119. funcdivide(dividend:Double?,bydivisor:Double?)->Double?{
  120. ifdividend==.None{
  121. return.None
  122. }
  123. ifdivisor==.None{
  124. return.None
  125. }
  126. ifdivisor==0{
  127. return.None
  128. }
  129. returndividend!/divisor!
  130. }
  131. 上面的函数可以正常使用,但是会存在两个问题:

  132. 那些前提条件可以利用guard语句。

  133. 使用了强制拆包。

  134. 请使用guard语句和避免使用强制拆包来优化这个函数

  135. 答案:guard语句是在Swift 2.0中引进的,它是用途是在未满足某个条件时,提供一个退出的路径。对于检查是否满足先决条件来说,它是非常有用的。因为它可以使你更清晰的表达逻辑——而不是像i各种f语句嵌套实现那么复杂。下面就是一个例子:

  136. guarddividend!=.Noneelse{return.None}
  137. 它也可以在optional binding(可选绑定)中使用。使用guard语句之后,使拆包后的变量可以被访问。

  138. guardletdividend=dividendelse{return.None}
  139. 所以divide函数被重写如下:

  140.  我们发现隐身的可拆包的运算在代码的最后一行,因为dividenddivisor这两个参数已经被拆包并且以分别以一个常量来存储。

  141.  

    因此,你可以再次使用guard语句,使上面的函数更简洁:

  142.  
     上面的函数中使用了两个guard语句,因为使用了where语句指定了divisor不能为0的条件。

  143.  

    高级

  144.  

    问题1- Swift 1.0 or later

  145.  

    下面是thermometer作为结构体的例子:

  146.  
  147. publicstructThermometer{
  148. publicvartemperature:Double
  149. publicinit(temperature:Double){
  150. self.temperature=temperature
  151. }
  152. }
  153.  
  154.  

    创建一个thermometer实例对象,你可以使用下面的代码

  155.  
  156. vart:Thermometer=Thermometer(temperature:56.8)
  157.  
  158.  

    但是像下面的代码那样初始化对象是否会更好:

  159.  
  160. varthermometer:Thermometer=56.8
  161.  
  162.  

    能这样做吗?如果可以,怎么做?提示it has to do with convertibles,but not convertibles like Camaros and Mustangs

  163.  

    答案:Swift 定义了下面的协议,这些协议可以使一种类型通过字面量的方式来初始化并赋值。

  164.  
  165. NilLiteralConvertible
  166. BooleanLiteralConvertible
  167. IntegerLiteralConvertible
  168. FloatLiteralConvertible
  169. UnicodeScalarLiteralConvertible
  170. ExtendedGraphemeClusterLiteralConvertible
  171. StringLiteralConvertible
  172. ArrayLiteralConvertible
  173. DictionaryLiteralConvertible
  174.  
  175.  

    采用相应的协议并且提供一个允许字面量初始化的公用方法。在Thermometer类型的例子下,我们需要实现FloatLiteralConvertible协议,代码如下:

  176.  
  177. extensionThermometer:FloatLiteralConvertible{
  178. publicinit(floatLiteralvalue:FloatLiteralType){
  179. self.init(temperature:value)
  180. }
  181. }
  182.  
  183.  

    那么现在,你就可以通过一个简单的float数字创建一Thermometer对象,代码如下:

  184.  
     问题2 - Swift 1.0 or later

  185.  

    Swift 拥有一系列预定义的运算符,这些运算符执行不同类型的操作,例如算术运算符和逻辑运算符。它甚至允许创建自定义的运算符,无论是一元运算符还是二元运算符。自定义一个满足一下规格的幂运算符:

  186.  

    以两个整数作为参数

  187.  

    返回第一个参数的第二个参数次方的值

  188.  

    忽略潜在溢出错误

  189.  

    答案:创建一个自定义的运算符需要两个步骤:声明它和实现它。

  190.  

    使用operator关键字来声明指定的类型(一元或者二元)、组成这个运算符字符的顺序已经它的优先级和关联性。

  191.  

    在这中情况下,运算符是^^,类型是infix(二进制),关联性是right,优先级设置成为155,原因是乘法和除法的优先级是150.下面就是具体的声明代码

  192.  
  193. infixoperator^^{associativityrightprecedence155}
  194.  
  195.  

    代码实现如下:

  196.  
  197. func^^(lhs:Int,rhs:Int)->Int{
  198. letl=Double(lhs)
  199. letr=Double(rhs)
  200. letp=pow(l,r)
  201. returnInt(p)
  202. }
  203.  
  204.  

    值得注意的是,它并不需要溢出考虑;如果操作产生的结果int不能代表,如大于int.max,就会发生运行时错误

  205.  

    问题3 - Swift 1.0 or later

  206.  

    你能像下面的代码一样使用原始值定义一个枚举类型吗?如果不行,说明原因。

  207.  
  208. enumEdges:(Double,Double){
  209. caseTopLeft=(0.0,0.0)
  210. caseTopRight=(1.0,0.0)
  211. caseBottomLeft=(0.0,1.0)
  212. caseBottomRight=(1.0,1.0)
  213. }
  214.  
  215.  

    答案:不行。原始值得类型必须满足一下条件

  216.  

    遵守Equatable协议

  217.  

    满足能转换成下列类型中的任何一个类型:

  218.  
  219. a.Int
  220. b.String
  221. c.Character
  222.  
  223.  

    在上面的代码中,原始值即使是独立的个体值,但是它仍然是一个不兼容的元组。

  224.  

    问题4- Swift 2.0 or later

  225.  

    下面的代码定义了一个结构体Pizza和一个协议Pizzeria,这个协议有一个包含makeMargherita()函数的扩展。

  226.  
  227. structPizza{
  228. letingredients:[String]
  229. }
  230. protocolPizzeria{
  231. funcmakePizza(ingredients:[String])->Pizza
  232. funcmakeMargherita()->Pizza
  233. }
  234. extensionPizzeria{
  235. funcmakeMargherita()->Pizza{
  236. returnmakePizza(["tomato","mozzarella"])
  237. }
  238. }
  239.  
  240.  

    现在你将要定义一个如下的Lombardi的餐馆:

  241.  
  242. structLombardis:Pizzeria{
  243. funcmakePizza(ingredients:[String])->Pizza{
  244. returnPizza(ingredients:ingredients)
  245. }
  246. funcmakeMargherita()->Pizza{
  247. returnmakePizza(["tomato","basil",serif; line-height: 28px;"> 下面的代码创建了Lombardis类型的两个实例对象,哪一个对象会产生一个带有basil的margherita披萨?

  248.  
  249. letlombardis1:Pizzeria=Lombardis()
  250. letlombardis2:Lombardis=Lombardis()
  251. lombardis1.makeMargherita()
  252. lombardis2.makeMargherita()
  253.  
  254.  

    答案:两个都可以。Pizzeria协议声明了makeMargherita()方法并且提供了一个默认的实现,而且它又在Lombardis的实现中被重写。在这两种情况下,由于这个方法在协议中被声明,那么在运行时相应的实现就会被调用

  255.  

    假如Pizzeria协议没有声明makeMargherita()方法,但是扩展中仍然提供了如下的代码的这个方法默认的实现,会发生什么?

  256.  
  257. protocolPizzeria{
  258. funcmakePizza(ingredients:[String])->Pizza
  259. }
  260. extensionPizzeria{
  261. funcmakeMargherita()->Pizza{
  262. returnmakePizza(["tomato",serif; line-height: 28px;"> 这种情况下,只有lombardis2会产生一个带有basilpizza,而lombardis1将会产生一个不带basilpizza,原因是它会调用扩展中定义的那个方法

  263.  

    下面的代码有一个编译错误,请指出来并说明原因。

  264.  
  265. structKitten{
  266. }
  267. funcshowKitten(kitten:Kitten?){
  268. guardletk=kittenelse{
  269. print("Thereisnokitten")
  270. }
  271. print(k)
  272. }
  273.  
  274.  

    提示:有三种方法修复它。

  275.  

    答案:guard语句中得else语句必须需要一个返回路径,你可以使用return,抛出异常或者调用@noreturn.最简单的解决方法添加一个return语句,代码如下:

  276.  
  277. funcshowKitten(kitten:Kitten?){
  278. guardletk=kittenelse{
  279. print("Thereisnokitten")
  280. return
  281. }
  282. print(k)
  283. }
  284.  
  285.  

    下面是抛出异常的版本:

  286.  
  287. enumKittenError:ErrorType{
  288. caseNoKitten
  289. }
  290. structKitten{
  291. }
  292. funcshowKitten(kitten:Kitten?)throws{
  293. guardletk=kittenelse{
  294. print("Thereisnokitten")
  295. throwKittenError.NoKitten
  296. }
  297. print(k)
  298. }
  299. tryshowKitten(nil)
  300.  
  301.  

    最后,调用一个@noreturn功能函数 fatalError( ) 解决方案:

  302.  
  303. structKitten{
  304. }
  305. funcshowKitten(kitten:Kitten?){
  306. guardletk=kittenelse{
  307. print("Thereisnokitten")
  308. fatalError()
  309. }
  310. print(k)
  311. }
  312.  
  313.  

    面试试题

  314.  

    你很棒,但是你还没达到绝地武士的要求。任何人都可以写出代码,但是你如何处理理论和实践相结合的开放式问题?

  315.  

    为了解决这些问题,你仍然需要使用Playgroud代码来验证一些问题。

  316.  

    初级

  317.  

    什么是optional类型,它是用来解决什么问题的?

  318.  

    答案:optional类型被用来表示任何类型的变量都可以表示缺少值。在Objective-C中,引用类型的变量是可以缺少值得,并且使用nil作为缺少值。基本的数据类型如int 或者float没有这种功能

  319.  

    Swiftoptional扩展了在基本数据类型和引用类型中缺少值的概念。一个optional类型的变量,在任何时候都可以保存一个值或者为nil

  320.  

    问题2- Swift 1.0 or later

  321.  

    Swfit中,什么时候用结构体,什么时候用类?

  322.  

    答案:一直都有这样的争论:到底是用类的做法优于用结构体,还是用结构体的做法优于类。函数式编程倾向于值类型,面向对象编程更喜欢类。

  323.  

    Swift 中,类和结构体有许多不同的特性。下面是两者不同的总结:

  324.  

    支持继承,结构体不支持

  325.  

    类是引用类型,结构体是值类型

  326.  

    并没有通用的规则决定结构体和类哪一个更好用。一般的建议是使用最小的工具来完成你的目标,但是有一个好的经验是多使用结构体,除非你用了继承和引用语义。

  327.  

    想要了解更多,点击这里

  328.  

    注意:在运行时,结构体的在性能方面更优于类,原因是结构体的方法调用是静态绑定,而类的方法调用是动态实现的。这就是尽可能得使用结构体代替类的又一个好的原因。

  329.  

    问题3- Swift 1.0 or later

  330.  

    什么是泛型?泛型是用来解决什么问题的?

  331.  

    答案:泛型是用来使类型和算法安全的工作的一种类型。在Swift中,在函数和数据结构中都可以使用泛型,例如类、结构体和枚举。

  332.  

    泛型一般是用来解决代码复用的问题。常见的一种情况是,你有一个函数,它带有一个参数,参数类型是A,然而当参数类型改变成B的时候,你不得不复制这个函数

  333.  

    例如,下面的代码中第二个函数就是复制第一个函数——它仅仅是用String类型代替了Integer类型。

  334.  
  335. funcareIntEqual(x:Int,_y:Int)->Bool{
  336. returnx==y
  337. }
  338. funcareStringsEqual(x:String,_y:String)->Bool{
  339. returnx==y
  340. }
  341. areStringsEqual("ray","ray")//true
  342. areIntEqual(1,1)//true
  343.  
  344.  

    Objective-C开发人员可能想到用NSObject类来解决这个问题,代码如下:

  345.  
  346. importFoundation
  347. funcareTheyEqual(x:NSObject,_y:NSObject)->Bool{
  348. returnx==y
  349. }
  350. areTheyEqual("ray","ray")//true
  351. areTheyEqual(1,serif; line-height: 28px;"> 这个代码会按照预期的方式工作,但是它在编译时不安全。它允许字符串和整数相比较,像这样:

  352.  
  353. areTheyEqual(1,"ray")
  354.  
  355.  

    应用程序不会崩溃,但是允许字符串和整数相比较可能不是预想的结果。

  356.  

    通过采用泛型,可以合并这两个函数为一个并同时保持类型安全。下面是代码实现:

  357.  
  358. funcareTheyEqual(x:T,_y:T)->Bool{
  359. returnx==y
  360. }
  361. areTheyEqual("ray","ray")
  362. areTheyEqual(1,1)
  363.  
  364.  

    上面的例子是测试两个参数是否相等,这两个参数的类型受到约束都必须遵循Equatable协议。上面的代码达到预想的结果,并且防止了传递不同类型的参数。

  365.  

    问题4- Swift 1.0 or later

  366.  

    哪些情况下你不得不使用隐式拆包?说明原因。

  367.  

    答案:对optional变量使用隐式拆包最常见的原因如下:

  368.  

    1、对象属性在初始化的时候不能nil,否则不能被初始化。典型的例子是Interface Builder outlet类型的属性,它总是在它的拥有者初始化之后再初始化。在这种特定的情况下,假设它在Interface Builder中被正确的配置——outlet被使用之前,保证它不为nil。

  369.  

    2、解决强引用的循环问题——当两个实例对象相互引用,并且对引用的实例对象的值要求不能为nil时候。在这种情况下,引用的一方可以标记为unowned,另一方使用隐式拆包。

  370.  

    建议:除非必要,不要对option类型使用隐式拆包。使用不当会增加运行时崩溃的可能性。在某些情况下,崩溃可能是有意的行为,但有更好的方法来达到相同的结果,例如,通过使用fatalError( )函数

  371.  

    问题5- Swift 1.0 or later

  372.  

    对一个optional变量拆包有多少种方法?并在安全方面进行评价。

  373.  

    答案:

  374.  
    • 强制拆包 !操作符——不安全

    • 隐式拆包变量声明——大多数情况下不安全

    • 可选绑定——安全

    • 自判断链接(optional chaining)——安全

    • nil coalescing 运算符(空值合并运算符)——安全

    • Swift 2.0 的新特性 guard 语句——安全

    • Swift 2.0 的新特性optional pattern(可选模式) ——安全(@Kametrixom支持

  375.  

  376.  

    中级

  377.  

    Swift 是面向对象编程语言还是函数式编程语言?

  378.  

    答案:Swift是一种混合编程语言,它包含这两种编程模式。它实现了面向对象的三个基本原则:

  379.  
    • 封装

    • 继承

    • 多态

  380.  

    说道Swift作为一种函数式编程语言,我们就不得不说一下什么是函数式编程。有很多不同的方法去定义函数式编程语言,但是他们表达的意义相同。

  381.  

    最常见的定义来自维基百科:...它是一种编程规范…它把电脑运算当做数学函数计算,避免状态改变和数据改变。

  382.  

    很难说Swift是一个成熟的函数式语言,但是它已经具备了函数式语言的基础。

  383.  

    下面的功能特性都包含在Swift中吗?

  384.  

    1、泛型类

  385.  

    2、泛型结构体

  386.  

    3、泛型协议

  387.  

    答案:

  388.  
    • Swift 包含1和2特性。泛型可以在类、结构体、枚举、全局函数或者方法中使用。

    • 3是通过typealias部分实现的。typealias不是一个泛型类型,它只是一个占位符的名字。它通常是作为关联类型被引用,只有协议被一个类型引用的时候它才被定义。

  389.  

    在Objective-C中,一个常量可以这样定义:

  390.  
  391. constintnumber=0;
  392.  
  393.  

    类似的Swift是这样定义的:

  394.  
  395. letnumber=0
  396.  
  397.  

    两者之间有什么不同吗?如果有,请说明原因。

  398.  

    答案:const常量是一个在编译时或者编译解析时被初始化的变量。通过let创建的是一个运行时常量,是不可变得。它可以使用stattic 或者dynamic关键字来初始化。谨记它的的值只能被分配一次。

  399.  

    声明一个静态属性或者函数,我们常常使用值类型的static修饰符。下面就是一个结构体的例子:

  400.  
  401. structSun{
  402. staticfuncilluminate(){}
  403. }
  404.  
  405.  

    对类来说,使用static 或者class修饰符,都是可以的。它们使用后的效果是一样的,但是本质上是不同的。能解释一下为什么不同吗?

  406.  

    static修饰的属性或者修饰的函数都不可以重写。但是使用class修饰符,你可以重写属性或者函数

  407.  

    当static在类中应用的时候,static就成为class final的一个别名。

  408.  

    例如,在下面的代码中,当你尝试重写illuminate()函数时,编译器就会报错:

  409.  
  410. classStar{
  411. classfuncspin(){}
  412. staticfuncilluminate(){}
  413. }
  414. classSun:Star{
  415. overrideclassfuncspin(){
  416. super.spin()
  417. }
  418. overridestaticfuncilluminate(){//error:classmethodoverridesa'final'classmethod
  419. super.illuminate()
  420. }
  421. }
  422.  
  423.  

    你能通过extension(扩展)保存一个属性吗?请解释一下原因。

  424.  

    答案:不能。扩展可以给当前的类型添加新的行为,但是不能改变本身的类型或者本身的接口。如果你添加一个新的可存储的属性,你需要额外的内存来存储新的值。扩展并不能实现这样的任务。

  425.  

    高级

  426.  

    问题1- Swift 1.2

  427.  

    在Swift1.2版本中,你能解释一下用泛型来声明枚举的问题吗?拿下面代码中Either枚举来举例说明吧,它有两个泛型类型的参数T和V,参数T在关联值类型为left情况下使用,参数V在关联值为rihgt情况下使用,代码如下:

  428.  
  429. enumEither{
  430. caseLeft(T)
  431. caseRight(V)
  432. }
  433.  
  434.  

    提示:验证上面的条件,需要在Xcode工程里面,而不是在Playgroud中。同时注意,这个问题跟Swift1.2相关,所以Xcode的版本必须是6.4以上。

  435.  

    答案:上面的代码会出现编译错误

  436.  
  437. unimplementedIRgenerationfeaturenon-fixedmulti-payloadenumlayout
  438.  
  439.  

    问题是T的内存大小不能确定前期,因为它依赖于T类型本身,但enum情况下需要一个固定大小的有效载荷。

  440.  

    最常用的解决方法是讲泛类型用引用类型包装起来,通常称为Box,代码如下:

  441.  
  442. classBox{
  443. letvalue:T
  444. init(_value:T){
  445. self.value=value
  446. }
  447. }
  448. enumEither{
  449. caseLeft(Box)
  450. caseRight(Box)
  451. }
  452.  
  453.  

    这个问题在Swift1.0及之后的版本出现,但是Swift2.0的时候,被解决了。

  454.  

    闭包是引用类型吗?

  455.  

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

  456.  

    UInt类型是用来存储无符号整型的。下面的代码实现了一个有符号整型转换的初始化方法

  457.  

    init(_ value: Int)

  458.  

    然而,在下面的代码中,当你给一个负值的时候,它会产生一个编译时错误

  459.  

    let myNegative = UInt(-1)

  460.  

    我们知道负数的内部结构是使用二进制补码的正数,在保持这个负数内存地址不变的情况下,如何把一个负整数转换成一个无符号的整数?

  461.  

    答案:使用下面的初始化方法

  462.  

    UInt(bitPattern: Int)

  463.  

    描述一种在Swift中出现循环引用的情况,并说明怎么解决

  464.  

    答案:循环引用出现在当两个实例对象相互拥有强引用关系的时候,这会造成内存泄露,原因是这两个对像都不会被释放。只要一个对象被另一个对象强引用,那么该对象就不能被释放,由于强引用的存在,每个对象都会保持对方存在。

  465.  

    解决这个问题的方法是,用weak或者unowned引用代替其中一个的强引用,来打破循环引用。

  466.  

    Swift2.0 增加了一个新的关键字来实现递归枚举。下面的例子是一个枚举类型,它在Node条件下有两个相关联的值类型T和List:

  467.  
  468. enumList{
  469. caseNode(T,List)
  470. }
  471.  
  472.  

    什么关键字可以实现递归枚举?

  473.  

    答案:indirect 关键值可以允许递归枚举,代码如下:

  474.  
  475. enumList{
  476. indirectcaseCons(T,80);">Where To Go From Here?

  477.  

    恭喜你到了文章的最后,如果你不知道所有问题的答案,也不要感到沮丧。

  478.  

    因为上面中得有些问题还是比较复杂的,并且Swift是一门富有表现力的语言,还有很多需要我们学。此外,苹果公司一直改善Swift的新特性,所以即使学的最好的人也不可能知道所有的一切。

  479.  

    在你现有的Swift基础知识之上,要深入了解Swift,你就的看看Swift by Tutorials这本书,或者加入我们实践教学协会RWDevCon

  480.  

    当然,关于Swift所有方面的资源都来是苹果公司官方文档The Swift Programming Language

  481.  

    事实上,学习一门语言最好的方式是用它。在你的工程里或者Plaugroud里面使用Swift编程。Swift几乎可以无缝衔接Object-C,所以在你现有的工程中使用Swift是一个学习Swift的很好的方法

  482.  

    谢谢你的访问和回答这些问题。在下面你可以随意提问交流。我也不介意你在下面贴上自己遇见的难题和挑战,我们可以相互学习。

  483.  

    温馨提示:未经博主许可禁止转载!

猜你在找的Swift相关文章