跳转至

Swift 4 语言笔记

快速开始

  • 版本判断
#if swift(>=3.2)
    print(">=3.2")
#else
    print("<3")
#endif
  • 常量let声明: 显式/隐式,类型转换必须显式
  • 变量var声明

  • 字符串格式化模板:"I have \(apples + oranges) pieces of fruit."

  • 多行字符串使用"""

let quotation = """
I am joker.

And She is a girl.

We are family.
"""

print(quotation)
  • 数组和字符允许最后一个元素带,号,如果类型信息能推断,可能使用[][:]来定义空数组和字典,否则使用初始化器: [String]()[String:String]()

  • ifswitchfor-inwhilerepeat-while的条件部分可以不带括号,但执行体必须用花括号包住

  • 正常类型后面跟个?号就是Optional类型,使用if let local = optional {}是使用optional类型的一种常用方式, local变量只在if - let执行体内部有效

  • ??是给Optional类型变量取默认值的方法

  • switch支持各种类型, case默认不需要加break,使用fallthrough可以漏到下一个case

let v = "raff"
switch v {
case "red":
    print("red")
case let x where x.hasPrefix("r"):
    print("r prefix")
    fallthrough
case let "r":
    print("fallthrough")
default:
    print("default")
}
  • 字典枚举时是无序的: for (key, value) in dict {}

  • 循环:

    var n = 2
    while n < 100 {
        n *= 2
    }
    print(n)
    

  • 最少执行一次的循环

    var m = 2
    repeat {
        m *= 2
    }while m < 100
    print(m)
    

  • ..<...可以生成一个序列,右边界不同

  • 定义函数func, ->指明返回值类型, 默认参数标签和参数名相同

    func greet(person: String, day: String) -> String {
        return "Hello, \(person), today is \(day)."
    }
    greet(person: "joker", day: "Thursday")
    

  • 可以使用tuple来返回多值,可根据位置索引取tuple各元素值,也可以命名可元素标签

  • 函数可以嵌套,可访问外层变量

  • 闭包{ 参数及返回会 in 体 }$0,闭包的第一个参数, 闭包作为函数的最后一个参数时可以放在最后,即trailing closure

  • class声明类,:表示继承,initdeinit初始化和反初始化器

  • override 覆盖父类方法必须是显式指定

  • getter和setter, newValue

    var Name: Type {
    
        get {
    
        }
    
        willSet {
    
        }
        set(newValue) {
    
        }
    }
    

  • 链式操作中经常使用?符号

  • enum定义枚举,rawValue可以指定,不仅限定于Int

  • struct定义结构体,和类的主要区别是结构体是值类型

  • protocol定义协议,类、结构体和枚举都可以使用协议,mutating修饰结构体和枚举中的协议方法实现,可以修改值类型成员

  • 可以使用扩展extension来使用协议,可以把协议名作为一种类型来调用所有遵循该协议类型对象,只限于协议定义的内容

  • throw可以抛出一个异常,throws指明一个函数可以抛出异常,使用do-try[?]-catch...来处理异常, try?把结果转化为Optional类型,抛出错误时返回nil, 否则返回包含结果的Optional

  • defer描述的代码块会在函数返回前一刻执行,不论函数执行是否抛出错误

  • 泛型<TypeName:TypeProtocol> where TypeRequirement在执行体前面使用:<T: Equatable><T> ... where T: Equatable等价

语言细节

  • keyword使用swift关键字同名的名字命名变量

  • //单行注释

  • /* */多行注释, 可以嵌套多行注释

  • 句尾分号可以省略

  • 0b0o0x表示二进制、八进制、十六进制

  • 指数表示:1e3 = 10 * 10 * 101p2 = 2 * 2

  • 易读数字格式:1_000_000 = 1000000

  • typealias NewTypeName = existedTypeName类型别名定义

  • 元组(name: ele1, ele2, _),元组可以通过下标或元素来访问单个元素,不适合复杂数据结构

  • Optionals类型的变量可以有值也可以没有值,统一可以表示各种类型变量没有值的情况

  • nil不能给非optionals变量赋值,OC中的nil是指向一个不存在对象的指针,只对对象类型有效,而swift中的nil不是一个指针,它只是表示一种不存在值的状态,对所有类型有效。

  • !强制解包,如果一个Optional变更确定有值, 就可以使用!来使用其值

  • Optionals 绑定,用于从Optional变量中提取值并赋于局部变量使用,在单个if语句中可以使用多个Optional绑定,只要有一个为假,则整个判断为假

  • 隐式解绑Optional!,确保它声明后一直有值且不为nil, 但它本身还是一个Optional,只是在访问时会自动解包

  • 函数能抛出错误用throws指明,当调用一个可以抛出错误的函数时,前面要加上try

  • Assertions只在调试期间起作用,precondition可调试和生产环境中都起作用。Assertions在单元测试中经常用到,如果编译器开了-Ounchecked选项,precondition会被一直认为成功。

assert(exp, msg) 失败时显示msg,成功时继续执行

assertionFailure(msg)直接表示失败时的msg显示

precondition(exp, msg)调试和生产环境都起作用
preconditionFailure(:file:line)
fatalError(_:file:line:)严重错误,不会被编译器优化
  • swfit的=赋值不会有返回值,数值默认不允许溢出,但可以明确指定可以溢出

  • 一元运算符紧挨运算子,没有空格:-a, b!,二元运算:a + b,三元运算:a ? b : c

  • 赋值运算的右边如果是元组,可以自动解析为多个元素赋给左边的变量

  • %运算的原理:a = (b x some multiplier ) + remainder, 不考虑b的符号

  • 混合赋值运算也不会返回值,这一点和赋值一致

  • 全等运算===,不全等!==,用来判断两个引用是否指向同一个对象

  • 如果两个元组有相同的类型和元素个数,它们也可以进行比较,从左到右依次比较,直到发现两个元素不相等时停止比较后面的元素,只有所有元素都支持同一比较运算时才可以比较两个元组,最高能比较有六个元素的无组,再多元素的比较需要自己实现

  • 三元运算符不要嵌套使用,使程序更加可读

  • a ?? b,如果a有值就返回,如果a为nil,则返回b

  • 闭区间a...b,半开区间:a..<b 单端区间在数组上的应用[a...]用在数组索引,表示从a开始及其后的所有元素,还有[...a],表示从头开始到a索引

  • """多行字符串中也可以使用\来转义换行符来续行,在闭合的"""前面的空格指名多行字符要忽略的缩进

  • String是值类型

  • 字符串是字符的集合,可以被枚举,元素的类型是Character

  • \()字符串插入可以用于多行字符串

  • \u{n}1~8位的Unicode字符标量

  • Character每一个实例都是一个扩展图像簇,每一个扩展图像簇都可以包含多个Unicode标量,用来生成单个可读字符,可以用来显示一些组合字符,使用count发生获取字符串长度,也是由于这个原因,swift的字符串中的字符不能使用索引来直接引用,endIndex不是一个有效的字符索引,String的indices是所有字符元素的索引数组

  • 子字符串类型Substring, 只是临时使用

  • Characterutf8utf16unicodeScalars三种Unicode兼容的编码数组

  • 集合类型:ArraySetDictionary

  • Array(repeating:count:)可以在定义时初始化,

  • enumerated()方法会返回以(index, emelemt)为元素的数组

  • 集合中元素的类型必须是能hash的,以确保唯一性,集合没有简短写法,声明集合必须显式指明类型,不能类型推断

  • switch中的case 后面可以跟where

  • 控制转移关键字: continuebreakfallthroughreturnthrow

  • 语句标签label:可以和breakcontinue一起使用,可以实现与C语言中goto相同的效果

  • guard - else如果guard的条件不成立,就执行else块儿的语句

  • #available(iOS 10, macOS 10.12, *)API有效性判断

  • 函数有参数标签参数名,默认参数标签和参数名一样,也可以分别指定,可以使用_忽略参数标签

  • 可变参数类型Double...,一个函数最多只能有一个可变参数,可变参数要放在参数列表的最后

  • 函数的参数默认是常量,inout修改参数可以在函数体内修改参数值,只能把变量传给inout类型的参数,并且传参时要在前面加一个&号,以表示这个参数可以被修改

  • 函数就是命名的闭包,嵌套函数可以捕获外层的变量,可以推断变量,单表达式闭包默认会返回值,还有Trailing Closure语法

  • 闭包的语法: {(parameters) -> return type in statements }, 闭包中参数的简写:$0

  • 如果闭包被赋给类的成员变量,并且闭包本身又引用了类的成员,就会造成循环引用,Swfit使用捕获列表来解决这种循环引用问题

  • 闭包是引用类型

  • escaping闭包:当一个闭包被当作参数传入一个函数,却在该函数返回后被调用。关键字@escaping

  • 自动闭包:自动创建来包裹表达式并传给其它函数,关键字:@autoclosure

  • 枚举可以有结合值和默认值(rawValue),

  • 递归枚举:有另一个枚举作为case结合值的枚举类型,使用indirect表明它是一个递归枚举

  • class是引用类型,使用===!==来判断两个引用是否指向同一个实例

  • swift中一些基本类型都是使用struct来实现的,像StringArrayDictionary等,不像Foundation中的实现是使用类

  • Structclass可以声明存储属性,但是StructClassEnumeration都可以声明计算属性

  • 储存属性可以定义属性观察器

  • 当一个值类型的实例被声明为常量,它的所有成员都不可变,class的实例如果被声明为常量,它的成员还是可以改变,因为它是引用类型

  • lazy属性,只在第一次使用时创建, 只能使用var声明,因为常量在初始化完成时必须有值, 而lazy变量可能在初始化完成时没有被创建, 多线程同时调用lazy变量时,如果它还没有被创建,则它不能保证只被创建一次

  • 计算属性也必须使用var来声明,属性观察器willSet(newValue)didSet(newValue/oldValue)

  • 全局常量和变量都是计算懒惰的,只是不需要lazy修饰

  • 使用static关键字定义类型属性,使用class修饰的计算属性可以被子类重新实现

  • StructEnumeration是值类型,默认不能从这们的实例方法修改属性 ,如果修改修改,需要在实例方法前面加mutaing关键字,甚至可以修改self为另一个实例

  • 类型方法使用static关键词放在函数前面定义,使用class定义的类型方法允许子类覆盖其实现

  • @discardableResult属性修改指时时数调用时可以忽略考虑返回值

  • 类、结构体、枚举都可以定义下标操作,使用关键字subscript定义

subscript(index: Int) -> {
    get {
        // return an appropriate subscript value here
    }

    set(newValue){
        // perform a suitable setting action here
    }
}
  • Dictionry的元素赋nil相当于删除这个键值对

  • subscript不能使用inout修改参数,也不能给参数提供默认值

  • 继承过来的属性不管是存储还是计算类型的都可以添加属性观察器

  • 使用关键字final来阻止属性、类型、下标或方法被子类覆盖或继承

  • 结构体或类型在实例创建时一定要把所有的存储属性都初始化,可以在初始化器里设置,也可以在属性定义时设置默认值,这两种方式都是直接设置属性值,不会调用属性观察器

  • 定制初始化器:没有函数名,使用关键字init指定,可以有参数标签和参数名称

  • 如果属性在初始化时没有值或者之后可能没有值,这样的属性应该定义为Optional

  • 初始化时可以赋值常量成员,类和结构体默认会提供初始化器

  • 初始化器可以调用其它初始化器来完成初始化过程,这种调用叫作初始化代理,值类型和Class类型的初始化代理方式有些区别

  • 值类型不支持继承,它们的初始化代理过程比较简单,只使用自身提供的初始化器作代理

  • 如果要自定义初始化器,在值类型中就不会自动生成的初始化器了需要自己显式的写出来,如果要保留自动生成的初始化器,自定义的初始化器要写在扩展里面

  • 所以类的存储属性包括继承而来的都必须在初始化赋予初值

  • class定义两类初始化器:指定初始化器和便利初始化器,用来确保所有的存储属性都能被初始化

  • 指定初始化器是一个类的主要初始化器,它负责初始化本类的所有属性并调用合适的父类指定初始化器初始化父类

  • 每个类至少要有一个指定初始化器,便利初始化器通过调用同类的指定初始化器来作一些初始化实例的工作,像它的名字一样,便利初始化器只是为了方便初始化流程而设计的

  • 便利初始化器使用convenience关键词标示,指定初始化器不用

  • 初始化代理的过程有三个规则:

    • 子类指定初始化器必须调用父类指定初始化器(纵向)
    • 便利初始化器必须调用同类的初始化器(横向)
    • 便利初始化器最终需要调用同类的指定初始化器完成初始化工作
  • 两阶段初始化,保证属性在初始化前不被访问。第一阶段:类先把自己的属性初始化 第二阶段:按需赋值初始化过的属性

  • 初始化安全检查:

    • 在指定初始化器调用父类指定初始化器之前,要确保本类的所有存储属性都先被初始化
    • 修改父类属性值前必须先调用父类的指定初始化器,否则新赋的值就会在父类指定初始化器被调用后而改变
    • 便利初始化器在改变属性值前要调用另一个初始化器
    • 在第一阶段初始化完成前,初始化器不可以调用实例方法或读任意属性值或引用self
  • 初始化阶段1:

    1. 初始化器被调用
    2. 实例内存被分配,但还没有初始化
    3. 本类的指定初始化器调用来初始化所有的本类存储变量
    4. 父类的指定初始化器被调用来初始化所有父类的存储变量,直到所有的父类存储变量都被初始化为止
    5. 这时所有的存储变量都被初始化,第一阶段完成
  • 初始化阶段2:从最高父类向下的过程中就可以进一步配置属性值了,这一阶段实例已经可以被正常使用了

  • failable初始化器init?标识,return nil指明初始化失败

  • rawValue的枚举类型会自动生成failable初始化器

  • init!创建自动解包的初始化器

  • required修饰的初始化器子类必须实现

  • 可以使用闭包来初始化一个变量

    var a = { return 0 }()
    

  • 反初始化deinit只作用于class,一个类最多只有一个deinit {}, 可以用来释放资源

  • Optional链式调用?.

  • trytry?try!三种错误处理的区别

    • try!抑制错误
    • try?如果发生错误时返回nil
    • try
  • defer块的代码会和正常的代码执行顺序不同,写在第一名的最后执行

  • isas判断类型和类型转换,as?尝试类型转换,as!强制类型转换,

  • Any可以表示所有类型的实例,甚至是函数类型

  • AnyObject可以表示任何类实例

  • 扩展可以给类、结构体、枚举和协议添加功能:

    • 可以添加计算实例属性和计算类属性
    • 可以定义实例方法和类方法
    • 提供新初始化器
    • 定义下标
    • 定义嵌套类型
    • 遵循协议
    • 不能覆盖已存在函数
    • 不能添加存储属性,不能添加属性观察器
    • 只能添加便利初始化器,不能添加指定初始化器
    • 不能添加deinit
  • 协议如果继承了AnyObject协议就只能由Class类型继承它

  • 组合类型protocol & antoherProtocol表示任何同时遵循两个协议的类型

  • isasas?,可以用来检查协议遵循

  • 协议部分前有optional修饰的被遵循后可以不必实现,@objc修饰的协议部分只能被继承自Objective-C的类遵循

  • 协议扩展可以定义默认实现,可以使用where定义约束

  • 泛型类型也可以添加约束

  • 协议可以定义associatedtype类型,使用时可以typealias associatedType = Int,也可以给附加类型加约束

  • weakunowned修改变量或属性可以用来解决循环引用问题,weak修改的一定是optional的变量,因为它会在运行时变为nil unowned修改的变量一定要有值,它不会是nil, unowned(unsafe)需要自己确保运行时安全

  • 闭包引起的循环引用可以使用捕获列表(capture list)解决:[weak self, unowned delegate]放在闭我的参数列表前面

  • 重叠访问可能会造成不一致问题,通常与inoutmutating有关,造成访问冲突,如果编译器不能保证安全访问,就不会允许访问

  • swift访问控制模型基于模块和源文件,模块是代码分发单位,可以是框架或者应用,每一个target都是一个模块

  • openpublic可以在同一个模块的任何源文件中访问,也可以在被导入的另一个模块的源文件中被访问,open的范围比public更大点,包括了被导入的另一个模块的范围

  • internel只能在本模块中的源文件中被访问

  • fileprivate只在文件内部可被访问

  • private只在一个声明的范围内部访问

-@testable修饰导入声明时,单元测试就可以访问模块的内部实体了

  • swfit默认运算不允许溢出,&+&-&*显式的指定允许溢出

  • 自定义运算和运算符:

prefix|infix|postfix operator +++: precedence

static prefix|infix|postfix func 
  • indirect可以用来定义递归枚举

  • ~=是枚举中表达式匹配时使用的运算符

语言参考

  • ->表示由后面的构成

  • |表示或者

  • opt下标,表示可有可无