IOS面试知识点-Swift
- Swift的优点:
- Swift和OC的相互调用?
- 类(Class)和结构体(struct有什么区别)?
- class和struct比较,优缺点?
- struct的优势
- Swift中什么可选型(Optional)
- Swift,什么是泛型?
- 访问关键字 open、public、internal、fileprivate、private
- 关键字: Strong、Weak、unowned 区别?
- 如何理解copy-on-write?
- 什么是属性观察?
- Swift为什么将String、Array、Dictionary设计成为值类型?
- 如何将Swift 中的协议(protocol)中的部分方法设计为可选(optional)?
- 比较Swift 和OC中初始化方法(init)有什么不同?
- 比较Swift和OC的protocl有什么不同?
- swift和OC 中的自省 有什么区别?
- 什么是函数重载? Swift支不支持函数重载?
- swift中枚举,关联值 和 原始值的区别
- Swift中的闭包结构是什么样子
- swift中储存属性和计算属性的区别?
- swift中什么类型属性(Type Propery)?
- swift中如何使用单利模式?
- swift中的下标是什么?
- 简单说明swift中的初始化器?
- 什么是运算符重载(Operator Overload)?
Swift是苹果在2014年6月WWDC发布的全新编程语言,借鉴了JS,Python,C#,Ruby等语言特性,看上去偏脚本化,Swift 仍支持 cocoa touch 框架
Swift的优点:
- 1.Swift更加安全,它是类型安全的语言。(有明确的类型声明)
- 2.Swift容易阅读,语法和文件结构简易化
- 3.Swift更易于维护,文件分离后结构更清晰。
- 4.Swift代码更少,简洁的语法,可以省去大量冗余代码
- 5.Swift速度更快,运算性能更高
Swift和OC的相互调用?
Swift调用OC的代码?
- 需要创建一个
Target-Bridging-Header.h
的桥接文件,在桥接文件中导入你需要调用OC类的头文件(.h)即可
OC调用swift代码
- 直接导入
Target-Swift.h
文件即可,Swift如果需要被OC调用,需要使用关键字@objc对方法或者属性进行修饰 或则使用**@objcMembers**来直接修饰整个类。
类(Class)和结构体(struct有什么区别)?
在Swift中,class是引用类型(指针类型),struct是值类型
值类型:
-
值类型在传递和赋值时将进行复制;赋值给var,let或则给函数传参,是直接将所有内容拷贝一份,类似于对文件进行copy,paste操作,产生了全新的文件副本。属于深拷贝(deep copy)即修改一个对象的值时,不影响另外一个对象
-
值类型: 比如结构体,枚举,是栈控件上的存储和操作的
引用类型:
-
引用类型只会使引用对象的一个”指向(地址值)“;赋值给var、let或则给函数传参,将是内存地址的拷贝一份,类似制作一个文件的替身(快捷方式,链接),指向同一个文件。属于浅拷贝(shallow copy)即当修改一个其中一个对象的值时,另外一个对象的值会随之改变。
-
引用类型:比如Class,是在堆空间上存储和操作的。
定义结构体struct
类型时其成员变量可以没有初始值(编译器会自动生成一个成员构造器,给变量赋一个默认值),如果使用这种格式定义一个类,编译器会报错,他会提醒你这个类没有被初始化
struct SRectangle {
var width = 200
var height: Int
}
class CRectangle {
var width = 200
var height: Int // 报错
}
class和struct比较,优缺点?
class有一下功能,struct是没有的:
- 1.class可以继承,子类可以使用父类的特性和方法。但是struct是不能被继承的
- 2.类型转换可以在运行时检查和解释一个实例对象
- 3.class可用 deinit来释放资源
- 4.一个类可以被多次引用
struct的优势
- 结构较小,适用于赋值操作,相比较一个class, 实例被多次引用(容易发生内存泄漏),struct更安全
- 无需担心内存泄漏问题(struct的操作都是在栈控件上,class在堆控件上)
Swift中什么可选型(Optional)
- 在Swift中,可选类型是为了表达一个变量为空的情况,当一个变量为空,他的值就是nil
- 在类型名称后面加个? 来定义一个可选类型
- 值类型或者引用类型都可以是可选型变量
var name: String? //nil
var age: Int? //nil
Swift,什么是泛型?
- 泛型只要是为了增加代码的灵活性而生的,它可以是对应的代码满足任意类型的变量或则方法;
- 泛型可以将类型参数化,提高了代码效率。减少代码量
//实现一个方法,可以交换任意类型
func swap<T>(a: inout T, b: inout T) -> Void {
(a, b) = (b , a)
}
访问关键字 open、public、internal、fileprivate、private
Swift 中有个5个级别的访问控制权限,从高到低依次是 open, public, internal, fileprivate, private
它们遵循的基本规则: 高级别的变量不允许被定义为低级别变量的成员变量,比如一个 private 的 class 内部允许包含 public的 String值,反之低级变量可以定义在高级别变量中;
- open: 具备最高访问权限,其修饰的类可以和方法,可以在任意 模块中被访问和重写.
- public: 权限仅次于 open,和 open 唯一的区别是: 不允许其他模块进行继承、重写
- internal: 默认权限, 只允许在当前的模块中访问,可以继承和重写,不允许在其他模块中访问
- fileprivate: 修饰的对象只允许在当前的文件中访问;
- private: 最低级别访问权限,只允许在定义的作用域内访问
关键字: Strong、Weak、unowned 区别?
- Swift的内存管理机制同OC一样,都是ARC管理机制。
- Strong:表示强引用,当一个对象声明为strong时,该对象的引用计数器加1.
- Weak: 代表弱引用, 当对象被声明为weak时,对象的引用计数器不会加1,它在对象释放后弱引用也随即消失。继续访问该对象,程序会得到nil,但是不会崩溃。
- unowned(无主引用),与弱引用本质上一样,唯一不同的是,在对象释放后,依然有一个无效的引用指向对象,它不是Optional也不指向nil,如果继续访问该对象,程序就会崩溃;也就是示例销毁后仍然储存着示例的内存地址(类似于OC中unsafe_unretained),试图在示例销毁后访问无主引用,会产生运行时错误(野指针)
weak 和 unowned 的引入是为了解决由strong带来的循环引用问题,简单来说,就是两个对象相互有一个强引用指向对放,这样导致两个对象在内存中无法释放
weak和unowned的使用场景;
- 当访问对象可能已经被释放了,则用weak,比如delegate的修饰
- 当访问对象确定不可能被释放,则用unowned。比如self的引用。
- 实际上为了安全起见,很多人大多时候都是用weak去修饰
如何理解copy-on-write?
值类型(比如:struct),在复制时,复制对象与原对象实际上在内存中指向同一个对象,当且仅当修改复制的对象时,才会在内存中创建一个新的对象
-
为了提升性能,Struct, String、Array、Dictionary、Set采取了Copy On Write的技术
-
比如仅当有“写”操作时,才会真正执行拷贝操作
-
对于标准库值类型的赋值操作,Swift 能确保最佳性能,所有没必要为了保证最佳性能来避免赋值
什么是属性观察?
属性观察是指当前类型内对特性属性进行监测,并做出响应,属性观察是swift中的特性。机油两种 willSet 和 didSet
-
willSet会传递新的值, 默认叫newValue
-
didSet会传递旧值, 默认叫oldValue
-
在初始化中设置属性不会触发willSet和didSet
var testStr: String = "test" {
willSet {
print("willSet===\(newValue)")
}
didSet {
print("didSet===\(oldValue)")
}
}
//调用
testStr = "test2"
//打印结果
//willSet===test2
//didSet===test
Swift为什么将String、Array、Dictionary设计成为值类型?
值类型和引用类型相比,最大优势可以高效的使用内存,值类型在栈上操作,引用类型在堆上操作,栈上操作仅仅是单个指针的移动,而堆上操作牵涉到合并,位移,重链接,Swift 这样设计减少了堆上内存分配和回收次数,使用 copy-on-write
将值传递与复制开销降到最低
如何将Swift 中的协议(protocol)中的部分方法设计为可选(optional)?
- 在协议前面加上@objc,然后在方法前面加上@objc optional 关键字,改变方式实际上是将协议转变成了OC模式
注意:
1.遵守这个协议的类,是继承NSObject或者不继承都没关系,但是如果你的协议是继承于NSObjectProtocol,那么类必须继承NSObject!
2. weak 关键字只能修饰Class,然后Protocol不管是类还是结构体(struct )还是枚举(enum)都可以遵守, 而weak修饰导致必须继承NSObjectProtocol协议
@objc protocol someProtocol {
@objc optional func test1() -> Void
}
protocol someProtocol2 {
func test1() -> Void
}
struct protocolTest: someProtocol2 {
//weak var delegate: someProtocol2? //报错
func test1() {
print("======")
}
}
- 使用扩展(extension),规定可选方法,在swift中,协议扩展可定义部分方法的默认实现
protocol someProtocol2 {
func test1() -> Void
func test2() -> Void
}
extension someProtocol2 {
func test2() -> Void {
print("swift通过扩展来实现协议可选方法")
}
}
struct protocolTest: someProtocol2 {
//weak var delegate: someProtocol2? //报错
func test1() {
print("======")
}
}
比较Swift 和OC中初始化方法(init)有什么不同?
swift 的初始化方法,更加严格和准确, swift初始化方法需要保证所有的非optional的成员变量都完成初始化, 同时 swfit 新增了convenience和 required两个修饰初始化器的关键字
- convenience只提供一种方便的初始化器,必须通过一个指定初始化器来完成初始化
- required是强制子类重写父类中所修饰的初始化方法
比较Swift和OC的protocl有什么不同?
-
Swift和OC中的 protocol相同点在与: 两者都可以被用作代理;
-
不同点: Swift中的protocol还可以对接口进行抽象,可以实现面向协议,从而大大提高了编程效率,Swift中的protocol可以用于值类型,结构体,枚举。
swift和OC 中的自省 有什么区别?
自省在OC中就是判断某一对象是否属于某一个类的操作,有以下下两种方式
- (BOOL)isKindOfClass:(Class)aClass;//用于判断某个类或子类的实例
- (BOOL)isMemberOfClass:(Class)aClass;//用于判断某个类的实例
-
在 Swift 中提供了一个更简洁的写法:对于一个不确定的类型,我们可以使用is来进行判断。is在功能上相当于原来的isKindOfClass,可以检查一个对象是否属于某类型或其子类型。
-
在Swift中由于很多 class 并非继承自 NSObject, 故而 Swift 使用 is 来判断是否属于某一类型, is 不仅可以作用于class, 还是作用于enum和struct
class ClassA { }
class ClassB: ClassA { }
let obj: AnyObject = ClassB()
if (obj is ClassA) {
print("obj 属于 ClassA")
}
if (obj is ClassB) {
print("obj 属于 ClassB")
}
什么是函数重载? Swift支不支持函数重载?
-
函数重载是指:函数名称相同,函数的参数个数不同,或则参数的类型不同,或参数标签不同,返回值类型与函数重载无关
-
swift支持函数重载
swift中枚举,关联值 和 原始值的区别
- 关联值–有时会将枚举的成员值跟其他类型的变量关联储存在一起,会非常有用
// 关联值
enum Date {
case digit(year: Int, month: Int, day: Int)
case string(String)
}
- 原始值–枚举成员可以使用相同类型的默认值预先关联,这个默认值叫做:原始值
// 原始值
enum Grade: String {
case perfect = "A"
case great = "B"
case good = "C"
case bad = "D"
}
- 枚举成员可以设置原始值,这些原始值的类型必须相同
- 原始值的隐私赋值:
- 当使用整数作为枚举成员的原始值时,隐式赋值的值依次递增1;
- 当使用字符串作为枚举类型的原始值时,每个枚举成员的隐式原始值为该枚举成员的名称;
- 使用枚举成员的rawValue属性可以访问该枚举成员的原始值
Swift中的闭包结构是什么样子
{
(参数列表) -> 返回值类型 in 函数体代码
}
- 什么是尾随闭包?
- 将一个很长的必报表达式作为函数最后一个实参
- 使用尾随闭包可以增强函数的可读性
- 尾随闭包是一个被书写在函数调用括号(后面)的闭包表达式
func exec(v1: Int, v2: Int, v3: (_ age: Int, _ age2: Int) -> Int) -> Void {
print(v3(v1, v2))
}
//调用
exec(v1: 10, v2: 20) { (age, age2) -> Int in
return age + age2
}
- 什么是逃逸闭包?
当闭包作为一个实际参数传递给一个函数或者变量的时候,我们就说这个闭包逃逸了,可以在形式参数前写@escaping
关键字来明确闭包是允许逃逸的。
- 非逃逸闭包,逃逸闭包,一般都是当做参数传递给函数
- 非逃逸闭包: 闭包调用发生在函数结束前,闭包调用在函数作用域内
- 逃逸闭包: 闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过
@escaping
声明
// 定义一个数组用于存储闭包类型
var completionHandlers: [() -> Void] = []
// 在方法中将闭包当做实际参数,存储到外部变量中
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
如果你不标记函数的形式参数为@escaping,你会遇到编译错误
- 什么是自动闭包?
自动闭包是一种自动创建的用来把作为实际参数传递给函数的表达式打包的闭包。它不接受任何实际参数,并且当它被调用时,它会返回内部打包的表达式的值。这个语法的好处在于通过写普通表达式代替显式闭包而使你省略包围函数形式参数的括号
//定义一个自动闭包作为函数的参数
func getMax(_ v1: Int, _ v3: @autoclosure () -> Int) -> Int {
return v1 > 0 ? v1 : v3()
}
getMax(10, 20)
- 为了避免与期望冲突,使用了
@autoclosure
的地方最好明确注释清楚:这个值会被推迟执行 @autoclosure
会自动将 20 封装成闭包 { 20 }@autoclosure
只支持 () -> T 格式的参数@autoclosure
并非只支持最后1个参数- 有
@autoclosure
、无@autoclosure
,构成了函数重载 - 如果你想要自动闭包允许逃逸,就同时使用
@autoclosure
和@escaping
标志。
swift中储存属性和计算属性的区别?
swift中跟实例对象相关的属性可以分为两大类
储存属性(Stored Propery)
- 类似成员变量这个概念
- 储存在实例对象的内存中
- 结构体,类可以定义储存属性
- 枚举不可以定义储存属性
计算属性(Computed Property)
- 本质就是方法(函数)
- 不占用实例对象的内存
- 枚举、结构体、类都可以定义计算属性
struct Circle {
// 存储属性
var radius: Double
// 计算属性
var diameter: Double {
set {
radius = newValue / 2
}
get {
return radius * 2
}
}
}
什么是延迟储存属性(Lazy Stored Property)?
使用lazy可以定义一个延迟存储属性,在第一次用到属性的时候才会进行初始化(类似OC中的懒加载)
- lazy属性必须是var,不能是let; let必须在实例对象的初始化方法完成之前就拥有值
- 如果多条线程同时第一次访问lazy属性,无法保证属性只被初始化一次
class PhotoView {
// 延迟存储属性
lazy var image: Image = {
let url = "https://...x.png"
let data = Data(url: url)
return Image(data: data)
}()
}
什么是属性观察器?
可以为非lazy的var存储属性设置属性观察器,通过关键字willSet和didSet来监听属性变化
struct Circle {
var radius: Double {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, radius)
}
}
init() {
self.radius = 1.0
print("Circle init!")
}
}
swift中什么类型属性(Type Propery)?
-
严格来说,属性可以分为:
-
实例属性:只能通过实例对象去访问
- 储存属性:储存在示例对象的内存中,每个实例对象都有一份
- 计算实例属性
-
类属性:只能通过类型去访问
- 储存类型属性: 整个运行过成中,就只有一份内存(类似于全局变量)
- 计算类型属性
-
可以通过
static
定义类型属性p如果是类,也可以用关键字class
-
struct Car {
static var count: Int = 0
init() {
Car.count += 1
}
}
-
不同于储存实例属性,你必须给储存属性设定初始值
- 因为类型没有像实例对象那样的init初始化器来初始化存储属性
-
存储类型属性默认就是lazy,会在第一次使用的时候才初始化
- 就算被多个线程同时访问,保证只会初始化一次
- 存储类型属性可以是let
-
枚举类型也可以定义类型属性(存储类型属性、计算类型属性)
swift中如何使用单利模式?
可以通过类型属性 + let + private来写单利: 代码如下:
public class FileManager {
public static let shared = {
// ....
// ....
return FileManager()
}()
// 把init初始化方法私有化
private init() { }
}
swift中的下标是什么?
- 使用subscript可以给任意类型(枚举、结构体、类)增加下标功能,有些地方也翻译为:下标脚本
- subscript的语法类似于实例方法、计算属性,本质就是方法(函数)
class TestIndex: NSObject {
var x = 0.0
var y = 0.0
//增加下标功能
subscript(index: Int) -> Double {
set {
if index == 0 {
x = newValue
} else {
y = newValue
}
}
get {
if index == 0 {
return x
} else {
return y
}
}
}
}
//测试代码
let test = TestIndex()
test[0] = 11.1
test[1] = 22.2
//下标访问
print(test.x)
print(test.y)
- subscript的返回值决定了:
- get方法的返回值类型
- set方法中newValue的类型
- subscript可以接受多个参数,并且是任意类型;
- subscript可以没有set方法,单是必须有get方法;
- 如果只有get方法,可以省略
struct Person {
var age = 30
subscript(index: Int) -> Int {
if index == 0 {
return age
} else {
return age * 2
}
}
}
var p = Person()
print(p[0]) // 30
print(p[1]) // 60
- subscript可以设置参数标签;也可以是类型方法
/**
可以设置参数标签 也可以是类型方法
*/
struct StructTestIndexDemo {
static subscript(index i: Int) -> Int {
if i == 0 {
return 30
} else {
return 60
}
}
}
//测试方法
print(StructTestIndexDemo[index: 0])//30
print(StructTestIndexDemo[index: 1])//60
结构体和类作为返回值的时候,作为只读(只有get方法)返回值时,结构体 是不可以修改的,因为结构体的内存是不可变的(值类型)
- subscript也可以接受多个参数的下标
class Matix {
var data = [
[1,0,0],
[0,1,0],
[0,0,1]
]
subscript(row: Int , col: Int) -> Int {
set {
guard row >= 0 && row < 3 && col >= 0 && col < 3 else {
return
}
data[row][col] = newValue
}
get {
guard row >= 0 && row < 3 && col >= 0 && col < 3 else {
return 0
}
return data[row][col]
}
}
}
var m = Matix()
m[1, 1] = 3
m[0, 1] = 4
简单说明swift中的初始化器?
- 类、结构体、枚举都可以定义初始化器
- 类有2种初始化器: 指定初始化器(designated initializer)、便捷初始化器(convenience initializer)
// 指定初始化器
init(parameters) {
statements
}
// 便捷初始化器
convenience init(parameters) {
statements
}
规则:
* 每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器
* 默认初始化器总是类的指定初始化器
* 类偏向于少量指定初始化器,一个类通常只有一个指定初始化器
-
初始化器的相互调用规则
- 指定初始化器必须从它的直系父类调用指定初始化器
- 便捷初始化器必须从相同的类里调用另一个初始化器
- 便捷初始化器最终必须调用一个指定初始化器
-
什么可选链?
可选链是一个调用和查询可选属性、方法和下标的过程,它可能为 nil 。如果可选项包含值,属性、方法或者下标的调用成功;如果可选项是 nil ,属性、方法或者下标的调用会返回 nil 。多个查询可以链接在一起,如果链中任何一个节点是 nil ,那么整个链就会得体地失败。- 多个?可以链接在一起
- 如果链中任何一个节点是nil,那么整个链就会调用失败
什么是运算符重载(Operator Overload)?
类、结构体、枚举可以为现有的运算符提供自定义的实现,这个操作叫做:运算符重载
struct Point {
var x: Int
var y: Int
// 重载运算符
static func + (p1: Point, p2: Point) -> Point {
return Point(x: p1.x + p2.x, y: p1.y + p2.y)
}
}
var p1 = Point(x: 10, y: 10)
var p2 = Point(x: 20, y: 20)
var p3 = p1 + p2//(30,30)