写这个文章最开始的缘由是我同窗问我你学了几个月 Swift,感受怎么样,和 OC 对比有什么特别之处,而后我说我也说很差。。本身从开始学 Swift 到如今也有很多几个月,不少特性或者说对比 OC 比较有特别的东西也没搞清楚,怎么好意思误人子弟,因此就本身四处谷歌百度写这个东西就当本身学习累积用。git
仍是发现本身太懒了,从开始写到写完断断续续用了半个多月,有时候遇到一个不会的点光查资料就查了半天,效率也比较低。公司 iOS 组里了解和使用过 Swift 的也很少,日常能请教的也只有小组长。至于写完估计也不会用来分享到组内,反正你们都不熟悉,也懒得浪费他们的时间了。github
在说其余的以前我想先扯下无聊的强类型语言/弱类型语言,动态语言/静态语言。express
动态语言/静态语言:能/不能 在运行时能够改变其结构的语言。编程
强/弱类型语言:这两个术语并无很是明确的定义,但主要用以描述编程语言对于混入不一样数据类型的值进行运算时的处理方式。强类型的语言遇到函数引数类型和实际调用类型不匹配的状况常常会直接出错或者编译失败;而弱类型的语言经常会实行隐式转换,或者产生难以意料的结果。swift
(呃呃呃,至于有些文章说的动态类型/静态类型语言,找了下维基百科并无单独拎出来定义,除了部分语言中提到过这个,意义偏向于逐渐模糊的强弱类型)。api
上述分类中,Swift 是静态强类型语言,OC 是动态强类型语言。Swift 语言有一个很重要的点是类型安全和推断,先看看下面一段代码:安全
let upperCases = ["DD", "PPP", "122", "21"]
let lowerCases = upperCases.map { $0.lowercased() }
if lowerCases.contains("dd") {
print("contain target")
} // 这种写法没问题
if upperCases.map { $0.lowercased() }.contains("dd") {
print("contain target")
} // err: Anonymous closure argument not contained in a closure
// 如下三种是加了个(),均不报错
if (upperCases.map { $0.lowercased() }).contains("dd") {
print("contain target")
}
if upperCases.map({ $0.lowercased() }).contains("dd") {
print("contain target")
}
if (upperCases.map{ $0.lowercased() }.contains("dd")) {
print("contain target")
}
复制代码
这个类型推断我也是懵逼,没明白编译器把这个表达式推断成什么样的错误状况,只能把它理解为编译器解释的不够完善。bash
元组类型是用括号包围,由一个逗号分隔的零个或多个类型的列表(只有一个值时会被推断为圆括号操做符,而非元组),这些值能够不加标签,直接用下标访问。若是为了意义明确,也能够为它加个标签,访问时就能够直接用标签访问。网络
元组和闭包、元类型等都是非正式类型,非正式类型时相对于结构体等正式类型而言,没有具体的类型名,虽然元组看起来比较像匿名结构体。闭包
let t: (String, Int, Double) = ("dd", 33, 33)
print(t.0, t.1, t.2)
let color: (r: Int, g: Int, b: Int, a: CGFloat) = (1, 2, 3, 0.1)
print(color.r, color.g, color.b, color.a)
复制代码
由于元组是由多个值以很是简单的方式构成,因此分解起来也比较简单,这时候就建立了三个临时变量:
let t: (String, Int, Double) = ("dd", 33, 33)
let (a, b, c) = t
print(a, b, c)
复制代码
经过上面的合成分解,让 Swift 在交换两个变量时会更加简单:
var j = 1, k = 3
(j, k) = (k, j)
print(j, k) // j = 3, k = 1
复制代码
其实就是至关于先把 j 和 k 的值存到元组对象里,而后再从元组对象中取值
元组的对比须要参数类型一致而且遵循 Equatable
, 才能够用 ==
,遵循 Comparable
, 能够用 >
、<
等。须要注意的是对比大小时是按顺序来,得出对比结果。
空元祖就是 (),里面没有元素,也就是你们更熟悉的 Void
。关于 Void
里面一些诡异的设定,Matt
大神写过 一篇文章,SwiftGG
翻译过 这篇文章,有兴趣的能够看看
这两个用法给出的废弃缘由是和 Swift 的简单易读的风格不搭,大概是 Swift 2.2 仍是 3.0 的更新改的,文档连接已经记不清了,Google 下就能出来。
其实元组把每一个参数加上标签后和函数的参数部分的结构基本一致,只是函数能够加别名,若是使用 _
来忽略参数标签名,那就和元组没加标签同样
func sum(x: Int, y: Int) -> Int {
return x + y
}
let params = (1,1)
sum(params)
// 初始化结构体
struct User {
var name: String
var age: Int
}
let t = (name: "dd", age: 12)
let user = User(t)
复制代码
类型是枚举:public enum Optional<Wrapped> : ExpressibleByNilLiteral
。定义很简单,可能有值也可能没有,官方文档上有更形象的说明,我也懒得照搬了。
咱们开发的时候大可能是用具体的值,这时候就须要把 optional
解析成具体值或者空,也就是解包,通常有三种方法: if/gurad let
、??
、!
。采用 !
强制解包时若是失败会引发 Crash。
let a: String? = "dd"
if let b = a {
print(type(of: a), type(of: b)) // Optional<String>, String
}
let c = a ?? "default"
let d: String?
print(d!) // 运行时错误 Crash
复制代码
这个见得比较少,形式像这种:String??
。为了更加形象的理解这个多重可选值,这里 Optional
能够把比成一个里面可能有东西的黑盒子,而 String??
能够理解成一个盒子 A,盒子里可能为空,也多是另外一个盒子 B,而盒子 B 里面多是空,也多是一个 String
(可选链不会增长可选层数)。理解了这两句话再看看下面这段代码:
var aNil:String? = nil
var anotherNil: String?? = aNil
var literaNil: String?? = nil
print(anotherNil, literaNil) // Optional(nil) nil
复制代码
根据上面的盒子例子能够理解,anotherNil
为盒子 A, 盒子 A 里有个盒子 B 即 aNil
是空,因此这个盒子。对于 literaNil
这个盒子里就是空,没有其余盒子。
至于用法,我也没怎么见过,只在 optional
的 map
函数中见过。
public enum Optional<Wrapped> : ExpressibleByNilLiteral {
public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
}
let s: String? = "1223"
let optionalMapped = s.map { Int($0) } // Optional(Optional(1223))
let optionalFlatMapped = s.flatMap { Int($0) } // Optional(1223)
复制代码
当返回值 U
是非 Optional
类型时,两者没有区别,返回的都是 Optional(1223)
;当 U
是 Optional
类型, map
函数返回的则是 Optional(Optional(Value))
。
这个黑盒子的存在,致使咱们使用时须要进行各类判断,当逻辑比较复杂的时候,代码会看起来比较啰嗦,这篇文章 列举了不少实用的例子(虽然里面有些例子我以为直接用系统的解包更好),这里从里面挑个感受比较好玩的
extension Optional {
/// 当可选值不为空时,解包并返回参数 `optional`
func and<B>(_ optional: B?) -> B? {
guard self != nil else { return nil }
return optional
}
/// 解包可选值,当可选值不为空时,执行 `then` 闭包,并返回执行结果
/// 容许你将多个可选项链接在一块儿
func and<T>(then: (Wrapped) throws -> T?) rethrows -> T? {
guard let unwrapped = self else { return nil }
return try then(unwrapped)
}
/// 将当前可选值与其余可选值组合在一块儿
/// 当且仅当两个可选值都不为空时组合成功,不然返回空
func zip2<A>(with other: Optional<A>) -> (Wrapped, A)? {
guard let first = self, let second = other else { return nil }
return (first, second)
}
/// 将当前可选值与其余可选值组合在一块儿
/// 当且仅当三个可选值都不为空时组合成功,不然返回空
func zip3<A, B>(with other: Optional<A>, another: Optional<B>) -> (Wrapped, A, B)? {
guard let first = self,
let second = other,
let third = another else { return nil }
return (first, second, third)
}
}
// 使用前
if user != nil, let account = userAccount() ...
// 使用后
if let account = user.and(userAccount()) ...
// 正常示例
func buildProduct() -> Product? {
if let var1 = machine1.makeSomething(),
let var2 = machine2.makeAnotherThing(),
let var3 = machine3.createThing() {
return finalMachine.produce(var1, var2, var3)
} else {
return nil
}
}
// 使用扩展
func buildProduct() -> Product? {
return machine1.makeSomething()
.zip3(machine2.makeAnotherThing(), machine3.createThing())
.map { finalMachine.produce($0.1, $0.2, $0.3) }
}
复制代码
这是个中置运算符,因为运算符不能归属给某个类型的特性就放在了 optional
外面(虽然除了它也没什么东西用这个)。这个运算符使用了自动闭包(一个没参数的闭包,调用时会直接返回值)的特性。
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?
复制代码
这两个的区别就是 defaultValue
也就是 ??
后面的闭包返回值类型,其实也就是说当 optional
为空时,defaultValue
是什么类型,??
就返回值什么类型,固然这个 defaultValue
只能是 T
或者 T?
。因此右边的值是非 optional
才能解包成功。
枚举是 Swift 的核心对象,工程中基本都会用到它,上面那个 Optional
就是枚举 在 OC 中用枚举有一个不爽的就是只能表示 Int
类型,有时须要把它和 String
相互转换,而 Swift 中的枚举就强大的多:
CGSize
这种,须要让 CGSize
遵循 StringLiteralConvertible
这种协议)case
类型相同的话能够经过 rawValue
获取具体的值public enum ResultCode {
case success
case failure(code: BusinessCode)
case networkStatus(code: Int)
case unknow
public enum BusinessCode: String {
case systemException = "ss"
case permissionLimited = "pp"
var cv: Int {
if self == .systemException {
return 1
}
return 2
}
}
func dd() -> Int {
switch self {
case .success:
return 0
case .failure(let codeValue):
return codeValue.cv
case .networkStatus(let codeValue):
return codeValue
case .unknow:
return 99
}
}
}
let e: ResultCode = .unknow
e.dd()
复制代码
上面这个有点繁琐的例子大概展现了刚才列举枚举的功能。此外 Swift 4.2 引入了一个新的 protocol:CaseIterable
,可用于合成简单枚举里类型全部的值: allCases
静态属性:
public protocol CaseIterable {
/// A type that can represent a collection of all values of this type.
associatedtype AllCases : Collection where Self == Self.AllCases.Element
/// A collection of all values of this type.
static var allCases: Self.AllCases { get }
}
enum Weekday : String, CaseIterable {
case monday, tuesday, wednesday, thursday, friday
}
print(Weekday.allCases)
复制代码
从上面协议的定义能够看出这个自动合成的属性是个 Collection
,而且里面的元素都是 Self
类型 (其实打印下类型就能发现这个 Collection
就是 Array
),至关于 [Self]
。有点很差的是只能合成上面那种简单属性,不能合成带关联值的好比上面那个 ResultCode
,这时候只能本身提供这个属性了。
enum MartialStatus : CaseIterable {
case single
case married(spouse: String)
static var allCases: [MartialStatus] {
return [.single, .married(spouse: "Leon")]
}
}
复制代码
还有个不经常使用的东西是递归枚举,下面这段代码代码是官方提供的,知道有这个东西看看就好,须要用的时候在具体看下。下面这段代码用来实现一个表达式: (5 + 4) * 2
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case let .number(value):
return value
case let .addition(left, right):
return evaluate(left) + evaluate(right)
case let .multiplication(left, right):
return evaluate(left) * evaluate(right)
}
}
print(evaluate(product))
// 打印“18”
复制代码
另外 Swift 5 里更新了枚举的一个未知值警告。只有使用某些列举系统枚举时会这样,缘由是编译器规定列举枚举时若是没用 default
的话就必须列举完。那对于系统的一个枚举,若是 iOS 13
给某个枚举新增了一个枚举值,就会致使老代码没列举全而报错。 这里说某些是由于像 ComparisonResult
这种比较大小定义是永远不会增改的,因此也不会报警告。点击警告会补充一个 @unknown default:
固然也不是使用时都加个 default
来避免这个,好比 Equatable
协议里就不适合用 default
。
func configure(for sizeClass: UIUserInterfaceSizeClass) {
switch sizeClass {
case .unspecified: break
case .compact: break
case .regular: break
}
}
// Warning: Switch covers known cases, but 'UIUserInterfaceSizeClass' may have additional unknown values, possibly added in future versions
复制代码
最后若是想了解枚举底层的东西,能够看下 这篇文章。
泛型是 Swift 最强大的特性之一,不少 Swift 标准库是基于泛型代码构建的。实际上,即便你没有意识到,你也一直在语言指南中使用泛型。例如,Swift 的 Array
和 Dictionary
都是泛型集合。这段话是官方文档上的。
写完这部分再回来补了这一小段,这里除了使用介绍和一个命名空间例子也没说其余强大的用法,可是工程中泛型的使用很频繁,感受是属于那种就是看起来不是很复杂可是巧妙使用会效果很好的东西。有个很不错的例子是 Promisskit
,里面使用了不少泛型,很巧妙。
泛型使用 占位符
来限定为某一类型(好比说 Int
、String
、Collection
),在其做用域的出现的该 占位符
均限定为相同的类型。申明是写法为:<占位符A, 占位符B>
。占位符
能够是任意字母组合,可是通常来讲命名须要具备意义,好比集合中的泛型申明就叫 Element
。 下面经过几个例子看下。
// 泛型函数
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
// 泛型类和结构体
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
}
// 对泛型进行约束
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 这里是泛型函数的函数体部分
}
// 使用 where 进行泛型限定 (T,U 都是集合,而且集合内的 element 类型相同)
func someFunction<T, U>(someT: T, someU: U) where T: Collection, U: Collection, T.Element == U.Element {}
// swift 5.0 加入的一个泛型枚举
public enum Result<Success, Failure> where Failure : Error {
case success(Success)
case failure(Failure)
}
复制代码
在申明协议时,能够声明一个或多个关联类型做为协议定义的一部分,关联类型经过 associatedtype
关键字来指定,该泛型当实际类型在协议被遵循时才会指定。
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
// 关联到 Int
struct IntStack: Container {
typealias Item = Int
}
// 将关联泛型放到类申明
struct Stack<Element>: Container {}
复制代码
说这个以前顺带提下命名空间的事,根据 Swift 系统类和一些主流的第三方库申明能够看出 Swift 的命名通常不须要加项目或者模块前缀,由于能够经过静/动态库名的点语法访问来避免 duplicated symbols
问题。好比 Swift 5.0 新增了一个 Result
枚举,而日常使用的 Alamofire
也有一个 Result
,因此使用时能够用 Result
和 Alamofire.Result
来区分。固然若是是直接写不加前置命名空间的话,优先推断本身模块内的结构,其次是系统的,最后是三方库。
下面直接拿咱们 SwfitTips
里的内容来展现,第一眼看起来有点绕,多看两遍也就懂了,最后实现系统类扩展的调用 :image.xx.imageInfo
/// Wrapper for Kingfisher compatible types. This type provides an extension point for
/// connivence methods in Kingfisher.
public struct KingfisherWrapper<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
/// Represents a type which is compatible with Kingfisher. You can use `kf` property to get a
/// value in the namespace of Kingfisher.
public protocol KingfisherCompatible { }
public extension KingfisherCompatible {
/// Gets a namespace holder for Kingfisher compatible types.
public var kf: KingfisherWrapper<Self> {
get { return KingfisherWrapper(self) }
set { }
}
}
extension Image: KingfisherCompatible { }
extension ImageView: KingfisherCompatible { }
extension Button: KingfisherCompatible { }
//usage
extension KingfisherWrapper where Base: Image {
var imageInfo: String { return "image info" }
}
let image = UIImage()
image.kf.imageInfo
复制代码
步骤:
struct
作再次封装struct
作方法扩展协议提及来比较简单,能够方法、属性申明,再经过 extension
提供默认实现,因此能够更形象的实现多继承了,做为类型使用时和类、结构体基本同样。
Swift 中更推崇面向协议编程,Swift 标准库中不少都是用协议实现的,好比 Collection
等。协议这种轻量级的结构让编码会更加轻松。逻辑也会更清楚,对于它来讲,用来抽象项目中的逻辑造成一套规则比协议自身的语法运用更加剧要。
下面看一段代码和注释简单介绍下用法。
protocol MyProtocol {
/// 协议
func defaultImplementation()
/// 定义的时候须要考虑是否要值类型来异变
mutating func mutatFunction()
/// 默认 required,用一个空实现来造成可选函数
func optFunction()
}
extension MyProtocol {
/// 协议
func defaultImplementation() {
print("default protocol implementation")
}
mutating func mutatFunction() {
print("mutatFunction")
}
func optFunction() {}
}
class MyClass {
/// father
func defaultImplementation() {
print("default father implementation")
}
}
class MySubClass: MyClass, MyProtocol {}
let sub = MySubClass()
// 这里根据点语法会提示两个函数,可是点击哪一个都是进入 father 里的
sub.defaultImplementation()
复制代码
闭包和 OC 的 block
以及一些匿名函数差很少,在 Swift 中, 函数是一个特殊的闭包,主要在于值捕获的范围区别,所以不少函数的参数若是是一个闭包的话,能够传函数用来代替闭包,例如 sort
等函数,下面介绍一些基本写法。
// 闭包属性 声明时不能加标签,标准结构
var callbcak: (String, String) -> Bool = { (param1: String, param2: String) in
return param1 > param2
}
// 尾随闭包的简化流程
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func sort(by areInIncreasingOrder: (String, String) -> Bool)
// 完整的书写方法
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
// 简化1:系统会根据 names 的类型推断出参数 s1,s2
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
// 简化1:在使用尾随闭包时,你不用写出它的参数标签,而且放在 () 外面,只有一个参数时,() 能够省略,$0 和 $1 分别表示第一个和第二个传参
reversedNames = names.sorted() { $0 > $1 }
// 因为 sort 函数的参数是个闭包, > 也是和闭包类型相同的函数(Comparable),因此能够直接用
reversedNames = names.sorted(by: >)
复制代码
尾随闭包上面的例子中已简单介绍过了,也没什么复杂的用法。
自动闭包是一种不接受任何参数,被调用时返回被包装在其中的表达式的闭包,用 @autoclosure
标记。这种语法可以省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// 打印“Now serving Ewa!
复制代码
上面说过的 ??
函数就利用了自动闭包的特性。
闭包可分为 逃逸闭包
和 非逃逸闭包
,用 @escaping
来标记是 逃逸闭包
。举个例子,好比一个函数接受了一个闭包做为参数,若是闭包在该函数的做用域内执行,那这就是 非逃逸闭包
,若是闭包被赋值给了对象的属性用于函数的做用域以外执行,就属于 逃逸闭包
,须要加 @escaping
来标记。
事实上 Swift 在使用属性时会隐式的调用 self
这个东西,因此咱们正常状况下能够不写,可是对于 逃逸闭包
捕获的属性,必须显示声明 self
。对于 非逃逸闭包
,编辑器会在函数做用域结束时自动释放闭包捕获的值(由于闭包已经用完,因此捕获的值没有用了)。
Swift 3.0 以后闭包默认是非逃逸的。代码中常见的闭包属性、网络请求返回等都是 逃逸闭包
。此外可选类型的闭包老是逃逸的,而且不能显示申明逃逸关键字。
class A {
var complete: (() -> String)?
var value: String?
var b: B?
func nonclosureEscaping(with complete: () -> String) {
value = complete()
}
func closureEscaping(with complete: @escaping () -> String) {
self.complete = complete
}
func optionalClosure(with complete: (() -> String)?) {
guard let c = complete else { return }
value = c()
}
}
class B {
var value: String?
func testBegin() {
let a = A()
a.nonclosureEscaping { () -> String in
return value!
}
a.closureEscaping { () -> String in
return self.value!
}
a.optionalClosure { () -> String in
return self.value!
}
}
}
let b = B()
b.value = "ddd"
b.testBegin()
复制代码
函数派发机制至关于 OC 里的方法寻址,只是原理不一样,用于找寻方法具体的执行对象,是一个很是基础的知识点,日常遇到这方面的问题比较少,也不怎么会关心这个。
Swift 中函数派发方式有三种:
这里直接说结论和注意点
根据上面的结论看下下面两段代码
protocol MyProtocol {}
extension MyProtocol {
func testFuncA() {
print("protocl - funcA")
}
}
class Myclass: MyProtocol {
func testFuncA() {
print("class - funcA")
}
}
let x: MyProtocol = Myclass()
x.testFuncA() // protocl - funcA
复制代码
因为 x 是协议类型,因此先会去查协议的函数表,而协议里并无 funcA 的声明,因此协议函数表里就不存在这个方法,也不会去根据表查找实现,就走了协议的 extension 的直接派发。
protocol MyProtocol {
func testFuncA()
}
... (和上面同样)
let x: MyProtocol = Myclass()
x.testFuncA() // class - funcA
复制代码
因为协议函数表里声明了 funcA,因此用协议函数表查找实现,找到了在 Myclass 中的实现,走的函数表派发。
至于验证过程,须要用 SIL
一种编译中间语言,感兴趣的能够看看最下面的参考连接
上面也说过,Swift 是一门静态语言,和 OC 不一样,不能在运行时作不少复杂的操做,这也是 Swift 被吐槽比较多的一点。 Swift 虽然不能像 OC 同样在运行时作不少事,可是仍是作一些小动做,好比和 Java
相似的反射机制等。下面主要讨论下 Swift 能作哪些相对来讲具备 动态
特性的事。
首先说一个你们都知道的东西:@objc
,它能够标记函数和类、协议等等。这个标记表示该方法或者该类能够被用于运行时,例如在 Swift 中相似按钮点击的 selector
都必须用 @objc
标记,表示该方法是运行时调用(按钮点击是运行时事件,编译器在编译时对没有调用到的非运行时函数会优化掉),同时能够作其余好比 KVO
这种在运时的操做。因此加了 @objc
后可用于 OC 代码里。在 Swift 4
以后的版本里,类继承 NSObjct
不会再默认带 @objc
标记,当重写 NSObjct
编辑器会自动提示带上 @objc
。 这部分用代码也说不了什么就不贴了。
除了上面用 @objc
标记这种比较恶趣味的方法外,还能够经过 Reflection
即反射来实现一些诸如运行时获取类型、成员信息,调用任意方法等行为。Reflection
主要使用 Mirror
这个结构体来作一些事情。
先看下最简单的用法,动态获取一个类的成员信息:
class User {
var name: String = "eeee"
var age: Int = 122
}
let u = User()
let um = Mirror(reflecting: u)
for child in um.children {
guard let label = child.label else { continue }
print(label, child.value)
}
//name eeee
//age 122
复制代码
看到这里应该就能联想到 OC 里的模型和 JSON 互转的一些第三方库, Swift 里这种方法也是能够用来作模型转 JSON,可是不能用于 JSON 转模型(除非你让模型继承 NSObject,而后采用 KVC 赋值这种肉眼可见的 OC 动态),不过 JSON 转模型能够用其余方法作,好比 像 HandyJson
的 作法 同样,原理是经过计算属性相对偏移量来赋值,比较复杂(只在 Github 瞟了几分钟,没太看懂)。可是如今能够直接使用系统的 Codable
功能。
Swift 每一个类型都关联着一个元类型,大多数元类型是个全局的单例对象,可是 Class 类型遵循子类型的规则,因此没有存全局的单例对象。
type<T, Metatype>(of value: T) -> Metatype
用于获取对象运行时类型,它能够获取结构体、枚举、以及大多数协议类型的实例,除了协议类型的泛型。也就是说这个方法的参数是个泛型,你能够传具体的的结构体、类等,也能够传一个类型是结构体、类、枚举的泛型,可是不能传协议类型的泛型。有点绕口,直接看下面注释文档的代码
func printGenericInfo<T>(_ value: T) {
let x = type(of: value)
print("'\(value)' of type '\(x)'")
}
func betterPrintGenericInfo<T>(_ value: T) {
let x = type(of: value as Any)
print("'\(value)' of type '\(x)'")
}
func otherPrintGenericInfo<T>(_ value: T) {
let x = type(of: value as! P)
print("'\(value)' of type '\(x)'")
}
protocol P {}
extension String: P {}
let stringAsP: P = "Hello!"
printGenericInfo(stringAsP) // print: 'Hello!' of type 'P'
betterPrintGenericInfo(stringAsP) // print: 'Hello!' of type 'String'
otherPrintGenericInfo(stringAsP) // print: 'Hello!' of type 'String'
复制代码
能够看出来前面两个方法惟一的区别是第二个方法强制转成 Any
这种。
This unexpected result occurs because the call to `type(of: value)` inside `printGenericInfo(_:)` must return a metatype that is an instance of `T.Type`, but `String.self` (the expected dynamic type) is not an instance of `P.Type` (the concrete metatype of `value`). To get the dynamic type inside `value` in this generic context, cast the parameter to `Any` when calling `type(of:)`
根据上面的文档,解释说第一个方法中 type(of:)
返回的必须是 T.Type
, 而 String.self
并非 P.Type
(不懂为何不是,extension
不算吗),这个解释仍是有点不能理解。
一开始想介绍下这个东西的,按照原来的打算,是准备把 codingKey
、KeyDecodingStrategy
以及对枚举的 Codable
说一下的。可是在看了喵神的 这个博客 后,加上以前对 Mirror
的一些接触,以为只是说上面那些简单的用法没什么意义,转而想的是为何 Codable
能够只让类型只遵循一个协议就能作到 JSON 编码解码,还有就是上面喵神连接说的重写 _JsonEncoder
方法来去除模型字典互转时多的那层 data 操做。
这里说的是用在 where
后面时二者的区别,通常状况下 ==
是判断左右相等,:
是用于限定类型,从语境上就能区分区别。当使用在 extension
时,二者的区别在于: 若是扩展的是值类型,则必须用 ==
;若是扩展的是引用类型,则使用 ==
仅对本类有效对子类无效,使用 :
则对本类和子类均有效。
后者只能表示任意的 class 类型,AnyClass
是 AnyObject
的元类型。