Skip to content

Latest commit

 

History

History
722 lines (508 loc) · 24.8 KB

02.md

File metadata and controls

722 lines (508 loc) · 24.8 KB

2017.02

查看一个对象的类型是否为Optional

Swift中,如果想在运行时判断一个对象的类型是不是可选类型,则可以使用Mirror,如下代码所示,所有的可选类型都将返回optional

let value: Int? = 3
Mirror(reflecting: value).displayStyle		// optional

MirrordisplayStyle属性的类型是Mirror.DisplayStyle,这是一个枚举类型,用于为Mirror的主体对象提供一种建议性的解析,其定义如下代码所示:

/// A suggestion of how a `Mirror`'s `subject` is to be interpreted.
///
/// Playgrounds and the debugger will show a representation similar
/// to the one used for instances of the kind indicated by the
/// `DisplayStyle` case name when the `Mirror` is used for display.
public enum DisplayStyle {

    case `struct`

    case `class`

    case `enum`

    case tuple

    case optional

    case collection

    case dictionary

    case set
}

枚举值范围之外的类型,其MirrordisplayStyle属性将返回nil,如下代码所示:

let value: Int = 20
Mirror(reflecting: value).displayStyle		// nil

let str: String = "30"
Mirror(reflecting: str).displayStyle		// nil

SwiftLint

realm开源了一套Swift代码规范检测工具SwiftLint,它可以对代码做静态分析,以确定代码是否遵循所定制的规范(目前主要基于Github的编码规范)。

SwiftLint目前有70+条规则,同时社区还在不断贡献新的规则。支持的编辑器包括XcodeAppCodeAtom,同时还支持命令行。

其执行的基本效果如下图所示。

Swift自定义操作符

Swift中,自定义操作符就是简单的二步:首先在全局使用operator关键字来声明操作符,同时用prefixinfixpostfix来声明操作符的位置;然后在所需要的类/结构体中实现操作符。如下代码所示:

postfix operator >?
postfix operator >!

extension MIType {
    public static postfix func >?(type: MIType) -> MIType {
        return MIType("Optional<\(type.name)>")
    }
    
    public static postfix func >!(type: MIType) -> MIType {
        return MIType("ImplicitlyUnwrappedOptional<\(type.name)>")
    }
}

自定义操作符需要以两类字符开头:

  1. ASCII字符中的/, =, -, +, !, *, %, <, >, &, |, ^, ?, ~
  2. Unicode中的Mathematical Operators, Miscellaneous SymbolsDingbats Unicode blocks这些字符中的字符

然后后面允许使用组合的Unicode字符。如下代码是以一个Miscellaneous Symbols开头的实现向量加法的操作符。

infix operator ★+

struct Vector2D {
    var x: CGFloat
    var y: CGFloat
}

extension Vector2D {
    static func ★+ (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y + right.y)
    }
}

let vector1 = Vector2D(x: 10, y: 20)
let vector2 = Vector2D(x: 30, y: 10)

let vector = vector1 ★+ vector2

vector.x		// 40.0
vector.y		// 30.0

参考

  1. The Swift Programming Language (Swift 4) -- Advanced Operators
  2. The Swift Programming Language (Swift 4) -- Lexical Structure

自定义操作符中的.

在自定义操作符时,可以以dot(.)开头,这种情况下,操作符后面还可以包含其它的dot(.),如下代码所示:

infix operator .+.

struct Vector2D {
    var x: CGFloat
    var y: CGFloat
}

extension Vector2D {
    static func .+.(left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y + right.y)
    }
}

let vector1 = Vector2D(x: 10, y: 20)
let vector2 = Vector2D(x: 30, y: 10)

let vector = vector1 .+. vector2

vector.x
vector.y

但如果操作符不是以dot开头,则后面不能再包含dot,如operator +.+这个声明会被看成是"+"操作符后面跟了个".+"操作符。编译器会给出如下错误提示:

infix operator +.+		// error: operator with postfix spacing cannot start a subexpression

参考

  1. The Swift Programming Language (Swift 4) -- Lexical Structure

Swift打印对象的地址

Swift中,我们可以使用withUnsafePointer(to:_:)函数来获取一个变量的指针,如下代码所示:

var x = 42
var y = 3.14
var z = "foo"
var obj = NSObject()

withUnsafePointer(to: &x) {ptr in print(ptr)}
withUnsafePointer(to: &y) {ptr in print(ptr)}
withUnsafePointer(to: &z) {ptr in print(ptr)}
withUnsafePointer(to: &obj) {ptr in print(ptr)}

// 输出
// 0x000000011a145660
// 0x000000011a145668
// 0x000000011a145670
// 0x000000011a145688

withUnsafePointer(to:_:)将第一个参数转换为指针,然后将这个指针作为参数去调用第二个参数指定的闭包。如果闭包有返回值,它将作为函数的返回值。

需要注意的是,�生成的指针的生命周期限定于闭包内部,不能将其指定给外部的变量。

第二种打印变量的指针的方式如下代码所示:

var x = 42
var y = 3.14
var z = "foo"
var obj = NSObject()

func printPointer<T>(ptr: UnsafePointer<T>) {
    print(ptr)
}

printPointer(ptr: &x)
printPointer(ptr: &y)
printPointer(ptr: &z)
printPointer(ptr: &obj)

// 输出
// 0x000000011a145660
// 0x000000011a145668
// 0x000000011a145670
// 0x000000011a145688

参考

  1. withUnsafePointer(to:_:)

ArraySlice的用途

Swift提供了ArraySlice来执行数组的切片操作。类似于其它语言中的切片(如Python),ArraySlice对象复用了原始数组的存储结构,而不是新开辟一块内存区域来将数组片断的元素拷贝过来。因此,它能让我们快速高效地对大数组的片段执行操作。如下代码所示:

var array: [Int] = []

for i in 0..<1000 {
    array.append(i)
}

let slice = array[100..<300]
let result = slice.map {
    $0 * 2
}.reduce(0) {
        $0 + $1
}

print(result)       // 79800

ArraySliceArray有相同的接口,所以通常可以在切片数组上执行与原始数组相同的操作。

参考

  1. ArraySlice

ArraySlice使用注意事项

使用ArraySlice时,需要注意两点:

  • ArraySlice会维持对原始数组的一个强引用,而不仅仅是它所表示的片断。这样即使原始数组对象的生命周期结束了,也可能无法释放。所以不建议长期存储ArraySlice对象,仅用于临时操作。如下代码所示:
class MyClass {
    var index: Int
    init(index: Int) {
        self.index = index
    }
}

class ViewController: UIViewController {
    
    var slice: ArraySlice<MyClass>? = nil
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var array: [MyClass] = []
        for i in 0..<100 {
            let clz = MyClass(index: i)
            array.append(clz)
        }
        
        slice = array[10..<30]
    }
}

array在生命周期结束后不会释放,如下图所示。

8-1-1

  • Array不同的是,ArraySlice起始索引不一定是0,而是取决于其创建方式。一般是采用共享索引的方式,即ArraySlice对象的起始索引就是切片的开始位置,如代码清单8-2-2所示,切片是从100开始,所以slice[100]是OK的,而slice[99]会报越界错误。通常建议使用startIndexendIndex来取代指定的索引值,如下代码所示。
var array: [Int] = []

for i in 0..<1000 {
    array.append(i)
}

let slice = array[100..<300]

slice[100]
slice[99]       // fatal error: Index out of bounds
slice[slice.startIndex]         // 100
slice[slice.endIndex - 1]       // 299

参考

  1. ArraySlice

尾调用优化

尾调用是指一个函数里的最后一个动作是一个函数调用的情形:即这个调用的返回值直接被当前函数返回。我们知道在函数调用时,会在内存中生成一个调用记录,即调用帧(call frame),用于保存调用位置和内部变量等信息。而尾调用由于是函数的最后一步操作,所以可以不在调用栈上添加一个新的调用帧,而是直接更新外层函数的调用帧,这种处理方式即尾调用优化(Tail Call Optimization, TCO)。

尾递归也是一种尾调用,通过TCO,可以使原本 O(n) 的调用栈空间只需要 O(1)

一些语言会针对尾递归做一些优化,但Swift不确保在所有情况下都做了优化。

参考

  1. Does Swift implement tail call optimization? and in mutual recursion case?
  2. Functional Swift: Tail Recursion Explained
  3. 尾调用

多维下标

我们通常看到的下标操作都是一维的,不过如果有需要,我们可以定义多维的下标操作,如下代码所示:

// �定义一个三维结构体
struct Vector3D<T: Equatable> {
    var x: T
    var y: T
    var z: T
}

// 向量数组
struct VectorArray<T: Equatable> {
    
    var vectors: [Vector3D<T>] = []
    
    init() {
        
    }
    
    mutating func append(vector: Vector3D<T>) {
        vectors.append(vector)
    }
    
    // 此处是根据输入的x, y, z值来确定是否有对应的值,而不是索引
    // 如果有,则返回该向量值;如果没有,返回nil
    subscript(x: T, _ y: T, _ z: T) -> Vector3D<T>? {
        for vector in vectors {
            if vector.x == x && vector.y == y && vector.z == z {
                return vector
            }
        }
        
        return nil
    }
}

var vectors = VectorArray<Int>()

vectors.append(vector: Vector3D(x: 0, y: 1, z: 2))
vectors.append(vector: Vector3D(x: 3, y: 4, z: 6))
vectors.append(vector: Vector3D(x: 8, y: 4, z: 8))

print(vectors[0, 0, 0])            // nil
print(vectors[3, 4, 6])            // Optional(__lldb_expr_167.Vector3D<Swift.Int>(x: 3, y: 4, z: 6))

Swift中打印线程调用栈

Swift 3中,如果想打印线程的调用栈,可以简单的使用Thread.callStackSymbols,如下代码所示:

for symbol in Thread.callStackSymbols {
    print(symbol)
}

// 0   ???                                 0x00000001198270a1 0x0 + 4722946209
// 1   MyPlayground                        0x0000000106051730 main + 0
// 2   CoreFoundation                      0x000000010989620c __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
// 3   CoreFoundation                      0x000000010987aa3b __CFRunLoopDoBlocks + 203
// 4   CoreFoundation                      0x000000010987a214 __CFRunLoopRun + 1300
// 5   CoreFoundation                      0x0000000109879a89 CFRunLoopRunSpecific + 409
// 6   GraphicsServices                    0x000000010d5e79c6 GSEventRunModal + 62
// 7   UIKit                               0x0000000106af5d30 UIApplicationMain + 159
// 8   MyPlayground                        0x00000001060517f9 main + 201
// 9   libdyld.dylib                       0x00000001062e5d81 start + 1
// 10  ???                                 0x0000000000000001 0x0 + 1

如果想使用C函数backtrace,需要在bridging header file中导入execinfo.h头文件,然后如理代码所示,记得最后需要用free释放symbols

var callstack = [UnsafeMutableRawPointer?](repeating: nil, count: 128)
let frames = backtrace(&callstack, Int32(callstack.count))
if let symbols = backtrace_symbols(&callstack, frames) {
    for frame in 0..<Int(frames) where symbols[frame] != nil {
        let symbol = String(cString: symbols[frame]!)
        print(symbol)
    }
    free(symbols)
}

参考

  1. how to call backtrace_symbols() in swift

Swift RawRepresentable

Swift编译器自动为带有raw typeenum添加了RawRepresentable协议的实现。RawRepresentable协议的定义如代码清单11-1-1所示,它可以为某个类型定义一个关联的raw value,并在两者之间切换。如下代码所示是一个典型的带raw value的枚举。

public protocol RawRepresentable {

    associatedtype RawValue

    public init?(rawValue: Self.RawValue)

    public var rawValue: Self.RawValue { get }
}
enum Counter: Int {
    case one = 1, two, three, four, five
}

for i in 3...6 {
    print(Counter(rawValue: i))
}

// 输出
// Optional(__lldb_expr_197.Counter.three)
// Optional(__lldb_expr_197.Counter.four)
// Optional(__lldb_expr_197.Counter.five)
// nil

参考

  1. RawRepresentable

Swift在扩展中重写父类方法

Swift在类的extension中,如果想重写父类的方法,会有以下两个限制(不局限于这两个,可能还有其它):

  1. 类本身必须是NSObject体系下的类,且要重写的方法的参数、返回值不能带有纯Swift的类/结构体/枚举;
  2. 不能使用inout参数;

当然参数和返回值的类型如果是StringArrayDictionarySet这些也是OK的。

Swift中操作符优先级

Swift 3中改进了操作符的优先级及结合性的声明方式。

Swift 3之前,是使用magic numbers(魔数)的方式来声明操作符的优先级,如下代码所示:

infix operator <~ {
    associativity left
    precedence 125
}

magic numbers总归是一个不好的东西,所以Swift 3改用precedence groups(优先级组)的方式来声明操作符的优先级,如下代码所示:

precedencegroup Equivalence {
    associativity: left
    higherThan: LogicalConjunctionPrecedence
    lowerThan: ComparisonPrecedence
}

infix operator ~ : Equivalence

系统为我们提供了一些默认的precedence groups,如下代码所示:

precedencegroup AssignmentPrecedence {
  assignment: true
  associativity: right
}
precedencegroup TernaryPrecedence {
  associativity: right
  higherThan: AssignmentPrecedence
}
precedencegroup DefaultPrecedence {
  higherThan: TernaryPrecedence
}
precedencegroup LogicalDisjunctionPrecedence {
  associativity: left
  higherThan: TernaryPrecedence
}
precedencegroup LogicalConjunctionPrecedence {
  associativity: left
  higherThan: LogicalDisjunctionPrecedence
}
precedencegroup ComparisonPrecedence {
  higherThan: LogicalConjunctionPrecedence
}
precedencegroup NilCoalescingPrecedence {
  associativity: right
  higherThan: ComparisonPrecedence
}
precedencegroup CastingPrecedence {
  higherThan: NilCoalescingPrecedence
}
precedencegroup RangeFormationPrecedence {
  higherThan: CastingPrecedence
}
precedencegroup AdditionPrecedence {
  associativity: left
  higherThan: RangeFormationPrecedence
}
precedencegroup MultiplicationPrecedence {
  associativity: left
  higherThan: AdditionPrecedence
}
precedencegroup BitwiseShiftPrecedence {
  higherThan: MultiplicationPrecedence
}

参考

  1. Improved operator declarations

Never类型

之前有一条知识小集讲了可以用 @noreturn 用来修饰函数,以告诉编译器函数永远不返回值给调用方。如下代码是原来的fatalError的声明。

public func fatalError(_ message: @autoclosure () -> String = default, file: StaticString = #file, line: UInt = #line)

不过作为函数类型的一个正交属性,它与函数声明的其它一些属性一起使用时会产生一些歧义,如与@throwsnon-void返回值。如当成 @throws 一起使用时,可能会困惑指"无法正常返回,但可以抛出异常",还是"完全无法返回"?

所以现在使用Never枚举类型来替代@noreturn,以消除这种歧义,所以fatalError现在声明如下代码所示。这样() throws -> Never就好理解了,即"不能正常返回,但可能抛出异常"。

/// Unconditionally prints a given message and stops execution.
///
/// - Parameters:
///   - message: The string to print. The default is an empty string.
///   - file: The file name to print with `message`. The default is the file
///     where `fatalError(_:file:line:)` is called.
///   - line: The line number to print along with `message`. The default is the
///     line number where `fatalError(_:file:line:)` is called.
public func fatalError(_ message: @autoclosure () -> String = default, file: StaticString = #file, line: UInt = #line) -> Never

需要注意的是Never是一个空枚举,如下代码所示:

/// The return type of functions that do not return normally; a type with no
/// values.
///
/// Use `Never` as the return type when declaring a closure, function, or
/// method that unconditionally throws an error, traps, or otherwise does
/// not terminate.
///
///     func crashAndBurn() -> Never {
///         fatalError("Something very, very bad happened")
///     }
public enum Never {
}

参考

  1. Remove @noreturn attribute and introduce an empty Never type

Uninhabited Types

Never是一个空枚举,即没有有效的case。所以它是一种uninhabited type,即明显没有值的类型。Swift中,以下两种情况可以看成是uninhabited type

  1. 一个枚举类型,如果没有case,或者所有case已知,且所有的case都有关联值,而所有的关联值类型都为空;
  2. 元组、结构体或者类,如果有任意的uninhabited type类型的存储属性;

If an expression of uninhabited type is evaluated, it is considered unreachable by control flow diagnostics.

另外,函数和元类型不能是uninhabited type

参考

  1. Remove @noreturn attribute and introduce an empty Never type

Repeated结构体

Swift提供了一个Repeated结构体,用于表示一个所有元素都相同的集合。我们可以使用repeatElement(_:count:)函数来创建Repeated集合,如下代码所示:

let repeatedName = repeatElement("test", count: 5)
for name in repeatedName {
    print(name)
}

// 输出
// test
// test
// test
// test
// test

@autoclosure

Swift中的@autoclosure还是一个很有意思的属性,它用于修饰函数中的闭包类型的参数,它主要有两个作用:

  • 将一个表达式自动封装成一个闭包,从而简化函数/方法的调用方式,如下代码所示;
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

// ��显示闭包参数,调用时参数以闭包的方式传入
func serve(curstomer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}

serve(curstomer: {customersInLine.remove(at: 0)})

// autoclosure闭包
func serve2(curstomer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}

// 表达式`customersInLine.remove(at: 0)`被自动封装成一个闭包
serve2(curstomer: customersInLine.remove(at: 0))
  1. 由于表达式封装成了闭包,所以可以延迟表达式的计算,一直到闭包被调用时。关于这一点的好处,可以参考这里中对assert实现的介绍。

需要注意的是,封装后的闭包一般不带参数,而表达式的值就作为返回值。所以声明函数时闭包一般没有输入参数,即使有也会被忽略,实际上在写代码时,Xcode就是将@autoclosure参数当成一个普通的类型

参考

  1. [The Swift Programming Language (Swift 4) -- Closures](https://developer.apple.com/library/prerelease/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID543 https://developer.apple.com/swift/blog/?id=4)

Autolayout Theory

View hierarchylayout可以定义为一系列的线性方程式。每个约束都表示一个方程式,大部分约束定义的是两个视图之间的关系,当然也可以是一个视图的两个不同的属性之间的关系,如高宽比。

下图是文档中给出的一个等式样例,

下图是一个基本的解释,

参考

  1. Auto Layout Guide

Swift Compile Warning

Swift中,我们通常会用FIXME/TODO/MARK来做一些注释标注,这些标注在代码的dropdown box中能显示出来,如下图所示:

不过,如果我们标注的是一些警告或错误信息的话,这用方式通常比较容易遗忘,不像Objective-C中的#warning一样,在编译时能被检测出来。

如果我们想在编译时让编译器高亮提示,即如下图所示,

则可以在Build Phases中添加一个Run Script,脚本代码如下图所示:

可以看到,我们还能添加自定义的注释标签,如WARNING/ERROR

参考

  1. HOW TO HIGHLIGHT YOUR TODOS, FIXMES, & ERRORS IN XCODE

iOS动画在系统中处理的2个阶段

接上一条。打包的layer和动画发送到render server后,会被反序列化成render treeRender server将对动画的每一帧执行以下两步操作:

  1. 计算所有layer属性的中间值,并设置OpenGL的几何属性(如纹理三角形)来执行渲染操作;
  2. 渲染可见的三角形到屏幕上

这两步操作在动画期间不断重复。第一步操作由CPU来完成,第二步操作GPU来完成。两步操作都是由系统控制。

iOS动画应用内处理的4个阶段

在iOS中,动画的执行主要分6个阶段,其中4个阶段是在应用内部处理的:

  1. Layout: 这一阶段是准备好view/layer的层级结构并设置layer的属性;
  2. Display: 这一阶段将绘制layer的内容。绘制操作会调用-drawRect:-drawLayer:inContext:方法;
  3. 准备阶段: 这一阶段是Core Animation准备将动画数据发送给render server。另外,还会执行其它一些操作,如解压在动画过程中显示的图片;
  4. 提交: 这是应用内部处理的最后一个阶段,Core Animation将打包layer及动画属性并通过IPC发送到render server

这几个阶段都是由CPU来处理的,而且我们只能控制前面两个阶段,后两个阶段将由Core Animation来控制。

导致layer绘制的性能问题

CALayer的大部分属性都是通过GPU来绘制的。以下几类操作可能导致layer绘制的性能问题:

  1. 过多的几何形状:这里的瓶颈主要是在对layer的预处理及通过IPC发送到render server这个过程,即CPU的处理,而不是GPU的绘制;
  2. 过多的重绘(overdraw):这主要是由绘制重叠的半透明层引起的;
  3. 离屏渲染:这个应该比较熟悉,诸如layer的圆角绘制、mask、阴影等操作都会导致离屏渲染,主要是因为需要为离屏绘制的图片分配额外的内存并在绘制上下文之间切换;
  4. 过大的图片:如果图片大小超过GPU所支持的最大纹理大小,则在绘制前CPU需要去做预处理。

参考

iOS Core Animation Advanced Techniques