Swift 2.0 笔记

  • let声明常量,var声明变量
  • 注释:///*...*/,多行注释/*...*/可嵌套!
  • 句尾分号可省略
  • 类型别名:typealias AudioSample = UInt16
  • 空合运算符, a ?? b等价于a!=nil ? a! : b

String

  • String是值类型,由编译器保证只在必要时拷贝
  • 字符串插值:\(...)
  • .characters表示对应的unicode字符组,.characters.count表示所含的unicode字符数
  • 不能用数字作索引,使用.startIndex.endIndex.startIndex.advancedBy(7)
  • 相等:==,前缀:.hasPrefix(),后缀:.hasSuffix()
  • 可赋值给Selector:let mySelector: Selector = “tappedButton:"

Tuple

  • let http404Error = (404, "Not Found")
  • 只需要一部分元组值,忽略的部分用_标记:let (justTheStatusCode, _) = http404Error
  • 通过下标访问元组元素:http404Error.0
  • 定义元组时给元素命名:let http200Status = (statusCode: 200, description: "OK"),然后通过名字访问这些元素:http200Status.description

Collection

  • 数组类型[Int]、集合类型Set<Int>、字典类型[String:Int]
  • 已推断出类型时,[]空数组,[:]空字典
  • 合并两数组可用+
  • 数组可区间替换,如array[4...6] = ["Bananas", "Apples"],把中间三个元素换为两个
  • 带下标遍历数组,如:for (index, value) in array.enumerate())
  • 集合的值和字典的键必须hashable;所有基本类型(如String, Int, Double, Bool)都hashable

Enum

  • 用法一:存储任何类型的关联值,类似union,给enum中各变量声明不同类型(且不赋实际值)

    1
    2
    3
    4
    enum Barcode {
    case UPCA(Int, Int, Int, Int)
    case QRCode(String)
    }

  • 用法二:给enum中各变量赋予同类型的原始值(rawValue)

    1
    2
    3
    4
    5
    enum ASCIIControlCharacter: Character {
    case Tab = "\t"
    case LineFeed = "\n"
    case CarriageReturn = "\r"
    }
    带原始值的枚举类型自带一个failable构造器init?(rawValue:)

  • 关联值是自身这一枚举类型,要在这关联值前用indirect表示该成员可递归

    1
    2
    3
    4
    5
    enum ArithmeticExpression {
    case Number(Int)
    indirect case Addition(ArithmeticExpression, ArithmeticExpression)
    indirect case Multiplication(ArithmeticExpression, ArithmeticExpression)
    }

Optional

  • 实际上是enum:
    1
    2
    3
    4
    enum Optional<T> {
    case None
    case Some(T)
    }
  • 可空类型(形如Type?),可能存在某个值或不存在值,用someOptional!强制展开
  • 可空绑定,如if let constName = someOptional { }
  • 可空链式调用,如street = paul.residence?.address?.street,返回的都是可空值(即使原函数返回非空值)
  • 隐式展开可空值(形如Type!),变量在首次初始化后不再为空(一般作实例变量,由类初始化),直接用someOptional访问

Control Flow

  • if/for/while等的条件语句不需要括号:for index in 1...n
  • switch的每个值都至少要有个case分支对应,当现有的分支无法涵盖所有值时要使用default分支
  • case分支不会fallthrough,不需要显式地break;但可用fallthrough关键字让控制流直接接上下一个case的执行代码(跳过下一个case的条件检查)
  • case可以区间匹配(如数值区间3...5,tuple区间(_, 0),逗号分隔的多个区间1,3,5...7),各case的区间重叠时按序选择匹配
  • case中的模式匹配:
    1
    2
    3
    4
    5
    6
    7
    8
    let color = (1.0, 1.0, 1.0, 1.0)
    switch color {
    case (0.0, 0.5...1.0, let blue, _):
    print("Green and \(blue * 100)% blue")
    case let (r, g, b, 1.0) where r == g && g == b:
    print("Opaque grey \(r * 100)%")
    ...
    }
  • 循环和switch语句可以加标签,这样多层嵌套时可以break/continue到指定标签
  • guard语句类似if语句,但guard总是有一个else分句

Function & Closure

  • 内外参数同名:func someFunc(paraName: Int) {};显式指定外部参数名: func someFunc(externalParaName localParaName: Int) {}
  • 参数列表末可以提供默认参数:func someFunc(paraName: Int = 12) {}
  • 可变参数数组,如func mean(numbers: Double...) -> Double {},最多只能有一个且放在参数列表最最后(默认参数之后)
  • 参数默认是常量,若要将参数作为可修改副本使用,在参数名前加varfunc someFunc(var paraName: String) {}
  • inout参数由外部在调用时传引用&varParam
    1
    2
    3
    4
    5
    func swapTwoInts(inout a: Int, inout _ b: Int) {
    let tmp = a; a = b; b = tmp
    }
    ...
    swapTwoInts(&someInt, &anotherInt)
  • 函数类型由参数和返回值类型组成,如:(Int, Int) -> Int, (Int) -> Int, () -> ()
  • 函数中可以嵌套定义新函数
  • 闭包语法:
    1
    2
    3
    { (parammeters) -> returnType in
    statements
    }
  • 闭包可用$0, $1, $2等速记参数:reversed = sort(names, { $0 > $1 })

Struct & Class

  • struct不能继承;struct是值类型(传拷贝),class是引用类型(传引用)
  • 所有基本类型(包括字符串、数组、字典)都是值类型,用struct实现的,编译器会保证只有确实必要时才执行实际拷贝
  • struct可以按成员初始化:let vga = Resolution(width: 640, height: 480)
  • lazy var在首次使用时才初始化;全局常量/变量自动是lazy的(不需lazy标记)
  • 若某实例方法要修改struct/enum等值类型中的属性(包括修改self),得给方法添加mutating声明
  • 定义下标索引,如:
    1
    2
    3
    4
    subscript(index: Int) -> Int {
    get {}
    set(newValue) {}
    }
    也可以是二维的,如: subscript(row: Int, column: Int) -> Double { ... } ,访问时可用matrix[1, 0]
  • 子类重载父类特性时要加override声明
  • 不想被重载的特性用final声明
  • 类至少有一个designated构造器,它先初始化本类引入的所有存储型属性,然后调用父类的designated构造器(delegate up),再为继承的属性设置新值
  • convenience构造器要前加convenience声明,它调用本类的其他构造器并最终调用到某个designated构造器(delegate across),再为任意属性设置新值
  • 两阶段初始化:就是个中序遍历,先初始化存储型属性,再访问super.init(),再之后算阶段二用来作些自定义
  • 若子类没有designated构造器,则自动继承父类的designated构造器
  • 若子类实现了父类的所有designated构造器(不管是自动继承的还是自己实现的),则自动继承父类的convenience构造器
  • 可失败构造器:init?(...),若要返回的对象隐式展开:init!(...)
  • 子类都必须实现的构造器,前加required修饰
  • 用闭包和函数设置属性的默认值:
    1
    2
    3
    4
    5
    6
    class SomeClass {
    let someProperty: SomeType = {

    return someValue
    }()
    }
  • 每个类最多只能有一个析构器deinit,子类析构器的最后会自动调用父类析构器

ARC

  • 对生命周期中会变为nil的实例使用弱引用(weak, optional)
  • 对初始化赋值后再也不会变为nil的实例使用无主引用(unowned)
  • 两个类中的属性互相引用,要打破循环强引用:
    • 两个都可为nil:其中一个使用弱引用(weak)
    • 一个可为nil,一个不可为nil:不可为nil的使用无主引用(unowned)
    • 两个都不可为nil:一个使用隐式展开可空属性(Type!),一个使用无主引用(unowned)
  • 将闭包赋给属性,要打破循环引用,用无主引用来捕获self,如:
    1
    2
    3
    4
    lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    ...
    }
    1
    2
    3
    4
    lazy var someClosure: () -> String = {
    [unowned self, weak delegate = self.delegate!] in
    ...
    }

Error Handling

  • 错误用符合ErrorType协议的值表示:enum VendingMachineError: ErrorType
  • throws声明函数将抛出错误:func canThrowErrors() throws -> String
  • throw抛出错误:throw VendingMachineError.OutOfStock
  • 调用抛出错误的函数时,前面加上trytry canThrowErrors()
  • 用do-catch捕获错误:
    1
    2
    3
    4
    5
    6
    do {
    try canThrowErrors()
    ...
    } catch someError {
    ...
    }
  • swift的错误和其他语言的异常类似,但是不会展开调用堆栈,因此throw语句的性能可以几乎和return语句一样
  • 如果确认某个声明了throws的函数不会抛出错误,可以用try!禁止错误传播:try! willOnlyThrowIfTrue(false)
  • defer语句将操作压栈,推迟到当前作用域结束时执行

Type Casting

  • 类型检查用is,强制类型转换用as,如let view = object as UIView,尝试类型转换用as?,如if let view = object as? UIView { ... }
  • 可以转换整个数组:if let viewArray = objectArray as? [UIView] { ... },有任一元素转换失败时整个返回nil
  • AnyObject表示任何class类型,Any表示任何类型

Extension

  • 可以添加新的方法,但不能重写已有的方法
  • 可以添加计算型属性,但不能添加存储型属性,也不能给已有属性添加属性观测器(willSet/didSet等)
  • 可以添加convenience构造器,但不能添加designated构造器或析构器

Protocol

  • 如果协议的方法将改变遵循该协议的对象的属性,则在该方法前加mutating关键字(为了使struct/enum等值类型也能遵循该协议)
  • 协议可以继承一个或多个其他协议:protocol SomeProtocol: InheritedProtocolOne, InheritedProtocolTwo {}
  • 限制只能由class遵循该协议(struct/enum不能遵循该协议),要在继承列表开头使用class关键字:protocol SomeClassOnlyProtocol: class, InheritedProtocol {}
  • 变量遵循了多个协议:person: protocol<Named, Aged>
  • 能用is/as/as?检查变量是否遵循协议
  • 协议包含可选方法或属性时,要加@objc前缀:
    1
    2
    3
    4
    @objc protocol CounterDataSource {
    optional func incrementForCount(count: Int) -> Int
    optional var fixedIncrement: Int { get }
    }
  • 可通过Extension为协议提供默认实现,扩展协议时还能用where描述限制条件:
    1
    2
    3
    extension CollectionType where Generator.Element : TextRepresentable {
    // 扩展CollectionType协议,但只适用于元素遵循TextRepresentable的情况
    }
  • 可以用typealias声明关联类型,给类型提供占位名,当某类实现这协议时能自动推断出实际的关联类型
    1
    2
    3
    4
    5
    protocol Container {
    typealias ItemType
    mutating func append(item: ItemType)
    ...
    }

Generics

  • 泛型,形如struct Stack<T> { ... }
  • 扩展泛型时,不需要在扩展的定义中再次声明类型参数,原类型参数可以直接使用:
    1
    2
    3
    extension Stack {
    var topItem: T? { ... }
    }
  • 泛型的类型参数可以添加约束,如:
    1
    2
    3
    4
    5
    func allItemsMatch<C1: Container, C2: Container
    where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
    (someContainer: C1, anotherContainer: C2) -> Bool {
    ...
    }

Access Control

  • private源文件内可见;internal模块内可见,是默认值,模块指以独立单元构建和发布的framework或application;public都可见
  • 函数的访问级别由参数和返回值中最小的一个决定

Operators

  • 算数运算符默认不会溢出,溢出运算符有:&+&-&*
  • 重载运算符,如:
    1
    2
    3
    4
    struct Vector2D { var x = 0.0,, y = 0.0 }
    func +(left: Vector2D, right: Vector2D) -> Vector2D { } // 中缀
    prefix func -(vector: Vector2D) -> Vector2D {} // 前缀
    func += (inout left: Vector2D, right: Vector2D) { left = left + right } // 组合赋值
  • 自定义操作符可声明优先级和结合性:
    1
    2
    infix operator +- { associativity left precedence 140 }
    func +- (left: Vector2D, right: Vector2D) -> Vector2D { }

Command Line

  • demangle: xcrun swift-demangle xxx

Using with Cocoa & ObjC

  • 在.m文件中使用swift:#import "ProductModuleName-Swift.h"
  • 在.swift文件中使用objc:把要使用的objc头文件加入ProductModuleName-Bridging-Header.h中,之后所有.swift文件中不需要import就能使用
  • 从objc类继承的swift类会由编译器自动插入@objc,因而在objc中可用
  • 不从objc类继承的swift类要在objc中可用,需要自己添加@objc标注
  • @objc(<#name#>)可以给swift类/方法起objc别名(不带名称空间)
  • swift闭包和objc闭包互通,swift闭包中的变量类似objc闭包中的__block变量
  • @NSManaged就像@dynamic,表示NSManagedObject子类属性的存储和实现将在运行时提供

内存布局

  • struct按值顺序排,会字节对齐
  • swift类都继承自SwiftObject,SwiftObject实现了协议,但不是NSObject的子类
  • SwiftObject前两个8字节分别是isa和refCount (swift中的NSObject类空着这后8字节refCount不用)
  • swift中的NSObject布局:isa, superclass, cache, …, 构成vtable的各方法指针
  • class的optional,跟指针一样,指向地址或nil;struct的optional多加1字节表示值是否nil
  • protocol共占40字节;class的protocol布局为:isa, 空着不用16字节, 指向底层类的metadata的指针,指向实现protocol的vtable的指针;struct的protocol布局为:24字节内inline存储值(超过24字节保存malloc的地址8字节, 空着不用16字节),指向底层类的metadata的指针,指向实现protocol的vtable的指针

Playground和.swift文件

  • main.swift和playground一样都是order-dependent的,且允许顶层代码
  • iOS里加@UIApplicationMain的那个文件就相当于main.swift
  • 其他swift文件都是order-independent的,且不允许顶层代码

Reference

  • [《The Swift Programming Language》][1]
  • 《Using Swift with Cocoa and Objective-C》
  • Intermediate Swift, WWDC2014
  • Exploring Swift Memory Layout [part I][] & [part II][] [1]: http://wiki.jikexueyuan.com/project/swift/ [part I]: https://www.mikeash.com/pyblog/friday-qa-2014-07-18-exploring-swift-memory-layout.html [part II]: https://www.mikeash.com/pyblog/friday-qa-2014-08-01-exploring-swift-memory-layout-part-ii.html