swift跟OC对比
# 面向对象开发核心&& 向协议开发核心
面向对象开发核心是: 封装-继承-(多态),弊端:继承链很长,代码高度耦合 面向协议开发核心是: 模块化(组件化)
OC不能面向协议开发的原因是,OC中的协议只能有方法的声明,不能有方法的实现,而Swift可以通过协议拓展,来声明方法和实现方法,所以swift可以进行面向协议编程。
protocol Emitterable {
}
//协议拓展
extension Emitterable{
/// 方法的声明和实现
func start() {
}
}
2
3
4
5
6
7
8
9
10
面向协议开发应用:
很多UIView会通过xib进行描述, 而我们经常需要从一个xib中加载UIView, 抽取单独的协议, 需要从xib中加载类, 只需要遵守协议即可,而不需要继承自父类,再转为子类。
使用loadFromNib方法,从xib中加载UIView,
protocol NibLoadable {
}
extension NibLoadable where Self : UIView {
static func loadFromNib(_ nibname : String? = nil) -> Self {
let loadName = nibname == nil ? "\(self)" : nibname!
return Bundle.main.loadNibNamed(loadName, owner: nil, options: nil)?.first as! Self
}
}
2
3
4
5
6
7
8
9
10
11
12
一般会使用where Self对可遵守协议的类型进行限制。此处UIView要求的子类才可遵守该协议,并且可以直接使用UIView相关的属性和方法。
# Class 和 Struct 的区别
swift中struct与的class的区别
共同点:
都可以将多个数据封装为一个整体
不同点:
1.类可以添加方法,结构体不可以
2.结构体在栈区,类在堆区
3.结构体是值类型,类是引用类型
4.类可以继承
使用场景: 1.如果封装的不仅有数据还有方法,只能使用类,
2.不涉及到方法的前提下:如果属性较少,就定义为结构体,存放栈中,方便调用,如果属性较多,就定义为类。
# 理解Swift值类型的写时复制
只有当一个结构体发生了写入行为时才会有复制行为。
在结构体内部用一个引用类型来存储实际的数据,在不进行写入操作的普通传递过程中,都是将内部的reference的应用计数+1,在进行写入操作时,对内部的reference做一次copy操作用来存储新的数据,防止和之前的reference产生意外的数据共享。
swift中提供该[isKnownUniquelyReferenced]函数,他能检查一个类的实例是不是唯一的引用,如果是,我们就不需要对结构体实例进行复制,如果不是,说明对象被不同的结构体共享,这时对它进行更改就需要进行复制。
# Swift 与 Objective-C 的联系与区别?
Swift和Objective-C 共用一套运行时环境,Swift 的类型可以桥接到Objective-C(下面我简称OC),反之亦然。两者可以互相引用混合编程。 其次就是,OC 之前积累的很多类库,在 Swift 中大部分依然可以直接使用,当然,Swift3之后,一些语法改变了很多,不过还是有迹可循的。OC出现过的绝大多数概念,比如引用计数、ARC、属性、协议、接口、初始化、扩展类、命名参数、匿名函数等,在Swift中继续有效(可能最多换个术语)。Swift大多数概念与OC一样。当然Swift也多出了一些新兴概念,这些在OC中是没有的,比如范型、元组等。
# Swift中的常量和OC中的常量有啥区别?
OC中用 const 是用来表示常量的,而 Swift 中用 let 是用来判断是不是常量。
OC中的常量(const)是编译期决定的,Swift中的常量(let)是运行时确定的。
上面的区别更进一步说,OC中 const 表明的常量类型和数值是在 compilation time 时确定的;而 Swift 中 let 只是表明常量(只能赋值一次),其类型和值既可以是静态的,也可以是一个动态的计算方法,它们在 runtime 时确定的。
# Any和AnyObject的区别?
AnyObject只能表示引用类型的任何实例,相当于Objective-C中的id类型。
Any可以表示类,结构体,枚举的任何实例。
AnyObject是Any的子集。 (延伸 oc中 id的本质是struct objc_object结构体指针,可以指向任何OC对象,id指向基础数据类型会报编译错误。)
# Swift 中的属性观察器有哪些,它们各自有什么用途?
Swift中的属性观察器包括willSet和didSet,它们用于监控属性值的变化,从而可以在属性的值即将更改和已经更改时执行自定义操作。这两个观察器的用途如下:
1、 willSet观察器在属性的值即将更改之前被调用。它使你可以读取即将被设定的新值,并可以执行一些自定义代码。willSet观察器可以带有一个默认参数名newValue,如果你不指定参数名,可以直接使用newValue来访问新的属性值。
2、 didSet观察器在属性的值已经更改后立即被调用。它使你可以读取已经被更改的旧值,并可以基于新值执行一些自定义代码。didSet观察器可以带有一个默认参数名oldValue,如果你不指定参数名,可以直接使用oldValue来访问旧的属性值。
属性观察器提供了一种强大的方式来响应属性值的变化,使得开发者可以在适当的时机插入自己的逻辑或执行一些清理工作。
# 为什么数组越界会崩溃,而字典用下标取值时key没有对应值的话返回的是nil而不会崩溃?
数组的对象的储蓄地址是连续的,如果越界了,那取到的地址不一定可用,所以报错。
# SwiftUI用一种叫做属性包装器的东西来标记可以作为状态的变量。这些包括:
@State:用来管理普通的UI状态。
@Binding:实现视图与子视图之间双向数据绑定。
@ObservedObject:用于在视图之间共享数据。
在SwiftUI中,@ObservedObject属性包装器充当了多视图间共享对象引用的角色。这个功能在我们操作复杂的数据模型,且该模型被多个视图同时使用时显得尤为重要。一旦某个视图基于新数据更新了这个对象,所有通过@ObservedObject引用了这个对象的视图,都能同步获得更新。
那么,被@ObservedObject观察的对象能否是单例呢?答案是可以的。实际上,推荐的做法就是向不同的视图注入同一个实例,这样可以确保数据的一致性。@EnvironmentObject:用于在整的应用程序中跨视图共享数据。
# 介绍下combine
Combine框架由三个核心部分组成:
发布者(Publishers):发布者是一种逐步发布值的对象,可以视为数据的起始点,数据可能来源于用户输入、网络请求或定时器等多种途径。发布者能够发布各种类型的数据,如整数、字符串或自定义类型,并且可以根据需要发布任意数量的值,无论是有限还是无限。
操作符(Operators):操作符是用于处理、筛选或组合发布者值流的功能性工具。它们可以接收一个或多个发布者作为输入,并输出一个新的发布者,携带经过转换的值。常见的操作符包括map、filter、flatMap和zip等。
订阅者(Subscribers):订阅者是接收并处理发布者值的消费者。它们可以以多种方式处理接收到的值,比如打印到控制台、更新用户界面或存储到数据库。订阅者能够接收不同类型的值,可以请求一定数量的值,或者无限接收值。
# swift和OC的区别?
swift是一门支持多编程范式的语言,既支持面向对象编程,也支持面向协议编程,同时还支持函数式编程,OC面向对象编程。
swift注重值类型,OC注重引用类型。
swift的协议不仅可以被类实现,也可以被struct和enum实现
# String 与 NSString 的关系与区别?
1)本质区别:String是结构体,NSString是类,结构体是值类型,值类型被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝。
# Swift中的访问控制权限?
Open:实体可被同一模块内所有实体访问,模块外可导入该模块即可访问,模块外可被继承和重写。
Public:实体可被同一模块内所有实体访问,模块外可导入该模块即可访问,模块外不能被继承和重写。
Internal:实体可被同一模块内所有实体访问,模块外无法访问,大部分实体默认是Internal级别。
fileprivate:限制实体只能在当前文件内访问到,不管是否在本类的作用域。
private: 限制实体只能在本类的作用域且在当前文件内能访问。
# 什么时候使用 final?
final关键字可以在class、func和var前修饰,表示不能被继承或重写,否则编译器会报错, 可以将类或者类中的部分实现保护起来,从而避免子类破坏。
它可以显示的指派函数的派发机制。
# Swift的Copy On Write机制了解过吗?
Swift中参数传递是值类型传递,它会对值类型进行copy操作,当传递一个值类型变量时(变量赋值,函数传参),它传递的是一份新的copy值,两个变量指向不同的内存区域。如果频繁操作的变量占内存较大,会产生性能问题。
Copy On Write是一种优化值类型copy的机制,对String、Int、Float等非集合数据类型,赋值直接拷贝,对于Array等集合类型数据,只有传递的内容值改变后才进行拷贝操作。
Copy On Write的实现:set函数中判断是否存在多个引用,只有存在多个引用的情况下才会进行拷贝操作。另外,自定义结构体是不支持Copy On Write的。
# In-Out(inout关键字)参数了解过吗?
默认情况下,函数参数默认是常量,试图从函数体中去改变一个函数的参数值会报编译错误。如果希望函数修改参数值,并在函数调用结束后仍然保留。这个时候就需要用到inout关键字。
inout关键字修饰的变量传递过程:
函数被调用,参数值会被拷贝
在函数体中,修改的是拷贝的值
函数返回时,拷贝的值会赋值给原参数
注意事项:
inout关键字只能修饰变量,无法修饰常量,因为常量和字面量不能被修改。
inout参数不能有默认值,可变参数不能标记为inout。
调用函数的时候,应该在变量名前放置&符号表示该变量可以由函数修改。
var variable: Int = 1
func changeNumber(num:inout Int) {
num = 2
print(num)
}
changeNumber(num: &variable) // 2
2
3
4
5
6
7
8
9
10
11
12
13
# swift的派发机制 ((函数的派发机制:静态派发(直接派发)、函数表派发、消息派发))
swift中所有值类型:struct、enum使用直接派发。
swift中协议的extensions(类似于OC的分类)使用直接派发,初始声明函数使用函数表派发
swift中class中extensions使用直接派发,初始化声明函数使用函数表派发,dynamic修饰的函数使用消息派发。
swift中NSObject的子类用@nonobjc或final修饰的函数使用直接派发,初始声明函数使用函数表派发,dynamic修饰的extensions使用消息派发
# swift显示指定派发方式?
添加final关键字的函数使用直接派发
添加static关键字函数使用直接派发
添加dynamic关键字函数使用消息派发
添加@objc关键字的函数使用消息派发
添加@inline关键字的函数告诉编译器可以使用直接派发
# map、filter、reduce 的作用
map 用于映射, 可以将一个列表转换为另一个列表
[1, 2, 3].map{"\($0)"}// 数字数组转换为字符串数组
["1", "2", "3"]
2
3
4
filter 用于过滤, 可以筛选出想要的元素
[1, 2, 3].filter{$0 % 2 == 0} // 筛选偶数
// [2]
2
3
4
reduce 合并
[1, 2, 3].reduce(""){$0 + "\($1)"}// 转换为字符串并拼接
// "123"
2
3
4
# swift 闭包函数
- 闭包表达式种类
- 逃逸闭包
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。
一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
2
3
4
5
6
- 自动闭包
自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机
如果你想让一个自动闭包可以“逃逸”,则应该同时使用 @autoclosure 和 @escaping 属性。@escaping 属性的讲解见上面的 逃逸闭包。