Swift面试题

1、Swift 比 Objective-C 有什么优势?

Swift 速度更快,运算性能更高。
Swift 语法简单易读、代码更少,更加清晰、易于维护
Swift 更加安全,它是类型安全的语言
Swift 泛型、结构体、枚举都很强大
Swift 便捷的函数式编程
Swift 类型判断

2、struct 与 class 的区别

  • struct是值类型,class是引用类型:
    值类型的变量直接包含它们的数据,对于值类型都有它们自己的数据副本,因此对一个变量操作不可能影响另一个变量.值类型包括结构体 (数组和字典),枚举,基本数据类型 (boolean, integer, float等).
    引用类型的变量存储对他们的数据引用,对一个变量操作可能影响另一个变量.
    二者的本质区别:struct是深拷贝;class是浅拷贝。
  • property的初始化不同:
    class 在初始化时不能直接把 property 放在默认的 constructor 的参数里,而是需要自己创建一个带参数的 constructor;而struct可以,把属性放在默认的 constructor 的参数里。
  • 变量赋值方式不同:
    struct是值拷贝;class是引用拷贝。
  • immutable变量:
    swift的可变内容和不可变内容用var和let来甄别,如果初始为let的变量再去修改会发生编译错误。struct遵循这一特性;class不存在这样的问题。
  • mutating function:
    struct 和 class 的差別是 struct 的 function 要去改变 property 的值的时候要加上 mutating,而 class 不用。
  • 继承:
    struct不可以继承,class可以继承。
  • struct比class更轻量:
    struct分配在栈中,class分配在堆中。

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

Swift 既是面向对象的,又是函数式的编程语言。
说 Swift 是面向对象的语言,是因为 Swift 支持类的封装、继承、和多态.
说 Swift 是函数式编程语言,是因为 Swift 支持 map, reduce, filter, flatmap 这类去除中间状态、数学函数式的方法,更加强调运算结果而不是中间过程。

4、Swift为什么将String,Array,Dictionary设计成值类型?

值类型相比引用类型,最大的优势在于内存使用的高效.值类型在栈上操作,引用类型在堆上操作.栈上的操作仅仅是单个指针的上下移动,而堆上的操作则牵涉到合并、移位、重新链接等.也就是说Swift这样设计,大幅减少了堆上的内存分配和回收的次数.同时写时复制又将值传递和复制的开销降到了最低.
String,Array,Dictionary设计成值类型,也是为了线程安全考虑.通过Swift的let设置,使得这些数据达到了真正意义上的“不变”,它也从根本上解决了多线程中内存访问和操作顺序的问题.
设计成值类型还可以提升API的灵活度.

5、Swift mutating关键字的使用?

默认情况下,struct中不能在实例方法中修改值类型的属性.若在实例方法中使用 mutating 关键字,不仅可以在实例方法中修改值类型的属性,而且会在方法实现结束时将其写回到原始结构.

struct Point {
  var x = 0.0, y = 0.0
  mutating func moveByX(deltaX: Double, y deltaY: Double) {
    x += deltaX
    y += deltaY
  }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveByX(2.0, y: 3.0)

6、 Optional(可选型)是什么?Optional(可选型)解决方式?

Optional是一个可选型枚举

enum Optional {
  case none
  case some(Wrapped)
}

Optional 类型表示: 有值 / 没有值
在Objective-C中并没有 Optional 类型, 只有nil,并且nil只能用于表示对象类型无值,并不能用于基础类型(int, float),枚举和结构体.基础类型需要返回类似 NSNotFound 的特殊值来表示无值,所以在Swift中定义了 Optinal 类型来表示各种类型的无值状态,并规定了nil不能用于非可选的常量和变量,只能用于Optinal类型.
解决方式:
强行打开 - 不安全
Optional 类型表示: 有值 / 没有值
在Objective-C中并没有 Optional 类型, 只有nil,并且nil只能用于表示对象类型无值,并不能用于基础类型(int, float),枚举和结构体.基础类型需要返回类似 NSNotFound 的特殊值来表示无值,所以在Swift中定义了 Optinal 类型来表示各种类型的无值状态,并规定了nil不能用于非可选的常量和变量,只能用于Optinal类型.
解决方式:
强行打开 - 不安全

let a: String = x!

隐式解包变量声明 - 在许多情况下不安全

var a = x!

可选绑定 - 安全

if let a = x {
  print("x was successfully unwrapped and is = \(a)")
}

可选链接 - 安全

let a = x?.count

无合并操作员 - 安全

let a = x ?? ""

警卫声明 - 安全

guard let a = x else {
  return
}

可选模式 - 安全

if case let a? = x {
  print(a)
}

7、、swift中,如何阻止方法,属性,下标被子类改写?

在类的定义中使用 final 关键字声明类、属性、方法和下标。final 声明的类不能被继承,final 声明的属性、方法和下标不能被重写。
如果只是限制一个方法或属性被重写,只需要在该方法或者属性前加一个 final.
如果需要限制整个类无法被继承, 那么可以在类名之前加一个final。

7、 inout 的作用

inout 输入输出参数,让输入参数可变类似__block 的作用。
1 函数参数默认为常量。试图从函数主体内部更改函数参数的值会导致编译时错误。这意味着您不能错误地更改参数的值。如果您希望函数修改参数的值,并且希望这些更改在函数调用结束后仍然存在,请将该参数定义为输入输出参数。
2 您可以通过将inout关键字放在参数类型的前面来编写输入/输出参数。一个在出参数具有传递的值中,由函数修改的功能,并将该部分送回出的功能来代替原来的值。
3 您只能将变量作为输入输出参数的参数传递。您不能将常量或文字值作为参数传递,因为无法修改常量和文字。当您将一个与号(&)作为变量传入in-out参数时,将它放在变量名的前面,以表明该变量可以被函数修改。
4 注意:输入输出参数不能具有默认值,并且可变参数不能标记为inout。

8、权限修饰符

open :修饰的属性或者方法在其他作用域既可以被访问也可以被继承或重载 override。
public :修饰的属性或者方法可以在其他作用域被访问,但不能在重载 override 中被访问,也不能在继承方法中的 Extension 中被访问。
internal:被修饰的属性和方法只能在模块内部可以访问,超出模块内部就不可被访问了。(默认)
fileprivate :其修饰的属性或者方法只能在当前的 Swift 源文件里可以访问。
private :只允许在当前类中调用,不包括 Extension ,用 private 修饰的方法不可以被代码域之外的地方访问。
从高到低排序如下:
open > public > interal > fileprivate > private

9、 dynamic framework 和 static framework 的区别是什么?

https://www.cnblogs.com/junhuawang/p/7598236.html
静态库和动态库, 静态库是每一个程序单独打包一份, 而动态库则是多个程序之间共享.
静态库和动态库是相对编译期和运行期的:静态库在程序编译时会被链接到目标代码中,程序运行时将不再更改静态库;而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入。
不同点:
静态库在链接时,会被完整的复制到可执行文件中,如果多个App都使用了同一个静态库,那么每个App都会拷贝一份,缺点是浪费内存.
动态库不会复制,只有一份,程序运行时动态加载到内存中,系统只会加载一次,多个程序共用一份,节约了内存.
共同点:
静态库和动态库都是闭源库,只能拿来满足某个功能的使用,不会暴露内部具体的代码信息.

10、Swift的静态派发

OC 中的方法都是动态派发(方法调用),Swift 中的方法分为静态派发和动态派发.
动态派发:指的是方法在运行时才找具体实现.Swift 中的动态派发和 OC 中的动态派发是一样的.
静态派发:静态派发是指在运行时调用方法不需要查表,直接跳转到方法的代码中执行.
静态派发的特点:
静态派发更高效,因为静态派发免去了查表操作.
静态派发的条件是方法内部的代码必须对编译器透明,且在运行时不能被更改,这样编译器才能帮助我们.
Swift 中的值类型不能被继承,也就是说值类型的方法实现不能被修改或者被复写,因此值类型的方法满足静态派发.

11、Error 如果要兼容 NSError 需要做什么操作

Error是一个协议, swift中的Error 都是enum, 可以转 NSError.如果需要Error有NSError的功能,实现 LocalizedError CustomNSError 协议.

12、Swift 中的 KVC 和 KVO

KVC
要继承 NSObject

class KVCClass :NSObject{
    var someValue: String = "123"
}
let kvc = KVCClass()
kvc.someValue // 123
kvc.setValue("456", forKey: "someValue")
kvc.someValue // 456

KVO
由于 Swift 为了效率, 默认禁用了动态派发, 因此 Swift 要实现 KVO, 除了要继承自 NSObject 外还要将观测的对象标记为 dynamic(让 swift 代码也能有 Objective-C 中的动态机制).

class KVOClass:NSObject {
    dynamic var someValue: String = "123"
    var someOtherValue: String = "abc"
}

class ObserverClass: NSObject {
    func observer() {
        let kvo = KVOClass()
        kvo.addObserver(self, forKeyPath: "someValue", options: .new, context: nil)
        kvo.addObserver(self, forKeyPath: "someOtherValue", options: .new, context: nil)
        kvo.someValue = "456"
        kvo.someOtherValue = "def"
        kvo.removeObserver(self, forKeyPath: "someValue")
        kvo.removeObserver(self, forKeyPath: "someOtherValue")
    }
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        print("\(keyPath!) change to \(change![.newKey] as! String)")
    }
}
ObserverClass().observer()
//someValue change to 456

13、 associatedtype 的作用

关联类型:为协议中的某个类型提供了一个别名,其代表的真实类型在实现者中定义.

//协议,使用关联类型

protocol TableViewCell {
    associatedtype T
    func updateCell(_ data: T)
}
 
//遵守TableViewCell
class MyTableViewCell: UITableViewCell, TableViewCell {
    typealias T = Model
    func updateCell(_ data: Model) {
        // do something ...
    }
}

14、swift中高阶函数map、filter、reduce

map 用于映射, 可以将一个列表转换为另一个列表

[1, 2, 3].map{"\($0)"}// 数字数组转换为字符串数组
["1", "2", "3"]

filter 用于过滤, 可以筛选出想要的元素

[1, 2, 3].filter{$0 % 2 == 0} // 筛选偶数
// [2] 

reduce 合并

[1, 2, 3].reduce(""){$0 + "\($1)"}// 转换为字符串并拼接
// "123"

15、 map 与 flatmap 的区别

1 map 可以对一个集合类型的所有元素做一个映射操作.
2 flatMap

  • 第一个作用和map一样,对一个集合类型的所有元素做一个映射操作,且可以过滤为nil的情况.

例如:

let array = [1,2,5,6,7,nil]
let array_map = array.map { $0 }
//[Optional(1), Optional(2), Optional(5), Optional(6), Optional(7), nil]
let array_flatmap = array_map.flatMap { $0 }
//[1, 2, 5, 6, 7]
  • 第二种情况可以进行“降维”操作
let array = [["1", "2"],["3", "4"]]
let array_map = array.map { $0 }
//[["1", "2"], ["3", "4"]]
let array_flatmap = array_map.flatMap { $0 }
//["1", "2", "3", "4"]

16、defer、guard的作用?

defer 语句块中的代码, 会在当前作用域结束前调用,无论函数是否会抛出错误。每当一个作用域结束就进行该作用域defer执行。 如果有多个 defer, 那么后加入的先执行.
guard :过滤器,拦截器
guard 和 if 类似, 不同的是, guard 总是有一个 else 语句, 如果表达式是假或者值绑定失败的时候, 会执行 else 语句, 且在 else 语句中一定要停止函数调用.

17、 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)
}

18、为什么数组索引越界会崩溃,而字典用下标取值时 key 没有对应值的话返回的是 nil 不会崩溃。

struct Array {
    subscript(index: Int) -> Element
}

struct Dictionary {
    subscript(key: Key) -> Value?
}

1 数组索引访问的是一段连续地址,越界访问也能访问到内存,但这段内存不一定可用,所以会引起Crash.
2 字典的key并没有对应确定的内存地址,所以是安全的.

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

使用 where 子句, 限制 Element 为 String

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

20、一个函数的参数类型只要是数字(Int、Float)都可以,要怎么表示。

Int、Float 都有一个协议

func myMethod(_ value: T) where T: Numeric {
    print(value + 1)
} 

或者 ExpressibleByIntegerLiteral 协议也行

21、一个类型表示选项,可以同时表示有几个选项选中(类似 UIViewAnimationOptions ),用什么类型表示?

需要实现自 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]

22、lazy

lazy懒加载,oc中实利用get方法实现,swift利用闭包实现.比如

private lazy var navLeftButton = { () -> UIButton in
        let btn = UIButton(type: .custom)
        btn.frame = CGRect(x: 0, y: 0, width: 50, height: 30)
        btn.setImage(UIImage(named:"back"), for: .normal)
        btn.addTarget(self, action: #selector(self.back), for: .touchUpInside)
        return btn
    }()

你可能感兴趣的:(Swift面试题)