Swift考察总结
Swift
一、Swift中struct和class有什么区别?
1.struct是值引用、更轻量,存放于栈区,class是类型引用,存放于堆区。struct无法继承,class可以继承。
对于值类型都有他们自己的数据副本,因此对一个变量操作不可能影响另一个变量。值类型包括结构体(数组和字典),枚举,基本数据类型(boolean, integer, float等)
二者的本质区别:struct是深拷贝;class是浅拷贝。
2.property的初始化不同:
class在初始化的时候不能直接把property放在默认的constructor的参数里,而是需要自己创建一个带参数的constructor;而struct可以把属性放在默认的constructor的参数里。
3.变量赋值方式不同。
struct是值拷贝;
class是引用拷贝
4.struct 和 class 的差別是 struct 的 function 要去改变 property 的值的时候要加上 mutating,而 class 不用
image.png5.继承
struct不可以继承,class可以继承。
6.struct比class更轻量:
struct分配在栈中,class分配在堆中。
二、Swift中的方法调用有哪些形式?
直接派发、函数表派发、消息机制派发。派发方式受声明位置,引用类型,特定行为的影响。为什么Swift有这么多派发形式?为了效率
直接派发 (Direct Dispatch)、函数表派发 (Table Dispatch )、消息机制派发 (Message Dispatch )都有
总结起来有这么几点:
- 值类型总是会使用直接派发, 简单易懂
- 而协议和类的 extension 都会使用直接派发
- NSObject 的 extension 会使用消息机制进行派发
- NSObject 声明作用域里的函数都会使用函数表进行派发.
- 协议里声明的, 并且带有默认实现的函数会使用函数表进行派发
指定派发方式
final
final 允许类里面的函数使用直接派发. 这个修饰符会让函数失去动态性. 任何函数都可以使用这个修饰符, 就算是 extension 里本来就是直接派发的函数. 这也会让 Objective-C 的运行时获取不到这个函数, 不会生成相应的 selector.
dynamic
dynamic 可以让类里面的函数使用消息机制派发. 使用 dynamic, 必须导入 Foundation 框架, 里面包括了 NSObject 和 Objective-C 的运行时. dynamic 可以让声明在 extension 里面的函数能够被 override. dynamic 可以用在所有 NSObject 的子类和 Swift 的原声类.
参考文章:深入理解 Swift 派发机制
三、Swift和OC的区别?
Swift和OC的区别有很多,这里总结这几条:
Swift | Objective-C | |
---|---|---|
语法特性 | 静态语言,更加安全 | 动态语言,不那么安全 |
语法 | 更精简 | 冗长 |
命名空间 | 有 | 无 |
方法调用 | 直接调用,函数表调用,消息转发 | 消息转发 |
泛型/元组/高阶函数 | 有 | 无 |
语言效率 | 性能更高,速度更快 | 略低 |
文件特性 | .swift 单文件 | .h/.m包含头文件 |
编程特性 | 可以更好的实现函数式编程/响应式编程 | 面向对象编程 |
四、从OC向Swift迁移的时候遇到的问题?
可以参考这篇文章:OC项目转Swift指南 里的混编注意事项。
五、怎么理解面向协议编程?
面向对象是以对象的视角观察整体结构,万物皆为对象。
面向协议则是用协议的方式组织各个类的关系,Swift底层几乎所有类都构建在协议之上。
面向协议能够解决面向对象的菱形继承,横切关注点和动态派发的安全性等问题。
六、 swift中 closure 与OC中block的区别?
- closure是匿名函数、block是一个结构体对象
- closure通过逃逸闭包来在内部修改变量,block 通过 __block 修饰符
七、@objc
在Swift代码中,使用@objc修饰后的类型,可以直接供Objective-C调用。
八、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()
九、associatedtype 的作用
关联类型:为协议中的某个类型提供了一个别名,其代表的真实类型在实现者中定义
//协议,使用关联类型
protocol TableViewCell {
associatedtype T
func updateCell(_ data: T)
}
//遵守TableViewCell
class MyTableViewCell: UITableViewCell, TableViewCell {
typealias T = Model
func updateCell(_ data: Model) {
// do something ...
}
}
十、泛型
1.类中泛型
实现一个栈,栈里边的元素可以是任何类型,但是所有元素又必须是同一种类型,使用泛型实现的代码就是这样的
//类作用域
class YJKStack<T>: NSObject {
//栈空间
private var list:[T] = []
//进栈
public func push(item:T){
list.append(item)
}
//出栈
public func pop() -> T{
return list.removeLast()
}
}
2.泛型类型约束
实际运用中,我们的参数虽然可以不是特定的类,但是通常需要这个参数要实现某个协议或者是某个类的子类。这时候就要给泛型添加约束了,代码就是下面这一堆喽
class YJKProtocolStack<T: A&B> 须实现多个协议的话,用 & 符号链接就好啦。
class YJKProtocolStack<T: A>: NSObject {
//栈空间
private var list:[T] = []
//进栈
public func push(item:T){
list.append(item)
}
//出栈
public func pop() -> T{
return list.removeLast()
}
}
protocol A {}
protocol B {}
十一、权限修饰符
open :修饰的属性或者方法在其他作用域既可以被访问也可以被继承或重载 override。
public :修饰的属性或者方法可以在其他作用域被访问,但不能在重载 override 中被访问,也不能在继承方法中的 Extension 中被访问。
internal:被修饰的属性和方法只能在模块内部可以访问,超出模块内部就不可被访问了。(默认)
fileprivate :其修饰的属性或者方法只能在当前的 Swift 源文件里可以访问。
private :只允许在当前类中调用,不包括 Extension ,用 private 修饰的方法不可以被代码域之外的地方访问。
从高到低排序如下:
open > public > interal > fileprivate > private
十二、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"
十三、defer、guard的作用?
guard :过滤器,拦截器
guard 和 if 类似, 不同的是, guard 总是有一个 else 语句, 如果表达式是假或者值绑定失败的时候, 会执行 else 语句, 且在 else 语句中一定要停止函数调用.
defer 语句块中的代码, 会在当前作用域结束前调用,无论函数是否会抛出错误。每当一个作用域结束就进行该作用域defer执行。 如果有多个 defer, 那么后加入的先执行.(延迟执行的意思)
十四、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)
}
十五、为什么数组索引越界会崩溃,而字典用下标取值时 key 没有对应值的话返回的是 nil 不会崩溃。
- 1 数组索引访问的是一段连续地址,越界访问也能访问到内存,但这段内存不一定可用,所以会引起Crash.
- 2 字典的key并没有对应确定的内存地址,所以是安全的.
十六、swift把struct作为数据模型的优缺点?
优点:
- 1.安全性:struct是值类型传递的,它们没有引用计数
- 内存:由于它们没有引用数,它们不会因为循环引用导致内存泄漏
- 速度:值类型通常来说是以栈的形式分配的,而不是用堆。因此它们比Class要快很多。
- 拷贝:OC里拷贝一个对象,你必须选用正确的拷贝类型(深拷贝、浅拷贝),而值类型的拷贝则非常轻松!
- 线程安全:值类型是自动线程安全的。无论你从哪个线程去访问你的struct,都非常简单。
缺点:
- OC与swift混合开发:OC调用的swift代码必须继承NSObject。
- 继承:struct不能相互继承
- NSUserDefatults: struct不能被序列化成NSData对象