Swift 一些特性

前言

写这个文章最开始的原因是我同学问我你学了几个月 Swift,感觉怎么样,和 OC 对比有什么特别之处,然后我说我也说不好。。自己从开始学 Swift 到现在也有不少几个月,很多特性或者说对比 OC 比较有特别的东西也没搞清楚,怎么好意思误人子弟,所以就自己四处谷歌百度写这个东西就当自己学习累积用。

还是发现自己太懒了,从开始写到写完断断续续用了半个多月,有时候遇到一个不会的点光查资料就查了半天,效率也比较低。公司 iOS 组里了解和使用过 Swift 的也不多,平常能请教的也只有小组长。至于写完估计也不会用来分享到组内,反正大家都不熟悉,也懒得浪费他们的时间了。

类型


在说其他的之前我想先扯下无聊的强类型语言/弱类型语言,动态语言/静态语言。

动态语言/静态语言:能/不能 在运行时可以改变其结构的语言。

强/弱类型语言:这两个术语并没有非常明确的定义,但主要用以描述编程语言对于混入不同数据类型的值进行运算时的处理方式。强类型的语言遇到函数引数类型和实际调用类型不匹配的情况经常会直接出错或者编译失败;而弱类型的语言常常会实行隐式转换,或者产生难以意料的结果。

(呃呃呃,至于有些文章说的动态类型/静态类型语言,找了下维基百科并没有单独拎出来定义,除了部分语言中提到过这个,意义偏向于逐渐模糊的强弱类型)。

上述分类中,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")
}

这个类型推断我也是懵逼,没明白编译器把这个表达式推断成什么样的错误情况,只能把它理解为编译器解释的不够完善。

元组(Tuples)


简单使用

元组类型是用括号包围,由一个逗号分隔的零个或多个类型的列表(只有一个值时会被推断为圆括号操作符,而非元组),这些值可以不加标签,直接用下标访问。如果为了意义明确,也可以为它加个标签,访问时就可以直接用标签访问。

元组和闭包、元类型等都是非正式类型,非正式类型时相对于结构体等正式类型而言,没有具体的类型名,虽然元组看起来比较像匿名结构体。

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)

Optional


类型是枚举: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 这个盒子里就是空,没有其他盒子。

至于用法,我也没怎么见过,只在 optionalmap 函数中见过。

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);当 UOptional 类型, 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 支持带参定义
  • 枚举里 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 的 ArrayDictionary 都是泛型集合。这段话是官方文档上的。

写完这部分再回来补了这一小段,这里除了使用介绍和一个命名空间例子也没说其他强大的用法,但是工程中泛型的使用很频繁,感觉是属于那种就是看起来不是很复杂但是巧妙使用会效果很好的东西。有个很不错的例子是 Promisskit ,里面使用了很多泛型,很巧妙。

基本介绍

泛型使用 占位符 来限定为某一类型(比如说 IntStringCollection),在其作用域的出现的该 占位符 均限定为相同的类型。申明是写法为:<占位符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 ,所以使用时可以用 ResultAlamofire.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 做再次封装
  • 定义适配协议,并使用 extension 为其提供默认实现
  • 对自定义的 struct 做方法扩展

Protocol


协议说起来比较简单,可以方法、属性申明,再通过 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 中函数派发方式有三种:

  • static dispatch:静态派发(直接派发)
  • table dispatch:函数表派发, 通过 SIL 分析,swift 中有两种函数表,协议用的 witness_table,类用的 virtual_table
  • message dispatch:消息派发,OC 中常用的派发方式

这里直接说结论和注意点

dispatch.png
  • 引用的类型决定了派发的方式。
  • NSObjectClass 和 Class 没什么区别(至少编译器未优化前是这样)

根据上面的结论看下下面两段代码

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 功能。

type(of:)

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 不算吗),这个解释还是有点不能理解。

其他一笔带过的小点


Codable

一开始想介绍下这个东西的,按照原来的打算,是准备把 codingKeyKeyDecodingStrategy 以及对枚举的 Codable 说一下的。但是在看了喵神的 这个博客 后,加上之前对 Mirror 的一些接触,觉得只是说上面那些简单的用法没什么意义,转而想的是为什么 Codable 可以只让类型只遵循一个协议就能做到 JSON 编码解码,还有就是上面喵神链接说的重写 _JsonEncoder 方法来去除模型字典互转时多的那层 data 操作。

static/class

  • class 只能被用于类里,static 可以被用于 class/struct/protocol/enum
  • static 可以用于修饰存储属性,class 不行
  • static 修饰的类方法不能被继承,class 可以,static 相当于 final class

==/:

这里说的是用在 where 后面时两者的区别,一般情况下 == 是判断左右相等,: 是用于限定类型,从语境上就能区分区别。当使用在 extension 时,两者的区别在于:
如果扩展的是值类型,则必须用 ==;如果扩展的是引用类型,则使用 == 仅对本类有效对子类无效,使用 : 则对本类和子类均有效。

Any/AnyObject

后者只能表示任意的 class 类型,AnyClassAnyObject 的元类型。

不明白的地方

  1. 逃逸闭包为什么要显示的声明 self
  2. 为什么函数表的方式无法动态生成函数插入到表里,消息派发机制可以?消息派发机制如何动态添加到函数表的,暂时猜测 runtime 的函数表是多个表的集合,不太懂。
  3. 编辑器有没有方法在静态编译时把运行时触摸事件的处理函数编译到函数表中,并且在运行时调用到

引用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容