iOS面试题-Swift
0.1 Swift数据类型,常量、变量、元组
定义值类型和引用类型
- 值类型 存储在栈区。 每个值类型变量都有其自己的数据副本,并且对一个变量的操作不会影响另一个变量。值类型包括:struct,enum,tuple。
- 引用类型 存储在其他位置(堆区),我们在内存中有一个指向该位置的引用。 引用类型的变量可以指向相同类型的数据。 因此,对一个变量进行的操作会影响另一变量所指向的数据。引用类型包括:class,closure,function。
和OC相比有什么区别
- OC 中仅有基本数据类型、基础 struct 是值类型。
- Swift 中的 struct、enum、tuple 都是值类型。(Array、Dictionary、String 都是struct)
Optional 是枚举类型,它有两个值:None 和 Some(T)。None 表示没有值,Some(T) 表示包含一个 T 类型的值。Optional 是值类型,它的本质是一个枚举类型,它的值是枚举类型的一个 case,所以它是值类型。
Optional 的本质
enum Optional<T> {
case none
case some(T)
}
- 强制解包:在变量名后面加一个感叹号,强制解包,如果可选类型为 nil,强制解包会导致运行时错误。
- 可选绑定:使用 if let 或者 guard let 语句进行可选绑定,判断可选类型是否包含值,如果包含就把值赋给一个临时的常量或者变量,用于后续语句中使用。
- 元组是一种数据结构,可以用于存储多个值,可以是不同类型的值。
- 元组中的值可以是任意类型,不要求是相同类型。
- 元组中的值可以通过名字或者数字索引来获取。
- 元组中的值可以是可选类型,也可以是非可选类型。
**字面量类型(Literal Type)**就是支持通过字面量进行实例初始化的数据类型,比如整型字面量、浮点型字面量、布尔型字面量、字符串字面量、数组字面量、字典字面量、空字面量等。
let a = 10
let b = "hello"
let c = [1, 2, 3]
以上代码中, 10、“hello”、[1, 2, 3] 就是字面量,用于表达源代码中一个固定值的表示法(notation)。
**字面量协议(Literal Protocol)**就是用来约束字面量类型的协议,Swift 包含一系列ExpressibleByLiteral协议,用于使用匹配的文字初始化自定义类型.
可以利用字面量协议来实现自定义类型的字面量初始化,比如自定义一个结构体,实现字面量协议,就可以使用字面量来初始化该结构体.
struct MyStruct: ExpressibleByIntegerLiteral {
var value: Int
init(integerLiteral value: Int) {
self.value = value
}
}
let a: MyStruct = 10
0.2 Swift流程控制
- if 语句:用于基于特定条件选择性执行代码。
- guard 语句:用于基于特定条件选择性执行代码,guard 语句总是有一个 else 分句,如果条件不满足则执行 else 分句中的代码。
- switch 语句:用于基于多个条件选择性执行代码。
- for-in 语句:用于重复执行一系列代码。
- while 语句:用于重复执行一系列代码直到条件不满足时停止执行。
- repeat-while 语句:用于重复执行一系列代码直到条件不满足时停止执行,和 while 语句的区别是 repeat-while 语句会先执行一次代码块。
- for in 循环遍历一个区间范围内的值,比如数字范围、字符串中的字符、数组中的元素等。
- for in 循环遍历一个集合中的值,比如数组、字典、集合等。
- for in 循环遍历一个序列中的值,比如数组、字典、集合等。
**区间类型(Range Type)**就是用来表示一个范围的类型,Swift 中的区间类型包括:闭区间、半开区间、单侧区间、单侧半开区间等.
// 闭区间
let closedRange = 0...5
// 半开区间
let halfOpenRange = 0..<5
// 单侧区间
let singleSideRange = 0...
// 单侧半开区间
let singleSideHalfOpenRange = ..<5
跨间隔的区间
// 利用stride, stride(from:to:by:) 从起始值到结束值,每次增加固定的值,直到结束值,但不包括结束值
let strideRange = stride(from: 0, to: 10, by: 2)
switch 与元组结合
let point = (1, 1)
switch point {
case (0, 0):
print("原点")
case (_, 0):
print("x轴")
case (0, _):
print("y轴")
case (-2...2, -2...2):
print("在矩形内")
default:
print("在矩形外")
}
元组与 where 结合
let point = (1, 1)
switch point {
case let (x, y) where x == y:
print("x == y")
case let (x, y) where x == -y:
print("x == -y")
case let (x, y):
print("(\(x), \(y))")
}
let score = 90
switch score {
case 0..<60:
print("不及格")
case 60..<80:
print("及格")
case 80..<90:
print("良好")
case 90...100:
print("优秀")
default:
print("成绩有误")
}
- guard..else 语句是用来检查条件是否成立,如果不成立就执行 else 分支中的代码,else 分支中的代码必须包含控制转移语句,比如 return、break、continue、throw 等,用来退出当前代码块。
- do..while 语句是用来重复执行一系列代码,直到条件不满足时停止执行,do..while 语句会先执行一次代码块,然后判断条件是否成立,如果成立就继续执行,否则就停止执行。
0.3 Swift结构体,类,枚举
枚举值原始值
枚举值原始值是指在定义枚举时指定的值,原始值可以是整型、浮点型、字符串型,原始值可以是隐式的,也可以是显式的。
enum Direction: Int {
case north = 1
case south = 2
case east = 3
case west = 4
}
枚举值附加值(关联值)
枚举值附加值是指在使用枚举值时指定的值,附加值可以是任意类型,附加值可以是隐式的,也可以是显式的。
enum Score {
case point(Int)
case grade(Character)
}
内存占用
枚举类型变量的内存占用可以解释为1个字节存储成员值(标明是哪个成员),加上占用字节数最大的关联值的字节数。比如上面原始值类型占用1字节, 关联值类型占用 1 + 16 = 9 字节, 由于内存对齐, 所以占用 24 字节.
没有区别:
struct Point {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
struct Point {
var x: Int
var y: Int
}
结构体不能继承,结构体是值类型,类是引用类型,结构体是通过复制来传递的,类是通过引用来传递的。
如果需要改变结构体中的属性,需要在方法前面加上 mutating 关键字,表示该方法可以修改结构体中的属性。
类的定义和结构体类似,但是编译器并没有为类自动生成可以传入成员值的初始化器。会生成一个无参的初始化器,
类中有初始值的话无参初始化器才会有用。
所有结构体都有一个编译器自动生成的初始化器。
- 结构体是值类型,类是引用类型。
- 结构体不能继承,类可以继承。
- 结构体在传递的时候是复制,类在传递的时候是引用。
0.4 Swift函数,闭包
- 实例方法是通过实例调用的方法,类型方法是通过类型调用的方法。
- 实例方法是可以访问实例属性和类型属性的,类型方法是可以访问类型属性和类型方法的。
闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的 lambdas 函数比较相似。
闭包表达式是一种构建内联闭包的方式,它的语法简洁。在保证不丢失语境的情况下,可以通过引用已经存在的变量或者函数来创建闭包表达式。
// 闭包表达式语法
{ (parameters) -> returnType in
statements
}
autoclosure 是一种自动创建闭包的技术,可以将一句表达式自动封装成一个闭包,然后将这个闭包作为参数传递给函数,这样就省去了写闭包的大量代码,提高了代码的简洁性。
func logIfTrue(_ predicate: () -> Bool) {
if predicate() {
print("True")
}
}
logIfTrue({ return 2 > 1 })
// 使用 autoclosure
func logIfTrue(_ predicate: @autoclosure () -> Bool) {
if predicate() {
print("True")
}
}
logIfTrue(2 > 1)
逃逸闭包是指在函数返回之后才执行的闭包,逃逸闭包需要在闭包前面加上 @escaping
关键字。
非逃逸闭包是指在函数返回之前就执行的闭包,非逃逸闭包需要在闭包前面加上 @noescape
关键字,Swift 3.0 之后默认就是非逃逸闭包,所以 @noescape
关键字已经被废弃了。
- 默认参数:在定义函数时可以给某个参数指定一个默认值,调用函数时如果没有传入该参数,则使用默认值。
- 可变参数:一个函数最多只能有一个可变参数,可变参数需要在参数类型后面加上
...
,可变参数在函数内部是一个数组。 - 输入输出参数:函数参数默认是常量,如果想要在函数中修改参数的值,并且想要修改函数外部传入的参数的值,可以将参数定义为输入输出参数,在参数类型前面加上
inout
关键字,输入输出参数不能有默认值,可变参数不能标记为输入输出参数。
0.5 Swift属性,单例
计算属性是通过某种方式计算得到的属性,计算属性可以用于类、结构体和枚举中,计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他的属性和值。
存储属性是存储在特定类或结构体的实例里的一个常量或变量,存储属性可以是变量存储属性,也可以是常量存储属性,存储属性只能用于类和结构体中。
只读计算属性只有 getter 没有 setter 的计算属性就是只读计算属性,只读计算属性只能用于类、结构体和枚举中。
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性,延迟存储属性必须声明成变量(使用 var 关键字),因为属性的初始值可能在实例构造完成之后才会得到,而常量属性在构造过程完成之前必须要有初始值,因此不能声明成延迟存储属性。
- willSet:在设置新的值之前调用。
- didSet:在新的值被设置之后立即调用。
- 实例属性是属于某个类、结构体或者枚举类型实例的属性,每次创建一个新的实例,实例都拥有一套属于自己的属性值,实例之间的属性相互独立。
- 类型属性是属于类型本身的属性,不属于某个实例,每个类型只有一份类型属性,不管创建多少个该类型的实例,这些实例都共享同一个类型属性。
class Singleton {
static let shared = Singleton()
private init() {}
}
存储类型属性是指在类型的作用域内定义的属性,用 static
关键字修饰的属性就是存储类型属性,存储类型属性可以是变量存储属性,也可以是常量存储属性,存储类型属性只能用于类和结构体中。
存储类型属性的特点
- 存储类型属性是延迟初始化的,只有在第一次被访问的时候才会初始化。
- 存储类型属性会被多个线程同时访问的时候,保证只会初始化一次,并且不需要使用 lazy 关键字。
- 存储类型属性:用
static
关键字修饰的属性就是存储类型属性,存储类型属性可以是变量存储属性,也可以是常量存储属性,存储类型属性只能用于类和结构体中。 - 计算类型属性:用
class
关键字修饰的属性就是计算类型属性,计算类型属性只能用于类中。
0.6 Swift泛型
- 泛型函数:在函数名后面使用尖括号定义泛型类型,泛型类型可以是多个,多个泛型类型之间用逗号分隔。
- 泛型类型:在类型名后面使用尖括号定义泛型类型,泛型类型可以是多个,多个泛型类型之间用逗号分隔。
- 泛型下标:在下标后面使用尖括号定义泛型类型,泛型类型可以是多个,多个泛型类型之间用逗号分隔。
关联类型是指在协议中定义的一个占位符名称,用于指定协议中的某个类型,关联类型通过 associatedtype 关键字来指定。
protocol Container {
associatedtype ItemType
mutating func append(_ item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
关联类型的作用
- 作为协议的一部分,为某个类型提供一个占位名(或者说别名),其实际类型在协议被实现时才会被指定。
- 关联类型可以在协议中被指定为类型的占位符,而不是一个具体的类型。
协议类型是指遵循了某个协议的类型,协议类型可以作为函数、方法或构造器中的参数类型或返回值类型,或者作为常量、变量或属性的类型,协议类型可以在类型后面加上 &
符号组合在一起。
protocol Runnable {
func run()
}
class Person: Runnable {
func run() {
print("Person run")
}
}
class Car: Runnable {
func run() {
print("Car run")
}
}
func get(_ type: Int) -> Runnable {
if type == 0 {
return Person()
} else {
return Car()
}
}
let runnable = get(0)
runnable.run()
- 类型约束:在定义泛型的时候,可以对泛型进行类型约束,只有满足约束的类型才能使用这个泛型。
- 关联类型约束:在协议中使用关联类型,可以给关联类型添加类型约束,只有满足约束的类型才能实现这个协议。
// 类型约束, T 必须是 Equatable 类型
func swapTwoValues<T: Equatable>(_ a: inout T, _ b: inout T) {
if a == b {
return
}
(a, b) = (b, a)
}
不透明类型是指在函数、方法或者构造器中,返回值的类型可以是协议类型,但是不能是泛型类型,也不能是关联类型,只能是具体的类型。
func get() -> some Runnable {
return Person()
}
0.7 swift 运算符
自定义运算符可以对已有的运算符进行重新定义,赋予它们更加特殊的功能,以适应自定义类型的需求,自定义运算符可以使用 operator
关键字在全局作用域内进行定义,也可以使用 infix
、prefix
、postfix
关键字在局部作用域内进行定义。
// 定义全局运算符
prefix operator +++
prefix func +++(value: Int) -> Int {
return value + 2
}
// 定义局部运算符
struct Point {
var x: Int
var y: Int
}
extension Point {
static prefix func -(point: Point) -> Point {
return Point(x: -point.x, y: -point.y)
}
}
let point = Point(x: 10, y: 20)
let newPoint = -point
print(newPoint)
0.8 swift 初始化器
- 指定初始化器是类中最主要的初始化器,一个类至少有一个指定初始化器,指定初始化器必须调用其直接父类的指定初始化器。
- 便捷初始化器是类中比较次要的初始化器,可以定义便捷初始化器来调用同一个类中的指定初始化器,并为其参数提供默认值,也可以定义便捷初始化器来调用同一个类中的便捷初始化器。
class Person {
var age: Int
init(age: Int) {
self.age = age
}
convenience init() {
self.init(age: 0)
}
}
class Student: Person {
var score: Int
init(age: Int, score: Int) {
self.score = score
super.init(age: age)
}
override convenience init(age: Int) {
self.init(age: age, score: 0)
}
}
- 重写父类指定初始化器:子类可以通过重写父类的指定初始化器来实现父类的初始化器,子类的指定初始化器必须调用父类的指定初始化器。
- 重写父类便捷初始化器:子类可以通过重写父类的便捷初始化器来实现父类的初始化器,子类的便捷初始化器必须调用同一个类中的指定初始化器或者便捷初始化器。
0.9 swift 内存管理
- ARC:ARC 是 Swift 中的内存管理方案,ARC 是 Automatic Reference Counting 的缩写,表示自动引用计数,ARC 会在类的实例不再被使用时自动释放其占用的内存。
- 引用类型:Swift 中的引用类型有 class、closure、function。
闭包循环引用是指闭包和闭包捕获的值相互引用,造成内存泄漏,解决闭包循环引用可以使用闭包捕获列表,闭包捕获列表会在闭包和捕获的值之间创建一个弱引用或无主引用,从而打破循环引用。
weak 弱引用
unowned 无主引用
lazy
关键字, 因为闭包在初始化器之前被调用, 而此时 self 还没有初始化完成.- UnsafePointer
:指向内存中某个类型的指针,不允许修改内存中的值。 - UnsafeMutablePointer
:指向内存中某个类型的指针,允许修改内存中的值。 - UnsafeRawPointer:指向内存中某个类型的指针,不允许修改内存中的值,不允许进行内存的访问。
- UnsafeMutableRawPointer:指向内存中某个类型的指针,允许修改内存中的值,不允许进行内存的访问。
0.10 Swift 扩展
如果分类中声明了一个属性,那么分类只会生成这个属性的set、get方法声明,也就是不会有实现
分类中的方法会覆盖原来类中的方法,但是不会覆盖原来类中的属性
扩展只能扩充计算属性,不能扩展存储属性,也不能扩展成员变量
扩展有时候也称为匿名分类,因为扩展是没有名字的
扩展不能添加指定构造器,但是可以添加便利构造器
扩展不能添加属性观察器
扩展不能添加父类
扩展也不能添加反初始化器
扩展可以给协议提供默认实现,也间接实现可选协议的效果
0.11 Swift继承
不同点
class 修饰的计算属性可以被重写,static 修饰的不能被重写。
static 可以修饰存储属性,static 修饰的存储属性称为静态变量(常量)。
static 修饰的静态方法不能被重写,class 修饰的类方法可以被重写。
class 修饰的类方法被重写时,可以使用static 让方法变为静态方法。
class 修饰的计算属性被重写时,可以使用static 让其变为静态属性,但它的子类就不能被重写了。
相同点
可以修饰方法,static 修饰的方法叫做静态方法,class 修饰的叫做类方法。
都可以修饰计算属性。
0.12 Swift模式匹配
模式匹配是指检查一个值是否满足某种模式的过程,Swift 中的模式匹配可以用在 switch 语句中,也可以用在 if、guard、for-in、while、do-while 语句中。
- 通配符模式:用下划线(_)表示,可以匹配任意类型的值,但是并不将匹配的值绑定到临时的常量或变量。
- 标识符模式:用于匹配任意值,如果匹配成功,会将匹配的值赋值给指定的标识符。
- 值绑定模式:用于将匹配的值绑定到一个临时的常量或变量,以便在模式的执行体中使用。
- 元组模式:用于匹配元组,如果匹配成功,会将元组中的值分解成单独的常量或变量,以便在模式的执行体中使用。
- 可选模式:用于匹配可选值,如果匹配成功,会将可选值中的值分解成单独的常量或变量,以便在模式的执行体中使用。
- 枚举模式:用于匹配枚举值,如果匹配成功,会将枚举值分解成单独的常量或变量,以便在模式的执行体中使用。
- 表达式模式:用于匹配表达式,如果匹配成功,会将表达式的值分解成单独的常量或变量,以便在模式的执行体中使用。
- 类型转换模式:用于匹配指定类型的值,如果匹配成功,会将值转换成指定类型,并赋值给指定的常量或变量。
- _:表示匹配任意类型的值。
- _?:非空任意值。
if case 语句是用来判断某个可选类型是否有值,如果有值,可以将值绑定到一个临时的常量或变量,以便在语句体中使用。
enum Score {
case point(Int)
case grade(Character)
}
let score = Score.point(100)
if case let Score.point(i) = score {
print(i)
}
0.13 Swift 协议
协议规定了用来实现某一特定功能所必需的方法和属性。
- 协议中可以定义方法、属性、下标、初始化器的声明,协议中的方法、属性、下标、初始化器不需要写访问级别修饰符,因为协议中声明的都是抽象的内容,具体实现的时候才需要指定访问级别修饰符。
- 协议中可以定义类型方法和类型属性,用 static 关键字来修饰。
- 协议中可以定义 mutating 方法,用 mutating 关键字来修饰。
- 协议中可以定义初始化器,但是不能定义析构器。
- 协议中可以定义下标,但是不能定义存储属性和计算属性。
- 协议中可以定义关联类型,关联类型通过 associatedtype 关键字来指定。
一般来讲是需要全部实现, 如果向要可选,可以使用extension
来实现. (或者利用OC的@optional
,但是需要加上 @objc 关键字)
protocol Runnable {
func run()
func run1()
}
extension Runnable {
func run1() {
print("run1")
}
}
0.14 蓝牙
- CBCentralManager:中心设备管理器,用于扫描、连接、发现服务和特征。
- CBPeripheralManager:外围设备管理器,用于广播、接收连接、提供服务和特征。
import CoreBluetooth
class ViewController: UIViewController, CBCentralManagerDelegate {
var centralManager: CBCentralManager!
override func viewDidLoad() {
super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
centralManager.scanForPeripherals(withServices: nil, options: nil)
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print(peripheral)
}
}
- 后台数据传输:在 Info.plist 文件中添加
Required background modes
键,值为App communicates using CoreBluetooth
,表示 App 需要使用 CoreBluetooth 进行后台数据传输。
- 多点连接:在 CoreBluetooth 中,一个 CBCentralManager 实例只能连接一个 CBPeripheral 实例,如果需要连接多个 CBPeripheral 实例,需要创建多个 CBCentralManager 实例。