1. 类(class)和结构体(struct)有什么区别?
在 Swift 中,类是引用类型,结构体是值类型。值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个"指向"。所以他们两者之间的区别就是两个类型的区别。
class Temperature {
var value: Float = 37.0
}
class Person {
var temp: Temperature?
func sick() {
temp?.value = 41.0
}
}
let A = Person()
let B = Person()
let temp = Temperature()
A.temp = temp
B.temp = temp
A.sick()
上面这段代码,由于 Temperature 是 class ,为引用类型,故 A 的 temp 和 B 的 temp指向同一个对象。A 的 temp修改了,B 的 temp 也随之修改。这样 A 和 B 的 temp 的值都被改成了 41.0。如果将 Temperature 改为 struct,为值类型,则 A 的 temp 修改不影响 B 的 temp。
内存中,引用类型如类是在堆上的,值类型如结构体是在栈上的,相比于栈上的操作,堆上的操作更加复杂耗时,所以苹果官方推荐使用结构体,这样可以提高App运行效率。
class优势
- class可以继承,子类可以使用父类的特性和方法
- 类型转换可以在runtime的时候检查和解释一个实例的类型
- 可以用deinit来释放资源
- 一个类可以被多次引用
struct优势 - 结构较小,适用于复制操作,相比于一个class的实例被多次饮用更加安全。
- 无需担心内存memory leak或者多线程冲突问题。
2.Swift是面向对象还是函数式的编程语言?
既是面向对象,也是面向函数。
面向对象:支持类的封装、继承、多态。
函数式编程:支持map、reduce、filter、flatmap这类去除中间状态,数学函数式的方法,更加强调运算结果而不是中间过程。
3.什么是可选类型(optional)?
在Swift中,可选类型是为了表达当一个变量值为空的情况。当一个值为空时,他就是nil。Swift中无论是引用类型或是值类型的变量,都可以是可选类型变量。
// 值类型Float,value 默认值为37.0
var value: Float? = 37.0
// 值类型String,key 默认值为 nil
var key: String? = nil
// 引用类型 UIImage,image 默认值为 nil
let image: UIImage?
OC中没有明确提出可选类型的概念,然而其引用类型却可以为nil,以此来标识其变量值为空的情况。Swift将这一理念扩大到值类型,并且明确提出了可选型的概念。
4.在Swift中,什么是范型(Generics)?
范型在Swift中主要为增加代码的灵活性而生,它可以使得对应的代码满足任意类型的变量或方法。
如以下代码,交换任意类型的变量:
func swap<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
Swift 是类型安全的语言,所以这里交换的两个变量其类型必须一致。
5. 请说明并比较以下关键词:Open, Public, Internal, File-private, Private
Swift有5个级别的访问控制权限,从高到低是Open, Public, Internal, File-private, Private。
基本原则是:高级别的变量不允许被定义为低级别变量的成员变量。比如一个 private 的 class 中不能含有 public 的 String。反之,低级别的变量却可以定义在高级别的变量中。比如 public 的 class 中可以含有 private 的 Int。
- Open:具备最高的访问权限,修饰的类和方法可以在任意Module中被访问和重写,是Swift3中新添加的访问权限。
- Public:权限仅次于Open,区别是他修饰的对象在任意Module中可以被访问,但不能重写。
- Internal:默认的权限,表示只能在当前定义的Module中访问和重写,可以被一个Module中的多个文件访问,但不能被其他的Module中的访问。
- File-private:Swift3新添加的权限,被修饰的对象只能在当前文件中使用,即它可以被一个文件中不同的class、extension、struct共同使用。
- Private:最低的访问权限,对象只能在定义的作用域内及其对应的扩展中使用,离开了这个对象,即使是同一个文件中的对象,也无法访问。
6. 请说明并比较以下关键词:strong, weak, unowned
Swift内存管理机制和OC一样为ARC,基本原理是,一个对象没有任何强引用指向它时,其占用的内存会被回收,反之,只要有任何一个强引用指向该对象,它就会一直存在内存中。
- strong代表强引用,默认属性,引用计数+1
- weak弱引用,引用计数不会增加,且对象释放后,指针会自动置为nil,不会野指针。
- unowned和弱引用本质上一样,区别是对象释放后,依然会有一个无效的引用指向该对象,不是Optional也不是nil,如果继续访问,会崩溃。
weak和unowned的使用场景有如下区别: - 当访问对象时,该对象可能已经释放,则用weak,如delegate。
- 当访问对象确定不会被释放,则用owned,比如self的使用。
- 为了安全起见,直接用weak。
7. 在 Swift 中,怎样理解是 copy-on-write?
当值类型如struct在复制时,复制的对象和原对象实际上在内存里指向同一个对象,当且仅当复制后的对象进行修改的时候,才会在内存里创建一个新的对象,如
// arrayA 是一个数组,为值类型
let arrayA = [1, 2, 3]
// arrayB 这个时候与 arrayA 在内存中是同一个东西,内存中并没有生成新的数组
var arrayB = arrayA
// arrayB 被修改了,此时 arrayB 在内存中变成了一个新的数组,而不是原来的 arrayA
arrayB.append(4)
复制的数组和原数组共享同一个地址直到其中之一发生改变,这样设计使得值类型可以复制多次而无需耗费多余内存,只有变化的时候才会增加开销,内存的使用更加高效。
8. 什么是属性观察(Property Observer)?
指在当前类型内对特定属性进行监视,并作出响应的行为。是swift的特性,有2种,为willSet和didSet,例如
var title: String {
willSet {
print("将标题从\(title)设置到\(newValue)")
}
didSet {
print("已将标题从\(oldValue)设置到\(title)")
}
}
这段代码对title做了监听,title发生改变前,willSet对应的作用域将被执行,新的值是newValue,当title发生改变之后,didSet对应的作用域将被执行,原来的值为oldValue,这就是属性观察。
总结:初始化方法对属性的设定,以及在willSet和didSet中对属性的再次设定都不会触发属性观察的调用。
9. 结构体中修改成员变量的方法
下面代码有什么问题?
protocol Pet {
var name: String { get set }
}
struct MyDog: Pet {
var name: String
func changeName(name: String) {
self.name = name
}
}
应该在 func changeName(name: String) 前加上关键词 mutating,表示该方法将会修改结构体中自己的成员变量。
注意,在设计协议的时候,由于protocol 可以被 class 和 struct 或者 enum 实现,故而要考虑是否用 mutating 来修饰方法。
类class中不存在这个问题,因为类可以随意修改自己的成员变量。
10. 用 Swift 实现或(||)操作
最直接的解法
func ||(left: Bool, right: Bool) –> Bool {
if left {
return true
} else {
return right
}
}
勉强正确但并不高效,或操作的本质是当左边为真的时候,我们无须计算右边,而上面这种事先将右边默认值预先准备好,再传入进行操作。当右边值的计算十分复杂时,会造成性能上浪费,所以正确方法如下:
func ||(left: Bool, right: @autoclosure () -> Bool) –> Bool {
if left {
return true
} else {
return right()
}
}
autoclosure可以将右边值的计算推迟到判定left为false的时候,这样就可以避免第一种方法带来的不必要的开销了。
11. 实现一个函数。输入是任一整数,输出要返回输入的整数+ 2
利用Swift 的 Currying 特性:
func add(_ num: Int) -> (Int) -> Int {
return { val in
return num + val
}
}
let addTwo = add(2), addFour = add(4), addSix = add(6), addEight = add(8)
Swift中的柯里化特性是函数式编程思想的体现,他将接受多个参数的方法进行变形,并用高阶函数的方式进行处理,使整个代码更加灵活。
12. 实现一个函数。求 0 到 100(包括0和100)以内是偶数并且恰好是其他数字平方的数。
简单方法:
func evenSquareNums(from: Int, to: Int) -> [Int] {
var res = [Int]()
for num in from...to where num % 2 == 0{
if (from...to).contains(num * num) {
res.append(num * num)
}
}
return res
}
evenSquareNums(from: 0, to: 100)
上面的写法正确,但不够优雅。首先这个方法完全可以利用泛型进行优化,同时可以在创建 res 数组时加上 reserveCapacity 以保证其性能。其实面对这个题目,最简单直接的写法是用函数式编程的思路,一行既可以解决问题:
(0...10).map { $0 * $0 }.filter { $0 % 2 == 0 }
Swift 有函数式编程的思想。其中 flatMap, map, reduce, filter 是其代表的方法。本题中map和 filter 的组合使用。相比于一般的 for 循环,这样的写法要更加得简洁漂亮。
网友评论