Swift运行时
- 在swift中如果我们要定义一个表示错误的类型非常简单,只要遵循Error协议就可以了,我们通常用枚举或结构体来表示错误类型,枚举可能用的多些,因为它能更直观的表达当前错误类型的每种错误细节。
如何抛出错误
- 函数、方法和初始化器都可以抛出错误。需要在参数列表后面,返回值前面加throws关键字
使用Do-Catch做错误处理
- 在Swift中我们使用do-catch块对错误进行捕获,当我们调用一个throws声明的函数或方法时,我们必须把调用语句放在do语句块中,同时do语句块后面紧接着使用catch语句块。
try?
- try?会将错误转换为可选值,当调用try?+函数或方法语句时候,如果函数或方法抛出错误,程序不会发生崩溃,而返回一个nil,如果没有抛出错误则返回可选值
try!
- 如果你确信一个函数或者方法不会抛出错误,可以使用try!来中断错误的传播。但是如果错误真的发生了,你会得到一个运行时错误。
Swift
let photo = try! loadImage(atPath:"./Resources/John Appleseed.jpg")
指定退出的清理动作
- defer关键字:defer block里的代码会在函数return之前执行,无论函数是从哪个分支return的,还是有throw,还是自动而然走到最后一行(类似于ts的finally)
权限控制(模块和源文件)
- 模块指的是独立的代码分发单元,框架或应用程序会作为一个独立的模块来构建和发布。在Swift中,一个模块可以使用import关键字导入另外一个模块
- 源文件就是Swift中的源代码文件,它通常属于一个模块,即一个应用程序或者框架。尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。
访问级别
潜规则1
- 如果一个类的访问级别是fileprivate或private那么该类的所有成员都是fileprivate或private(此时成员无法修改访问级别),如果一个类的访问级别是open、internal或者public那么它的所有成员都是internal,类成员的访问级别不能高于类的访问级别(注意:嵌套类型的访问级别也符合此条规则)
潜规则2
- 常量、变量、属性、下标脚本访问级别低于其所声明的类型级别,并且如果不是默认访问级别(internal)要明确声明访问级别(例如一个常量是一个private类型的类类型,那么此常量必须声明为private或fileprivate)
潜规则3
- 在不违反1、2两条潜规则的情况下,setter的访问级别可以低于getter的访问级别(例如一个属性访问级别是internal),那么可以添加private(set)修饰符将setter权限设置为private,在当前模块中只有此源文件可以访问,对外部是只读的)
潜规则4
- 必要构造方法(required修改)的访问级别必须和类访问级别相同,结构体的默认逐一构造函数的访问级别不高于其成员的访问级别(例如一个成员时private那么这个构造函数就是private,但是可以通过自定义来声明一个public的构造函数),其他方法(包括其他构造方法和普通方法)的访问级别遵循潜规则1
不透明类型(why)
- 代码是可以编译通过的,但是makeTrapezoid的返回类型有凑有偿,被暴露了出去
- 不能将其Container用作函数的返回类型,因为该协议具有关联类型。也不能将它用作返回类型的泛型约束,因为函数体外没有足够的信息来推断泛型类型需要什么
解决问题
返回不透明类型vs返回协议类型
- 返回opaque类型看起来非常类似于使用协议类型作为函数的返回类型,但这两种返回类型的不同之处在于它们是否保留了类型标识。opaque类型是
指一种特定类型
,尽管函数的调用者不能看到是哪种类型;协议类型可以指代符合协议的任何类型。一般来说,协议类型为存储值的基础类型提供了更大的灵活性,而不透明类型可以对这些基础类型做出更强有力的保证。
ARC
- Swift使用自动引用计数(ARC)来跟踪并管理应用使用的内存。大部分情况下,这意味着在Swift语言中,内存管理"仍然工作",不需要自己去考虑内存管理的事情。当实例不再被使用时,ARC会自动释放这些类的实例所占用的内存。
- 引用计数只应用在类的实例。结构体(Structure)和枚举类型是值类型,并非引用类型,不是以引用的方式来存储和传递的
ARC如何工作
循环引用
- 在两个类实例彼此保持对方的强引用,使得每个实例都使对方保持有效时会发生这种情况。我们称之为强引用环。
- 通过用弱引用或者无主引用来取代强引用,我们可以解决强引用环问题
解决循环引用
- 弱引用和无主引用允许引用环中的一个实例引用另外一个实例,但不是强引用。因此实例可以互相引用但是不会产生强引用环。
- 对于生命周期中引用会变为nil的实例,使用弱引用;对于初始化时赋值之后引用再也不会赋值为nil的实例,使用无主引用
弱引用
- 弱引用不会增加实例的引用计数,因此不会阻止ARC销毁引用的实例。这种特性使得引用不会变成强引用环。声明属性或者变量的时候,关键字weak表明引用弱引用。
- 弱引用只能声明为变量类型,因为运行时它的值可能改变。弱引用绝对不能声明为常量。
- 因为弱引用可以没有值,所以声明弱引用的时候必须是可选类型的。在Swift语言中,推荐用可选类型来作为可能没有值的引用的类型。
无主引用
- 和弱引用相似,无主引用也不强持有实例。但是和弱引用不同的是,无主引用默认始终有值。因此,无主引用定义为非可选类型(non-optional-type).在属性、变量前添加unowned关键字,可以声明一个无主引用。
- 因为是非可选类型,因此当使用无助引用的时候,不需要展开,可以直接访问。不过非可选类型变量不能赋值为nil,因此当实例被销毁的时候,ARC无法将引用赋值为nil。
- 当实例被销毁后,试图访问该实例的无主引用会触发运行时错误。使用无主引用时请确保引用始终指向一个未销毁的实例。
闭包引用循环
- 将一个闭包赋值给类实例的某个属性,并且这个闭包使用了实例,这样也会产生强引用环。这个闭包可能访问了实例的某个属性,例如self.someProperty,或者调用了实例的某个方法,例如self.someMethod。这两种情况导致了闭包使用self,从而产生了循环引用。
闭包引用循环解决
- 定义占有列表-占有列表中的每个元素都是由weak或者unowned关键字和实例的引用(如self或someInstance)组成。每一对都在中括号中,通过逗号分开。
- 当闭包和占有的实例总是互相引用时并且总是同时销毁时,将闭包内的占有定义为无主引用。
- 相反的,当占有引用有时可能会是nil时,将闭包内的占有定义为弱引用。
内存安全
- 默认情况下,Swift会克服代码层面上的一些不安全的行为,如:确保一个变量被初始化完后才能被访问、确保变量在销毁后不会被访问等等安全操作。
- Swift也会确保在多路访问内存中同一区域时不会冲突(独占访问该区域)。通常情况下,我们完全无需考虑内存访问冲突的问题,因为Swift是自动管理内存的。然而,在码代码的时候,了解那些地方可能发生内存访问冲突是非常重要的。通常情况喜爱,如果你的代码有内存访问冲突,那么Xcode会提示编译错误或者运行时错误。
- 内存访问分为两种
- 即时访问:即在访问开始至结束前都不可能有其他代码来访问同一区域
- 长期访问:即在访问开始至结束前可能有其他代码来访问同一区域。长期访问可能和其他即时访问或者长期访问重叠
inout参数访问冲突
解决访问冲突问题:
self访问冲突
有问题
三方库
简介
使用cocoapods
- 网络请求:Alamofire
- JSON解析:SwiftJSON
- 资源管理:R.swift
- 社交分享:MonkeyKing
- 图片缓存与加载:Kingfisher
- 自动布局:SnapKit
- 标准款扩展:Dollar
网络请求:Alamofire
- Alamofire是在平果URL Loading System基础上封装的网络库,简单易用并且可扩展
- Alamofire
基本用法
- AF命名空间,链式调用
Swift
AF.request("https://time.geekbang.org").response { response in
debugPrint(response)
}
request方法详解
SwiftJSON
Dollar(是对标准库的扩展,类似于js的loadash)
Snapkit
- make初始化约束还调用
- update更新约束的使用
- remark有冲突约束的时候使用
图片加载和缓存-Kingfisher(类似SDWebImage)
R.Swift
R.Swift之前
R.Swift的使用
MonkeyKing
OC和Swift运行时简介
Objective-C运行时
- 动态类型(dynamic typing)
- 动态绑定(dynamic binding)
- 动态加载(dynamic loading)
派发方式
- 直接派发(Direct Dispatch)
- 函数表派发(Table Dispatch)
- 消息机制派发(Message Dispatch)
直接派发
- 直接派发是最快的,不止是因为需要调用的指令集会更少,并且编译器还能够有很大的优化空间,例如函数内联等,直接派发也有人称为静态调用
- 然而,对于编程来说直接调用也是最大的局限,而且因为缺乏动态性所以没办法支持继承和多继承
函数表派发
- 查表是一种简单,易实现,而且性能可预知的方式.然而,这种派发方式比起直接派发还是慢一点,从字节码角度来看,多了两次读和一次跳转,由此带来了性能的损耗,另一个慢的原因在于编译器可能会由于函数内执行的任务导致无法优化(如果函数带有副作用的话)
- 这种基于数组的实现,缺陷在于函数表无法拓展.子类会在虚数函数表的最后插入新的函数,没有位置可以让extesion安全地插入函数
消息机制派发
- 消息机制是调用函数最动态的方式.也是Cocoa的基石,这样的机制催生了KVO,UIAppearence和CoreData等功能.这种运作方式的关键在于开发者可以在运行时改变函数的行为.不止可以通过swizzling来改变,甚至可以用isa-swizzling修改对象的继承关系,可以在面向对象的基础上实现自定义派发
Swift运行时
- 纯Swift类的函数调用已经不再是Objective-c的运行时发消息,而是类似C++的vtable,在编译时就确定了调用哪个函数,所以没法通过runtime获取方法、属性。
- 而Swift为了兼容Objective-C,凡是继承自NSObject的类都会保留其动态性,所以我们能通过runtime拿到他的方法。这里有一点说明:老版本的Swift(如2.2)是编译期隐式的自动帮我加上了@objc,而4.0以后版本的Swift去掉了隐式特性,必须使用显式添加
- 不管是纯Swift类还是继承自NSObject的类只要在属性和方法前面添加@objc关键字就可以使用runtime
- 值类型总是会使用直接派发,简单易懂
- 而协议和类的extension都会使用直接派发
- NSObject的extension会使用消息机制进行派发
- NSObject声明作用域里的函数都会使用函数表进行派发
- 协议里声明的,并且带有默认实现的函数会使用函数表进行派发
Swift运行时-final @objc
- 可以在标记为final的同时,也是用@objc来让函数可以使用消息机制派发。这么做的结果就是,调用函数的时候会使用直接派发,但也会在Objective-C的运行时里注册响应的selector.函数可以响应perfrom(selector:)以及别的Objective-C特性,但在直接调用时又可以有直接派发的性能
Swift运行时
Swift与OC的桥接
Swift调用OC
OC调用Swift
NS_SWIFT_NAME
- 在Objective-C中,重新命名在swift中的名称
NS_SWIFT_UNAVAILABLE
- 在Swift中不可见,不能使用
- OC中可以调用Swift方法,要保证swift的类是继承自NSObject的
Subclass
- 对于自定义的类而言,Objective-C的类,不能继承自Swift的类,即要混编的OC类不能是Swift类的子类。反过来,需要混编的Swift类可以继承自OC的类。
宏
- 定义一个常量值,后面可以方便使用;如#define TOOLBAR_HEIGHT 44;
- 定义一个不变化的常用值,或者一个较长的对象属性;如#define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width)
- 定义一个会变化的常用变量值,或者一个较长的对象属性;如: #define STATUS_BAR_HEIGHT ([UIApplication sharedApplication].statusBarFrame.size.height)
- 定义一个带参数的宏,类似于一个函数;如#define RGB_COLOR(r,g,b) [UIColor colorWithRed:r/255.f gree:g/255.f blue:b/255.f alpha:1.0]
Swift独有特性
- Swift中有许多OC没有的特性,比如,Swift元组、为一等公民的函数、还有特有的枚举类型。所以,要使用的混编文件要注意Swift独有属性问题。
NS_REFINED_FOR_SWIFT
- Objective-C的API和Swift的风格相差比较大,Swift调用Objective-C的API时可能由于数据类型等不一致导致无法达到预期(比如,Objective-C里的方法采用了C语言风格的多参数类型;或者Objective-C方法返回NSNotFound,在Swift中期望返回nil)。这时候就要NS_REFINED_FOR_SWIFT