记录iOS开发中的一些知识点
1.常用的几个高阶函数
2.高阶函数扩展
3.优雅的判断多个值中是否包含某一个值
4.Hashable、Equatable和Comparable协议
5.可变参数函数
6.where关键字
7.switch中判断枚举类型,尽量避免使用default
8.iOS9之后全局动态修改StatusBar样式
9.使用面向协议实现app的主题功能
10.swift中多继承的实现
11.华丽的TableView刷新动效
12.实现一个不基于Runtime的KVO
13.实现多重代理
14.自动检查控制器是否被销毁
15.向控制器中注入代码
16.给Extension添加存储属性
17.用闭包实现按钮的链式点击事件
18.用闭包实现手势的链式监听事件
19.用闭包实现通知的监听事件
20.AppDelegate解耦
21.常见的编译器诊断指令
22.最后执行的defer代码块
23.定义全局常量
24.使用Codable协议解析JSON
25.dispatch_once替代方案
26.被废弃的+load()和+initialize()
27.交换方法 Method Swizzling
28.获取View的指定子视图
29.线程安全: 互斥锁和自旋锁(10种)
函数式编程在swift中有着广泛的应用,下面列出了几个常用的高阶函数.
常用来对数组进行排序.顺便感受下函数式编程的多种姿势.
let intArr = [13, 45, 27, 80, 22, 53]
let sortOneArr = intArr.sorted { (a: Int, b: Int) -> Bool in
return a < b
}
// [13, 22, 27, 45, 53, 80]
let sortTwoArr = intArr.sorted { (a: Int, b: Int) in
return a < b
}
// [13, 22, 27, 45, 53, 80]
let sortThreeArr = intArr.sorted { (a, b) in
return a < b
}
// [13, 22, 27, 45, 53, 80]
let sortFourArr = intArr.sorted {
return $0 < $1
}
// [13, 22, 27, 45, 53, 80]
let sortFiveArr = intArr.sorted {
$0 < $1
}
// [13, 22, 27, 45, 53, 80]
let sortSixArr = intArr.sorted(by: <)
// [13, 22, 27, 45, 53, 80]
let mapArr = intArr.map { $0 * $0 }
// [169, 2025, 729, 6400, 484, 2809]
let optionalArr = [nil, 4, 12, 7, Optional(3), 9]
let compactMapArr = optionalArr.compactMap { $0 }
// [4, 12, 7, 3, 9]
let evenArr = intArr.filter { $0 % 2 == 0 }
// [80, 22]
// 组合成一个字符串
let stringArr = ["1", "2", "3", "*", "a"]
let allStr = stringArr.reduce("") { $0 + $1 }
// 123*a
// 求和
let sum = intArr.reduce(0) { $0 + $1 }
// 240
let chainArr = [4, 3, 5, 8, 6, 2, 4, 7]
let resultArr = chainArr.filter {
$0 % 2 == 0
}.map {
$0 * $0
}.reduce(0) {
$0 + $1
}
// 136
extension Sequence {
// 可以将一些公共功能注释为@inlinable,给编译器提供优化跨模块边界的泛型代码的选项
@inlinable
public func customMap<T>(
_ transform: (Element) throws -> T
) rethrows -> [T] {
let initialCapacity = underestimatedCount
var result = ContiguousArray<T>()
// 因为知道当前元素个数,所以一次性为数组申请完内存,避免重复申请
result.reserveCapacity(initialCapacity)
// 获取所有元素
var iterator = self.makeIterator()
// 将元素通过参数函数处理后添加到数组中
for _ in 0..<initialCapacity {
result.append(try transform(iterator.next()!))
}
// 如果还有剩下的元素,添加进去
while let element = iterator.next() {
result.append(try transform(element))
}
return Array(result)
}
}
map的实现无非就是创建一个空数组,通过for循环遍历将每个元素通过传入的函数处理后添加到空数组中,只不过swift的实现更加高效一点.
关于其余相关高阶函数的实现:Sequence.swift
class Pet {
let type: String
let age: Int
init(type: String, age: Int) {
self.type = type
self.age = age
}
}
var pets = [
Pet(type: "dog", age: 5),
Pet(type: "cat", age: 3),
Pet(type: "sheep", age: 1),
Pet(type: "pig", age: 2),
Pet(type: "cat", age: 3),
]
pets.forEach { p in
print(p.type)
}
let cc = pets.contains { $0.type == "cat" }
let firstIndex = pets.firstIndex { $0.age == 3 }
// 1
let lastIndex = pets.lastIndex { $0.age == 3 }
// 4
let sortArr = pets.sorted { $0.age < $1.age }
let arr1 = pets.prefix { $0.age > 3 }
// [{type "dog", age 5}]
let arr2 = pets.drop { $0.age > 3 }
// [{type "cat", age 3}, {type "sheep", age 1}, {type "pig", age 2}, {type "cat", age 3}]
let line = "BLANCHE: I don't want realism. I want magic!"
let wordArr = line.split(whereSeparator: { $0 == " " })
// ["BLANCHE:", "I", "don\'t", "want", "realism.", "I", "want", "magic!"]
我们最常用的方式
let string = "One"
if string == "One" || string == "Two" || string == "Three" {
print("One")
}
这种方式是可以,但可阅读性不够,那有啥好的方式呢?
if ["One", "Two", "Three"].contains(where: { $0 == "One"}) {
print("One")
}
if string == any(of: "One", "Two", "Three") {
print("One")
}
func any<T: Equatable>(of values: T...) -> EquatableValueSequence<T> {
return EquatableValueSequence(values: values)
}
struct EquatableValueSequence<T: Equatable> {
static func ==(lhs: EquatableValueSequence<T>, rhs: T) -> Bool {
return lhs.values.contains(rhs)
}
static func ==(lhs: T, rhs: EquatableValueSequence<T>) -> Bool {
return rhs == lhs
}
fileprivate let values: [T]
}
这样做的前提是any中传入的值需要实现Equatable
协议.
实现Hashable协议的方法后我们可以根据hashValue
方法来获取该对象的哈希值.
字典中的value的存储就是根据key的hashValue
,所以所有字典中的key都要实现Hashable协议.
class Animal: Hashable {
var hashValue: Int {
return self.type.hashValue ^ self.age.hashValue
}
let type: String
let age: Int
init(type: String, age: Int) {
self.type = type
self.age = age
}
}
let a1 = Animal(type: "Cat", age: 3)
a1.hashValue
// 哈希值
实现Equatable协议后,就可以用==
符号来判断两个对象是否相等了.
class Animal: Equatable, Hashable {
static func == (lhs: Animal, rhs: Animal) -> Bool {
if lhs.type == rhs.type && lhs.age == rhs.age{
return true
}else {
return false
}
}
let type: String
let age: Int
init(type: String, age: Int) {
self.type = type
self.age = age
}
}
let a1 = Animal(type: "Cat", age: 3)
let a2 = Animal(type: "Cat", age: 4)
a1 == a2
// false
基于Equatable基础上的Comparable类型,实现相关的方法后可以使用<
、<=
、>=
、>
等符号进行比较.
class Animal: Comparable {
// 只根据年龄选项判断
static func < (lhs: Animal, rhs: Animal) -> Bool {
if lhs.age < rhs.age{
return true
}else {
return false
}
}
let type: String
let age: Int
init(type: String, age: Int) {
self.type = type
self.age = age
}
}
let a1 = Animal(type: "Cat", age: 3)
let a2 = Animal(type: "Cat", age: 4)
let a3 = Animal(type: "Cat", age: 1)
let a4 = Animal(type: "Cat", age: 6)
// 按照年龄从大到小排序
let sortedAnimals = [a1, a2, a3, a4].sorted(by: <)
在日常开发中会涉及到大量对自定义对象的比较操作,所以Comparable
协议的用途还是比较广泛的.
Comparable
协议除了应用在类上,还可以用在结构体和枚举上.
// 常用的姿势
[2, 3, 4, 5, 6, 7, 8, 9].reduce(0) { $0 + $1 }
// 44
// 使用可变参数函数
sum(values: 2, 3, 4, 5, 6, 7, 8, 9)
// 44
// 可变参数的类型是个数组
func sum(values:Int...) -> Int {
var result = 0
values.forEach({ a in
result += a
})
return result
}
应用:
// 给UIView添加子控件
let view = UIView()
let label = UILabel()
let button = UIButton()
view.add(view, label, button)
extension UIView {
/// 同时添加多个子控件
///
/// - Parameter subviews: 单个或多个子控件
func add(_ subviews: UIView...) {
subviews.forEach(addSubview)
}
}
where的主要作用是用来做限定.
// 只遍历数组中的偶数
let arr = [11, 12, 13, 14, 15, 16, 17, 18]
for num in arr where num % 2 == 0 {
// 12 14 16 18
}
enum ExceptionError:Error{
case httpCode(Int)
}
func throwError() throws {
throw ExceptionError.httpCode(500)
}
do{
try throwError()
// 通过where添加限定条件
}catch ExceptionError.httpCode(let httpCode) where httpCode >= 500{
print("server error")
}catch {
print("other error")
}
let student:(name:String, score:Int) = ("小明", 59)
switch student {
case let (_,score) where score < 60:
print("不及格")
default:
print("及格")
}
//第一种写法
func genericFunctionA<S>(str:S) where S:ExpressibleByStringLiteral{
print(str)
}
//第二种写法
func genericFunctionB<S:ExpressibleByStringLiteral>(str:S){
print(str)
}
// 为Numeric在Sequence中添加一个求和扩展方法
extension Sequence where Element: Numeric {
var sum: Element {
var result: Element = 0
for item in self {
result += item
}
return result
}
}
print([1,2,3,4].sum) // 10
let names = ["Joan", "John", "Jack"]
let firstJname = names.first(where: { (name) -> Bool in
return name.first == "J"
})
// "Joan"
let fruits = ["Banana", "Apple", "Kiwi"]
let containsBanana = fruits.contains(where: { (fruit) in
return fruit == "Banana"
})
// true
参考: Swift where 关键字
通过switch
语句来判断枚举类型,不使用default
,如果后期添加新的枚举类型,而忘记在switch
中处理,会报错,这样可以提高代码的健壮性.
enum State {
case loggedIn
case loggedOut
case startUI
}
func handle(_ state: State) {
switch state {
case .loggedIn:
showMainUI()
case .loggedOut:
showLoginUI()
// Compiler error: Switch must be exhaustive
}
}
最常用的方法是通过控制器来修改StatusBar
样式
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
注意:如果当前控制器有导航控制器,需要在导航控制器中这样设置(如下代码),不然不起作用.
override var preferredStatusBarStyle: UIStatusBarStyle {
return topViewController?.preferredStatusBarStyle ?? .default
}
这样做的好处是,可以针对不同的控制器设置不同的StatusBar
样式,但有时往往会多此一举,略嫌麻烦,那如何全局统一处理呢?
iOS9之前的做法比较简单,在plist
文件中设置View controller-based status bar appearance
为NO
.
在需要设置的地方添加
UIApplication.shared.setStatusBarStyle(.default, animated: true)
这样全局设置StatusBar
样式就可以了,但iOS9之后setStatusBarStyle
方法被废弃了,苹果推荐使用preferredStatusBarStyle
,也就是上面那种方法.
我们可以用UIAppearance
和导航栏的barStyle
去全局设置StatusBar
的样式.
-
UIAppearance
属性可以做到全局修改样式. -
导航栏的
barStyle
决定了NavigationBar
的外观,而barStyle
属性改变会联动到StatusBar
的样式.- 当
barStyle = .default
,表示导航栏的为默认样式,StatusBar
的样式为了和导航栏区分,就会变成黑色. - 当
barStyle = .black
,表示导航栏的颜色为深黑色,StatusBar
的样式为了和导航栏区分,就会变成白色.
这个有点绕,总之就是
StatusBar
的样式和导航栏的样式反着来. - 当
具体实现:
@IBAction func segmentedControl(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
// StatusBar为黑色,导航栏颜色为白色
UINavigationBar.appearance().barStyle = .default
UINavigationBar.appearance().barTintColor = UIColor.white
default:
// StatusBar为白色,导航栏颜色为深色
UINavigationBar.appearance().barStyle = .black
UINavigationBar.appearance().barTintColor = UIColor.darkNight
}
// 刷新window下的子控件
UIApplication.shared.windows.forEach {
$0.reload()
}
}
public extension UIWindow {
func reload() {
subviews.forEach { view in
view.removeFromSuperview()
addSubview(view)
}
}
}
做为修改全局样式的UIAppearance
用起来还是很方便的,比如要修改所有UILabel
的文字颜色.
UILabel.appearance().textColor = labelColor
又或者我们只想修改某个CustomView
层级下的子控件UILabel
UILabel.appearance(whenContainedInInstancesOf: [CustomView.self]).textColor = labelColor
定义好协议中需要实现的属性和方法
protocol Theme {
// 自定义的颜色
var tint: UIColor { get }
// 定义导航栏的样式,为了联动状态栏(具体见第9小点)
var barStyle: UIBarStyle { get }
var labelColor: UIColor { get }
var labelSelectedColor: UIColor { get }
var backgroundColor: UIColor { get }
var separatorColor: UIColor { get }
var selectedColor: UIColor { get }
// 设置主题样式
func apply(for application: UIApplication)
// 对特定主题样式进行扩展
func extend()
}
对协议添加extension
,这样做的好处是,如果有多个结构体或类实现了协议,而每个结构体或类需要实现相同的方法,这些方法就可以统一放到extension
中处理,大大提高了代码的复用率.
如果结构体或类有着相同的方法实现,那么结构体或类的实现会覆盖掉协议的extension
中的实现.
extension Theme {
func apply(for application: UIApplication) {
application.keyWindow?.tintColor = tint
UITabBar.appearance().with {
$0.barTintColor = tint
$0.tintColor = labelColor
}
UITabBarItem.appearance().with {
$0.setTitleTextAttributes([.foregroundColor : labelColor], for: .normal)
$0.setTitleTextAttributes([.foregroundColor : labelSelectedColor], for: .selected)
}
UINavigationBar.appearance().with {
$0.barStyle = barStyle
$0.tintColor = tint
$0.barTintColor = tint
$0.titleTextAttributes = [.foregroundColor : labelColor]
}
UITextView.appearance().with {
$0.backgroundColor = selectedColor
$0.tintColor = tint
$0.textColor = labelColor
}
extend()
application.windows.forEach { $0.reload() }
}
// ... 其余相关UIAppearance的设置
// 如果某些属性需要在某些主题下定制,可在遵守协议的类或结构体下重写
func extend() {
// 在主题中实现相关定制功能
}
}
Demo中白色主题的UISegmentedControl
需要设置特定的颜色,我们可以在LightTheme
的extension
中重写extend()
方法.
extension LightTheme {
// 需要自定义的部分写在这边
func extend() {
UISegmentedControl.appearance().with {
$0.tintColor = UIColor.darkText
$0.setTitleTextAttributes([.foregroundColor : labelColor], for: .normal)
$0.setTitleTextAttributes([.foregroundColor : UIColor.white], for: .selected)
}
UISlider.appearance().tintColor = UIColor.darkText
}
}
在设置完UIAppearance
后需要对所有的控件进行刷新,这个操作放在apply
方法中.具体实现
public extension UIWindow {
/// 刷新所有子控件
func reload() {
subviews.forEach { view in
view.removeFromSuperview()
addSubview(view)
}
}
}
swift本身并不支持多继承,但我们可以根据已有的API去实现.
swift中的类可以遵守多个协议,但是只可以继承一个类,而值类型(结构体和枚举)只能遵守单个或多个协议,不能做继承操作.
多继承的实现:协议的方法可以在该协议的extension
中实现
protocol Behavior {
func run()
}
extension Behavior {
func run() {
print("Running...")
}
}
struct Dog: Behavior {}
let myDog = Dog()
myDog.run() // Running...
无论是结构体还是类还是枚举都可以遵守多个协议,所以多继承就这么做到了.
// MARK: - 闪烁功能
protocol Blinkable {
func blink()
}
extension Blinkable where Self: UIView {
func blink() {
alpha = 1
UIView.animate(
withDuration: 0.5,
delay: 0.25,
options: [.repeat, .autoreverse],
animations: {
self.alpha = 0
})
}
}
// MARK: - 放大和缩小
protocol Scalable {
func scale()
}
extension Scalable where Self: UIView {
func scale() {
transform = .identity
UIView.animate(
withDuration: 0.5,
delay: 0.25,
options: [.repeat, .autoreverse],
animations: {
self.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
})
}
}
// MARK: - 添加圆角
protocol CornersRoundable {
func roundCorners()
}
extension CornersRoundable where Self: UIView {
func roundCorners() {
layer.cornerRadius = bounds.width * 0.1
layer.masksToBounds = true
}
}
extension UIView: Scalable, Blinkable, CornersRoundable {}
cyanView.blink()
cyanView.scale()
cyanView.roundCorners()
请看下面代码
protocol ProtocolA {
func method()
}
extension ProtocolA {
func method() {
print("Method from ProtocolA")
}
}
protocol ProtocolB {
func method()
}
extension ProtocolB {
func method() {
print("Method from ProtocolB")
}
}
class MyClass: ProtocolA, ProtocolB {}
此时ProtocolA
和ProtocolB
都有一个默认的实现方法method()
,由于编译器不知道继承过来的method()
方法是哪个,就会报错.
💎钻石问题,当某一个类或值类型在继承图谱中有多条路径时就会发生.
解决方法:
1. 在目标值类型或类中重写那个发生冲突的方法method()
.
2. 直接修改协议中重复的方法
相对来时第二种方法会好一点,所以多继承要注意,尽量避免多继承的协议中的方法的重复.
先看效果(由于这个页面的内容有点多,我尽量不放加载比较耗时的文件)
我们都知道TableView
的刷新动效是设置在tableView(_:,willDisplay:,forRowAt:)
这个方法中的.
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.alpha = 0
UIView.animate(
withDuration: 0.5,
delay: 0.05 * Double(indexPath.row),
animations: {
cell.alpha = 1
})
}
这样一个简单的淡入效果就OK了.但这样做显然不够优雅,我们如果要在多个TableView
使用这个效果该怎样封装呢?
// Animation接收三个参数
typealias Animation = (UITableViewCell, IndexPath, UITableView) -> Void
final class Animator {
private var hasAnimatedAllCells = false
private let animation: Animation
init(animation: @escaping Animation) {
self.animation = animation
}
func animate(cell: UITableViewCell, at indexPath: IndexPath, in tableView: UITableView) {
guard !hasAnimatedAllCells else {
return
}
animation(cell, indexPath, tableView)
// 确保每个cell动画只执行一次
hasAnimatedAllCells = tableView.isLastVisibleCell(at: indexPath)
}
}
enum AnimationFactory {
static func makeFade(duration: TimeInterval, delayFactor: Double) -> Animation {
return { cell, indexPath, _ in
cell.alpha = 0
UIView.animate(
withDuration: duration,
delay: delayFactor * Double(indexPath.row),
animations: {
cell.alpha = 1
})
}
}
// ...
}
将所有的动画设置封装在Animation
的闭包中.
最后我们就可以在tableView(_:,willDisplay:,forRowAt:)
这个方法中使用了
let animation = AnimationFactory.makeFade(duration: 0.5, delayFactor: 0.05)
let animator = TableViewAnimator(animation: animation)
animator.animate(cell: cell, at: indexPath, in: tableView)
动画相关的可以参考我之前写的文章 猛击
实现效果
示例Demo
Swift并没有在语言层级上支持KVO,如果要使用必须导入Foundation
框架, 被观察对象必须继承自NSObject
,这种实现方式显然不够优雅.
KVO本质上还是通过拿到属性的set方法去搞事情,基于这样的原理我们可以自己去实现.
话不多说,直接贴代码,新建一个Observable
文件
public class Observable<Type> {
// MARK: - Callback
fileprivate class Callback {
fileprivate weak var observer: AnyObject?
fileprivate let options: [ObservableOptions]
fileprivate let closure: (Type, ObservableOptions) -> Void
fileprivate init(
observer: AnyObject,
options: [ObservableOptions],
closure: @escaping (Type, ObservableOptions) -> Void) {
self.observer = observer
self.options = options
self.closure = closure
}
}
// MARK: - Properties
public var value: Type {
didSet {
removeNilObserverCallbacks()
notifyCallbacks(value: oldValue, option: .old)
notifyCallbacks(value: value, option: .new)
}
}
private func removeNilObserverCallbacks() {
callbacks = callbacks.filter { $0.observer != nil }
}
private func notifyCallbacks(value: Type, option: ObservableOptions) {
let callbacksToNotify = callbacks.filter { $0.options.contains(option) }
callbacksToNotify.forEach { $0.closure(value, option) }
}
// MARK: - Object Lifecycle
public init(_ value: Type) {
self.value = value
}
// MARK: - Managing Observers
private var callbacks: [Callback] = []
/// 添加观察者
///
/// - Parameters:
/// - observer: 观察者
/// - removeIfExists: 如果观察者存在需要移除
/// - options: 被观察者
/// - closure: 回调
public func addObserver(
_ observer: AnyObject,
removeIfExists: Bool = true,
options: [ObservableOptions] = [.new],
closure: @escaping (Type, ObservableOptions) -> Void) {
if removeIfExists {
removeObserver(observer)
}
let callback = Callback(observer: observer, options: options, closure: closure)
callbacks.append(callback)
if options.contains(.initial) {
closure(value, .initial)
}
}
public func removeObserver(_ observer: AnyObject) {
callbacks = callbacks.filter { $0.observer !== observer }
}
}
// MARK: - ObservableOptions
public struct ObservableOptions: OptionSet {
public static let initial = ObservableOptions(rawValue: 1 << 0)
public static let old = ObservableOptions(rawValue: 1 << 1)
public static let new = ObservableOptions(rawValue: 1 << 2)
public var rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
}
使用起来和KVO差不多.
需要监听的类
public class User {
// 监听的属性需要是Observable类型
public let name: Observable<String>
public init(name: String) {
self.name = Observable(name)
}
}
使用
// 创建对象
let user = User(name: "Made")
// 设置监听
user.name.addObserver(self, options: [.new]) { name, change in
print("name:\(name), change:\(change)")
}
// 修改对象的属性
user.name.value = "Amel" // 这时就可以被监听到
// 移除监听
user.name.removeObserver(self)
注意: 在使用过程中,如果改变value, addObserver方法不调用,很有可能是Observer对象已经被释放掉了.
作为iOS开发中最常用的设计模式之一Delegate
,只能是一对一的关系,如果要一对多,就只能使用NSNotification
了,但我们可以有更好的解决方案,多重代理.
protocol MasterOrderDelegate: class {
func toEat(_ food: String)
}
这边用了NSHashTable
来存储遵守协议的类,NSHashTable
和NSSet
类似,但又有所不同,总的来说有这几个特点:
1. NSHashTable
中的元素可以通过Hashable
协议来判断是否相等.
2. NSHashTable
中的元素如果是弱引用,对象销毁后会被移除,可以避免循环引用.
class masterOrderDelegateManager : MasterOrderDelegate {
private let multiDelegate: NSHashTable<AnyObject> = NSHashTable.weakObjects()
init(_ delegates: [MasterOrderDelegate]) {
delegates.forEach(multiDelegate.add)
}
// 协议中的方法,可以有多个
func toEat(_ food: String) {
invoke { $0.toEat(food) }
}
// 添加遵守协议的类
func add(_ delegate: MasterOrderDelegate) {
multiDelegate.add(delegate)
}
// 删除指定遵守协议的类
func remove(_ delegateToRemove: MasterOrderDelegate) {
invoke {
if $0 === delegateToRemove as AnyObject {
multiDelegate.remove($0)
}
}
}
// 删除所有遵守协议的类
func removeAll() {
multiDelegate.removeAllObjects()
}
// 遍历所有遵守协议的类
private func invoke(_ invocation: (MasterOrderDelegate) -> Void) {
for delegate in multiDelegate.allObjects.reversed() {
invocation(delegate as! MasterOrderDelegate)
}
}
}
class Master {
weak var delegate: MasterOrderDelegate?
func orderToEat() {
delegate?.toEat("meat")
}
}
class Dog {
}
extension Dog: MasterOrderDelegate {
func toEat(_ food: String) {
print("\(type(of: self)) is eating \(food)")
}
}
class Cat {
}
extension Cat: MasterOrderDelegate {
func toEat(_ food: String) {
print("\(type(of: self)) is eating \(food)")
}
}
let cat = Cat()
let dog = Dog()
let cat1 = Cat()
let master = Master()
// master的delegate是弱引用,所以不能直接赋值
let delegate = masterOrderDelegateManager([cat, dog])
// 添加遵守该协议的类
delegate.add(cat1)
// 删除遵守该协议的类
delegate.remove(dog)
master.delegate = delegate
master.orderToEat()
// 输出
// Cat is eating meat
// Cat is eating meat
- IM消息接收之后在多个地方做回调,比如显示消息,改变小红点,显示消息数.
UISearchBar
的回调,当我们需要在多个地方获取数据的时候,类似的还有UINavigationController
的回调等.
检查内存泄漏除了使用Instruments
,还有查看控制器pop
或dismiss
后是否被销毁,后者相对来说更方便一点.但老是盯着析构函数deinit
看日志输出是否有点麻烦呢?
UIViewController
有提供两个不知名的属性:
isBeingDismissed
: 当modal出来的控制器被dismiss
后的值为true
.isMovingFromParent
: 在控制器的堆栈中,如果当前控制器从父控制器中移除,值会变成true
.
如果这两个属性都为true
,表明控制器马上要被销毁了,但这是由ARC去做内存管理,我们并不知道多久之后被销毁,简单起见就设个2秒吧.
extension UIViewController {
public func dch_checkDeallocation(afterDelay delay: TimeInterval = 2.0) {
let rootParentViewController = dch_rootParentViewController
if isMovingFromParent || rootParentViewController.isBeingDismissed {
let disappearanceSource: String = isMovingFromParent ? "removed from its parent" : "dismissed"
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: { [weak self] in
if let VC = self {
assert(self == nil, "\(VC.description) not deallocated after being \(disappearanceSource)")
}
})
}
}
private var dch_rootParentViewController: UIViewController {
var root = self
while let parent = root.parent {
root = parent
}
return root
}
}
我们把这个方法添加到viewDidDisappear(_:)
中
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
dch_checkDeallocation()
}
如果发生循环引用,控制就不会被销毁,会触发assert
报错.
使用场景: 在某些控制器的viewDidLoad
方法中,我们需要添加一段代码,用于统计某个页面的打开次数.
最常用的解决方案:
在父类或者extension
中定义一个方法,然后在需要做统计的控制器的viewDidLoad
方法中调用刚刚定义好的方法.
或者还可以使用代码注入.
ViewControllerInjector.inject(into: [ViewController.self], selector: #selector(UIViewController.viewDidLoad)) {
// $0 为ViewController对象
// 统计代码...
}
swift虽然是门静态语言,但依然支持OC的runtime
.可以允许我们在静态类型中使用动态代码.代码注入就是通过runtime
的交换方法实现的.
class ViewControllerInjector {
typealias methodRef = @convention(c)(UIViewController, Selector) -> Void
static func inject(into supportedClasses: [UIViewController.Type], selector: Selector, injection: @escaping (UIViewController) -> Void) {
guard let originalMethod = class_getInstanceMethod(UIViewController.self, selector) else {
fatalError("\(selector) must be implemented")
}
var originalIMP: IMP? = nil
let swizzledViewDidLoadBlock: @convention(block) (UIViewController) -> Void = { receiver in
if let originalIMP = originalIMP {
let castedIMP = unsafeBitCast(originalIMP, to: methodRef.self)
castedIMP(receiver, selector)
}
if ViewControllerInjector.canInject(to: receiver, supportedClasses: supportedClasses) {
injection(receiver)
}
}
let swizzledIMP = imp_implementationWithBlock(unsafeBitCast(swizzledViewDidLoadBlock, to: AnyObject.self))
originalIMP = method_setImplementation(originalMethod, swizzledIMP)
}
private static func canInject(to receiver: Any, supportedClasses: [UIViewController.Type]) -> Bool {
let supportedClassesIDs = supportedClasses.map { ObjectIdentifier($0) }
let receiverType = type(of: receiver)
return supportedClassesIDs.contains(ObjectIdentifier(receiverType))
}
}
代码注入可以在不修改原有代码的基础上自定义自己所要的.相比继承,代码的可重用性会高一点,侵入性会小一点.
我们都知道Extension
中可以添加计算属性,但不能添加存储属性.
对 我们可以使用runtime
private var nameKey: Void?
extension UIView {
// 给UIView添加一个name属性
var name: String? {
get {
return objc_getAssociatedObject(self, &nameKey) as? String
}
set {
objc_setAssociatedObject(self, &nameKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}
}
通常按钮的点击事件我们需要这样写:
btn.addTarget(self, action: #selector(actionTouch), for: .touchUpInside)
@objc func actionTouch() {
print("按钮点击事件")
}
如果有多个点击事件,往往还要写多个方法,写多了有没有觉得有点烦,代码阅读起来还要上下跳转.
private var actionDictKey: Void?
public typealias ButtonAction = (UIButton) -> ()
extension UIButton {
// MARK: - 属性
// 用于保存所有事件对应的闭包
private var actionDict: (Dictionary<String, ButtonAction>)? {
get {
return objc_getAssociatedObject(self, &actionDictKey) as? Dictionary<String, ButtonAction>
}
set {
objc_setAssociatedObject(self, &actionDictKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}
// MARK: - API
@discardableResult
public func addTouchUpInsideAction(_ action: @escaping ButtonAction) -> UIButton {
self.addButton(action: action, for: .touchUpInside)
return self
}
@discardableResult
public func addTouchUpOutsideAction(_ action: @escaping ButtonAction) -> UIButton {
self.addButton(action: action, for: .touchUpOutside)
return self
}
@discardableResult
public func addTouchDownAction(_ action: @escaping ButtonAction) -> UIButton {
self.addButton(action: action, for: .touchDown)
return self
}
// ...其余事件可以自己扩展
// MARK: - 私有方法
private func addButton(action: @escaping ButtonAction, for controlEvents: UIControl.Event) {
let eventKey = String(controlEvents.rawValue)
if var actionDict = self.actionDict {
actionDict.updateValue(action, forKey: eventKey)
self.actionDict = actionDict
}else {
self.actionDict = [eventKey: action]
}
switch controlEvents {
case .touchUpInside:
addTarget(self, action: #selector(touchUpInsideControlEvent), for: .touchUpInside)
case .touchUpOutside:
addTarget(self, action: #selector(touchUpOutsideControlEvent), for: .touchUpOutside)
case .touchDown:
addTarget(self, action: #selector(touchDownControlEvent), for: .touchDown)
default:
break
}
}
// 响应事件
@objc private func touchUpInsideControlEvent() {
executeControlEvent(.touchUpInside)
}
@objc private func touchUpOutsideControlEvent() {
executeControlEvent(.touchUpOutside)
}
@objc private func touchDownControlEvent() {
executeControlEvent(.touchDown)
}
@objc private func executeControlEvent(_ event: UIControl.Event) {
let eventKey = String(event.rawValue)
if let actionDict = self.actionDict, let action = actionDict[eventKey] {
action(self)
}
}
}
btn
.addTouchUpInsideAction { btn in
print("addTouchUpInsideAction")
}.addTouchUpOutsideAction { btn in
print("addTouchUpOutsideAction")
}.addTouchDownAction { btn in
print("addTouchDownAction")
}
利用runtime
在按钮的extension
中添加一个字典属性,key
对应的是事件类型,value
对应的是该事件类型所要执行的闭包.然后再添加按钮的监听事件,在响应方法中,根据事件类型找到并执行对应的闭包.
链式调用就是不断返回自身.
有没有觉得如果这样做代码写起来会简洁一点呢?
和tips17中的按钮点击事件类似,手势也可以封装成链式闭包回调.
view
.addTapGesture { tap in
print(tap)
}.addPinchGesture { pinch in
print(pinch)
}
public typealias GestureClosures = (UIGestureRecognizer) -> Void
private var gestureDictKey: Void?
extension UIView {
private enum GestureType: String {
case tapGesture
case pinchGesture
case rotationGesture
case swipeGesture
case panGesture
case longPressGesture
}
// MARK: - 属性
private var gestureDict: [String: GestureClosures]? {
get {
return objc_getAssociatedObject(self, &gestureDictKey) as? [String: GestureClosures]
}
set {
objc_setAssociatedObject(self, &gestureDictKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}
// MARK: - API
/// 点击
@discardableResult
public func addTapGesture(_ gesture: @escaping GestureClosures) -> UIView {
addGesture(gesture: gesture, for: .tapGesture)
return self
}
/// 捏合
@discardableResult
public func addPinchGesture(_ gesture: @escaping GestureClosures) -> UIView {
addGesture(gesture: gesture, for: .pinchGesture)
return self
}
// ...省略相关手势
// MARK: - 私有方法
private func addGesture(gesture: @escaping GestureClosures, for gestureType: GestureType) {
let gestureKey = String(gestureType.rawValue)
if var gestureDict = self.gestureDict {
gestureDict.updateValue(gesture, forKey: gestureKey)
self.gestureDict = gestureDict
} else {
self.gestureDict = [gestureKey: gesture]
}
isUserInteractionEnabled = true
switch gestureType {
case .tapGesture:
let tap = UITapGestureRecognizer(target: self, action: #selector(tapGestureAction(_:)))
addGestureRecognizer(tap)
case .pinchGesture:
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(pinchGestureAction(_:)))
addGestureRecognizer(pinch)
default:
break
}
}
@objc private func tapGestureAction (_ tap: UITapGestureRecognizer) {
executeGestureAction(.tapGesture, gesture: tap)
}
@objc private func pinchGestureAction (_ pinch: UIPinchGestureRecognizer) {
executeGestureAction(.pinchGesture, gesture: pinch)
}
private func executeGestureAction(_ gestureType: GestureType, gesture: UIGestureRecognizer) {
let gestureKey = String(gestureType.rawValue)
if let gestureDict = self.gestureDict, let gestureReg = gestureDict[gestureKey] {
gestureReg(gesture)
}
}
}
具体实现 猛击
// 通知监听
self.observerNotification(.notifyName1) { notify in
print(notify.userInfo)
}
// 发出通知
self.postNotification(.notifyName1, userInfo: ["infoKey": "info"])
// 移除通知
self.removeNotification(.notifyName1)
public typealias NotificationClosures = (Notification) -> Void
private var notificationActionKey: Void?
// 用于存放通知名称
public enum NotificationNameType: String {
case notifyName1
case notifyName2
}
extension NSObject {
private var notificationClosuresDict: [NSNotification.Name: NotificationClosures]? {
get {
return objc_getAssociatedObject(self, ¬ificationActionKey)
as? [NSNotification.Name: NotificationClosures]
}
set {
objc_setAssociatedObject(self, ¬ificationActionKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}
public func postNotification(_ name: NotificationNameType, userInfo: [AnyHashable: Any]?) {
NotificationCenter.default.post(name: NSNotification.Name(name.rawValue), object: self, userInfo: userInfo)
}
public func observerNotification(_ name: NotificationNameType, action: @escaping NotificationClosures) {
if var dict = notificationClosuresDict {
guard dict[NSNotification.Name(name.rawValue)] == nil else {
return
}
dict.updateValue(action, forKey: NSNotification.Name(name.rawValue))
self.notificationClosuresDict = dict
} else {
self.notificationClosuresDict = [NSNotification.Name(name.rawValue): action]
}
NotificationCenter.default.addObserver(self, selector: #selector(notificationAction),
name: NSNotification.Name(name.rawValue), object: nil)
}
public func removeNotification(_ name: NotificationNameType) {
NotificationCenter.default.removeObserver(self)
notificationClosuresDict?.removeValue(forKey: NSNotification.Name(name.rawValue))
}
@objc func notificationAction(notify: Notification) {
if let notificationClosures = notificationClosuresDict, let closures = notificationClosures[notify.name] {
closures(notify)
}
}
}
具体实现过程和tips17、tips18类似.
作为iOS整个项目的核心App delegate
,随着项目的逐渐变大,会变得越来越臃肿,一不小心代码就过了千行.
大型项目的App delegate
体积会大到什么程度呢?我们可以参考下国外2亿多月活的Telegram
的 App delegate.是不是吓一跳,4千多行.看到这样的代码是不是很想点击左上角的x.
是时候该给App delegate
解耦了,目标: 每个功能的配置或者初始化都分开,各自做各自的事情.App delegate
要做到只需要调用就好了.
命令模式: 发送方发送请求,然后接收方接受请求后执行,但发送方可能并不知道接受方是谁,执行的是什么操作,这样做的好处是发送方和接受方完全的松耦合,大大提高程序的灵活性.
protocol Command {
func execute()
}
struct InitializeThirdPartiesCommand: Command {
func execute() {
// 第三方库初始化代码
}
}
struct InitialViewControllerCommand: Command {
let keyWindow: UIWindow
func execute() {
// 根控制器设置代码
}
}
struct InitializeAppearanceCommand: Command {
func execute() {
// 全局外观样式配置
}
}
struct RegisterToRemoteNotificationsCommand: Command {
func execute() {
// 远程推送配置
}
}
final class StartupCommandsBuilder {
private var window: UIWindow!
func setKeyWindow(_ window: UIWindow) -> StartupCommandsBuilder {
self.window = window
return self
}
func build() -> [Command] {
return [
InitializeThirdPartiesCommand(),
InitialViewControllerCommand(keyWindow: window),
InitializeAppearanceCommand(),
RegisterToRemoteNotificationsCommand()
]
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
StartupCommandsBuilder()
.setKeyWindow(window!)
.build()
.forEach { $0.execute() }
return true
}
使用命令模式的好处是,如果要添加新的配置,设置完后只要加在StartupCommandsBuilder
中就可以了.App delegate
中不需要添加任何内容.
但这样做只能将didFinishLaunchingWithOptions
中的代码解耦,App delegate
中的其他方法怎样解耦呢?
组合模式: 可以将对象组合成树形结构来表现"整体/部分"层次结构. 组合后可以以一致的方法处理个别对象以及组合对象.
这边我们给App delegate
每个功能模块都设置一个子类,每个子类包含所有App delegate
的方法.
// 推送
class PushNotificationsAppDelegate: AppDelegateType {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
print("推送配置")
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("推送相关代码...")
}
// 其余方法
}
// 外观样式
class AppearanceAppDelegate: AppDelegateType {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
print("外观样式配置")
return true
}
}
// 控制器处理
class ViewControllerAppDelegate: AppDelegateType {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
print("根控制器设置代码")
return true
}
}
// 第三方库
class ThirdPartiesConfiguratorAppDelegate: AppDelegateType {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
print("第三方库初始化代码")
return true
}
func applicationDidEnterBackground(_ application: UIApplication) {
print("ThirdPartiesConfiguratorAppDelegate - applicationDidEnterBackground")
}
func applicationDidBecomeActive(_ application: UIApplication) {
print("ThirdPartiesConfiguratorAppDelegate - applicationDidBecomeActive")
}
}
typealias AppDelegateType = UIResponder & UIApplicationDelegate
class CompositeAppDelegate: AppDelegateType {
private let appDelegates: [AppDelegateType]
init(appDelegates: [AppDelegateType]) {
self.appDelegates = appDelegates
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
appDelegates.forEach { _ = $0.application?(application, didFinishLaunchingWithOptions: launchOptions) }
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
appDelegates.forEach { _ = $0.application?(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) }
}
func applicationDidEnterBackground(_ application: UIApplication) {
appDelegates.forEach { _ = $0.applicationDidEnterBackground?(application)
}
}
func applicationDidBecomeActive(_ application: UIApplication) {
appDelegates.forEach { _ = $0.applicationDidBecomeActive?(application)
}
}
}
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let appDelegate = AppDelegateFactory.makeDefault()
enum AppDelegateFactory {
static func makeDefault() -> AppDelegateType {
return CompositeAppDelegate(appDelegates: [
PushNotificationsAppDelegate(),
AppearanceAppDelegate(),
ThirdPartiesConfiguratorAppDelegate(),
ViewControllerAppDelegate(),
]
)
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
_ = appDelegate.application?(application, didFinishLaunchingWithOptions: launchOptions)
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
appDelegate.application?(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
}
func applicationDidBecomeActive(_ application: UIApplication) {
appDelegate.applicationDidBecomeActive?(application)
}
func applicationDidEnterBackground(_ application: UIApplication) {
appDelegate.applicationDidEnterBackground?(application)
}
}
App delegate
解耦相比命令模式,使用组合模式可自定义程度会更高一点.
下面列出了一些常见的编译器诊断指令:
swift4.2中添加了这两个命令,再也不用在项目中自己配置错误和警告的命令了.
警告
// Xcode会报一条黄色警告
#warning("此处逻辑有问题,明天再说")
// TODO
#warning("TODO: Update this code for the new iOS 12 APIs")
错误❌:
// 手动设置一条错误
#error("This framework requires UIKit!")
#if !canImport(UIKit)
#error("This framework requires UIKit!")
#endif
#if DEBUG
#warning("TODO: Update this code for the new iOS 12 APIs")
#endif
分别用于获取文件名,函数名称,当前所在行数,一般用于辅助日志输出.
自定义Log
public struct dc {
/// 自定义Log
///
/// - Parameters:
/// - message: 输出的内容
/// - file: 默认
/// - method: 默认
/// - line: 默认
public static func log<T>(_ message: T, file: NSString = #file, method: String = #function, line: Int = #line) {
#if DEBUG
print("\(file.pathComponents.last!):\(method)[\(line)]: \(message)")
#endif
}
}
一般用来判断当前代码块在某个版本及该版本以上是否可用.
if #available(iOS 8, *) {
// iOS 8 及其以上系统运行
}
guard #available(iOS 8, *) else {
return //iOS 8 以下系统就直接返回
}
@available(iOS 11.4, *)
func myMethod() {
// do something
}
defer
这个关键字不是很常用,但有时还是很有用的.
具体用法,简而言之就是,defer
代码块会在函数的return前执行.
func printStringNumbers() {
defer { print("1") }
defer { print("2") }
defer { print("3") }
print("4")
}
printStringNumbers() // 打印 4 3 2 1
下面列举几个常见的用途:
func foo() throws {
defer {
print("two")
}
do {
print("one")
throw NSError()
print("不会执行")
}
print("不会执行")
}
do {
try foo()
} catch {
print("three")
}
// 打印 one two three
defer
可在函数throw之后被执行,而如果将代码添加到throw NSError()
底部和do{}
底部都不会被执行.
func writeFile() {
let file: FileHandle? = FileHandle(forReadingAtPath: filepath)
defer { file?.closeFile() }
// 文件相关操作
}
这样一方面可读性好一点,另一方面不会因为某个地方throw了一个错误而没有关闭资源文件了.
func getData(completion: (_ result: Result<String>) -> Void) {
var result: Result<String>?
defer {
guard let result = result else {
fatalError("We should always end with a result")
}
completion(result)
}
// result的处理逻辑
}
defer
中可以做一些result的验证逻辑,这样不会和result的处理逻辑混淆,代码清晰.
作为整个项目中通用的全局常量为了方便管理最好集中定义在一个地方.
下面介绍几种全局常量定义的姿势:
public struct Screen {
static var width: CGFloat {
return UIScreen.main.bounds.size.width
}
static var height: CGFloat {
return UIScreen.main.bounds.size.height
}
static var statusBarHeight: CGFloat {
return UIApplication.shared.statusBarFrame.height
}
}
Screen.width // 屏幕宽度
Screen.height // 屏幕高度
Screen.statusBarHeight // statusBar高度
好处是能比较直观的看出全局常量的定义逻辑,方便后面扩展.
正常情况下的enum
都是与case
搭配使用,如果使用了case
就要实例化enum
.其实也可以不写case
.
public enum ConstantsEnum {
static let width: CGFloat = 100
static let height: CGFloat = 50
}
ConstantsEnum.width
let instance = ConstantsEnum()
// ERROR: 'ConstantsEnum' cannot be constructed because it has no accessible initializers
ConstantsEnum
不可以实例化,会报错.
相比struct
,使用枚举定义常量可以避免不经意间实例化对象.
使用extension
几乎可以为任何类型扩展常量.
例如,通知名称
public extension Notification.Name {
// 名称
static let customNotification = Notification.Name("customNotification")
}
NotificationCenter.default.post(name: .customNotification, object: nil)
增加自定义颜色
public extension UIColor {
class var myGolden: UIColor {
return UIColor(red: 1.000, green: 0.894, blue: 0.541, alpha: 0.900)
}
}
view.backgroundColor = .myGolden
增加double常量
public extension Double {
public static let kRectX = 30.0
public static let kRectY = 30.0
public static let kRectWidth = 30.0
public static let kRectHeight = 30.0
}
CGRect(x: .kRectX, y: .kRectY, width: .kRectWidth, height: .kRectHeight)
因为传入参数类型是确定的,我们可以把类型名省略,直接用点语法.
swift4.0推出的Codable
协议用来解析JSON还是挺不错的.
public protocol Decodable {
public init(from decoder: Decoder) throws
}
public protocol Encodable {
public func encode(to encoder: Encoder) throws
}
public typealias Codable = Decodable & Encodable
Codable
是Decodable
和Encodable
这两个协议的综合,只要遵守了Codable
协议,编译器就能帮我们实现好一些细节,然后就可以做编码和解码操作了.
public struct Pet: Codable {
var name: String
var age: Int
}
let json = """
[{
"name": "WangCai",
"age": 2,
},{
"name": "xiaoHei",
"age": 3,
}]
""".data(using: .utf8)!
// JSON -> 模型
let decoder = JSONDecoder()
do {
// 对于数组可以使用[Pet].self
let dogs = try decoder.decode([Pet].self, from: json)
print(dogs)
}catch {
print(error)
}
// 模型 -> JSON
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted // 美化样式
do {
let data = try encoder.encode(Pet(name: "XiaoHei", age: 3))
print(String(data: data, encoding: .utf8)!)
// {
// "name" : "XiaoHei",
// "age" : 3
// }
} catch {
print(error)
}
下面我们重写系统的方法.
init(name: String, age: Int) {
self.name = name
self.age = age
}
// decoding
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let name = try container.decode(String.self, forKey: .name)
let age = try container.decode(Int.self, forKey: .age)
self.init(name: name, age: age)
}
// encoding
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(age, forKey: .age)
}
enum CodingKeys: String, CodingKey {
case name
case age
}
对于编码和解码的过程,我们都是创建一个容器,该容器有一个keyedBy
的参数,用于指定属性和JSON中key两者间的映射的规则,因此我们传CodingKeys
的类型过去,说明我们要使用该规则来映射.对于解码的过程,我们使用该容器来进行解码,指定要值的类型和获取哪一个key的值,同样的,编码的过程中,我们使用该容器来指定要编码的值和该值对应json中的key.
当然了,现实开发中需要解析的JSON不会这么简单.
let json = """
{
"aircraft": {
"identification": "NA875",
"color": "Blue/White"
},
"route": ["KTTD", "KHIO"],
"departure_time": {
"proposed": 1540868946509,
"actual": 1540869946509,
},
"flight_rules": "IFR",
"remarks": null,
"price": "NaN",
}
""".data(using: .utf8)!
public struct Aircraft: Codable {
public var identification: String
public var color: String
}
public enum FlightRules: String, Codable {
case visual = "VFR"
case instrument = "IFR"
}
public struct FlightPlan: Codable {
// 嵌套模型
public var aircraft: Aircraft
// 包含数组
public var route: [String]
// 日期处理
private var departureTime: [String: Date]
public var proposedDepartureDate: Date? {
return departureTime["proposed"]
}
public var actualDepartureDate: Date? {
return departureTime["actual"]
}
// 枚举处理
public var flightRules: FlightRules
// 空值处理
public var remarks: String?
// 特殊值处理
public var price: Float
// 下划线key转驼峰命名
private enum CodingKeys: String, CodingKey {
case aircraft
case flightRules = "flight_rules"
case route
case departureTime = "departure_time"
case remarks
case price
}
}
let decoder = JSONDecoder()
// 解码时,日期格式是13位时间戳 .base64:通过base64解码
decoder.dateDecodingStrategy = .millisecondsSince1970
// 指定 infinity、-infinity、nan 三个特殊值的表示方式
decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "+∞", negativeInfinity: "-∞", nan: "NaN")
do {
let plan = try decoder.decode(FlightPlan.self, from: json)
plan.aircraft.color // Blue/White
plan.aircraft.identification // NA875
plan.route // ["KTTD", "KHIO"]
plan.proposedDepartureDate // 2018-10-30 03:09:06 +0000
plan.actualDepartureDate // 2018-10-30 03:25:46 +0000
plan.flightRules // instrument
plan.remarks // 可选类型 空
plan.price // nan
}catch {
print(error)
}
swift4.1中有个属性可以自动将key转化为驼峰命名:
decoder.keyDecodingStrategy = .convertFromSnakeCase
,如果CodingKeys
只是用来转成驼峰命名的话,设置好这个属性后就可以不用写CodingKeys
这个枚举了.
OC中用来保证代码块只执行一次的dispatch_once
在swfit中已经被废弃了,取而代之的是使用static let
,let
本身就带有线程安全性质的.
例如单例的实现.
final public class MySingleton {
static let shared = MySingleton()
private init() {}
}
但如果我们不想定义常量,需要某个代码块执行一次呢?
private lazy var takeOnceTime: Void = {
// 代码块...
}()
_ = takeOnceTime
定义一个懒加载的变量,防止在初始化的时候被执行.后面加一个void
,为了在_ = takeOnceTime
赋值时不耗性能,返回一个Void
类型.
lazy var
改为static let
也可以,为了使用方便,我们用一个类方法封装下
class ClassName {
private static let takeOnceTime: Void = {
// 代码块...
}()
static func takeOnceTimeFunc() {
ClassName.takeOnceTime
}
}
// 使用
ClassName.takeOnceTimeFunc()
这样就可以做到和dispatch_once
一样的效果了.
我们都知道OC中两个方法+load()
和+initialize()
.
+load()
: app启动的时候会加载所有的类,此时就会调用每个类的load方法.
+initialize()
: 第一次初始化这个类的时候会被调用.
然而在目前的swift版本中这两个方法都不可用了,那现在我们要在这个阶段搞事情该怎么做? 例如method swizzling
.
JORDAN SMITH大神给出了一种很巧解决方案.UIApplication
有一个next
属性,它会在applicationDidFinishLaunching
之前被调用,这个时候通过runtime
获取到所有类的列表,然后向所有遵循SelfAware协议的类发送消息.
extension UIApplication {
private static let runOnce: Void = {
NothingToSeeHere.harmlessFunction()
}()
override open var next: UIResponder? {
// Called before applicationDidFinishLaunching
UIApplication.runOnce
return super.next
}
}
protocol SelfAware: class {
static func awake()
}
class NothingToSeeHere {
static func harmlessFunction() {
let typeCount = Int(objc_getClassList(nil, 0))
let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount)
let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types)
objc_getClassList(autoreleasingTypes, Int32(typeCount))
for index in 0 ..< typeCount {
(types[index] as? SelfAware.Type)?.awake()
}
types.deallocate()
}
}
之后任何遵守SelfAware
协议实现的+awake()
方法在这个阶段都会被调用.
黑魔法Method Swizzling
在swift中实现的两个困难点
- swizzling 应该保证只会执行一次.
- swizzling 应该在加载所有类的时候调用.
分别在tips25
和tips26
中给出了解决方案.
下面给出了两个示例供参考:
protocol SelfAware: class {
static func awake()
static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector)
}
extension SelfAware {
static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
let originalMethod = class_getInstanceMethod(forClass, originalSelector)
let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector)
guard (originalMethod != nil && swizzledMethod != nil) else {
return
}
if class_addMethod(forClass, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!)) {
class_replaceMethod(forClass, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
} else {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
}
}
class NothingToSeeHere {
static func harmlessFunction() {
let typeCount = Int(objc_getClassList(nil, 0))
let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount)
let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types)
objc_getClassList(autoreleasingTypes, Int32(typeCount))
for index in 0 ..< typeCount {
(types[index] as? SelfAware.Type)?.awake()
}
types.deallocate()
}
}
extension UIApplication {
private static let runOnce: Void = {
NothingToSeeHere.harmlessFunction()
}()
override open var next: UIResponder? {
UIApplication.runOnce
return super.next
}
}
在SelfAware
的extension
中为swizzlingForClass
做了默认实现,相当于一层封装.
extension UIButton: SelfAware {
static func awake() {
UIButton.takeOnceTime
}
private static let takeOnceTime: Void = {
let originalSelector = #selector(sendAction)
let swizzledSelector = #selector(xxx_sendAction(action:to:forEvent:))
swizzlingForClass(UIButton.self, originalSelector: originalSelector, swizzledSelector: swizzledSelector)
}()
@objc public func xxx_sendAction(action: Selector, to: AnyObject!, forEvent: UIEvent!) {
struct xxx_buttonTapCounter {
static var count: Int = 0
}
xxx_buttonTapCounter.count += 1
print(xxx_buttonTapCounter.count)
xxx_sendAction(action: action, to: to, forEvent: forEvent)
}
}
extension UIViewController: SelfAware {
static func awake() {
swizzleMethod
}
private static let swizzleMethod: Void = {
let originalSelector = #selector(viewWillAppear(_:))
let swizzledSelector = #selector(swizzled_viewWillAppear(_:))
swizzlingForClass(UIViewController.self, originalSelector: originalSelector, swizzledSelector: swizzledSelector)
}()
@objc func swizzled_viewWillAppear(_ animated: Bool) {
swizzled_viewWillAppear(animated)
print("swizzled_viewWillAppear")
}
}
通过递归获取指定view
的所有子视图.
使用
let subViewArr = view.getAllSubViews() // 获取所有子视图
let imageViewArr = view.getSubView(name: "UIImageView") // 获取指定类名的子视图
实现
extension UIView {
private static var getAllsubviews: [UIView] = []
public func getSubView(name: String) -> [UIView] {
let viewArr = viewArray(root: self)
UIView.getAllsubviews = []
return viewArr.filter {$0.className == name}
}
public func getAllSubViews() -> [UIView] {
UIView.getAllsubviews = []
return viewArray(root: self)
}
private func viewArray(root: UIView) -> [UIView] {
for view in root.subviews {
if view.isKind(of: UIView.self) {
UIView.getAllsubviews.append(view)
}
_ = viewArray(root: view)
}
return UIView.getAllsubviews
}
}
extension NSObject {
var className: String {
let name = type(of: self).description()
if name.contains(".") {
return name.components(separatedBy: ".")[1]
} else {
return name
}
}
}
UIAlertController
好用,但可自定义程度不高,例如我们想让message
文字左对齐,就需要获取到messageLabel
,但UIAlertController
并没有提供这个属性.
我们就可以通过递归拿到alertTitleLabel
和alertMessageLabel
.
extension UIAlertController {
public var alertTitleLabel: UILabel? {
return self.view.getSubView(name: "UILabel").first as? UILabel
}
public var alertMessageLabel: UILabel? {
return self.view.getSubView(name: "UILabel").last as? UILabel
}
}
虽然通过这种方法可以拿到alertTitleLabel
和alertMessageLabel
.但没法区分哪个是哪个,alertTitleLabel
为默认子控件的第一个label
,如果title
传空,message
传值,alertTitleLabel
和alertMessageLabel
获取到的都是message
的label
.
如果有更好的方法欢迎讨论.
无并发,不编程.提到多线程就很难绕开锁🔐.
iOS开发中较常见的两类锁:
自旋锁较适用于锁的持有者保存时间较短的情况下,实际使用中互斥锁会用的多一些.
四种锁分别是:
NSLock
、NSConditionLock
、NSRecursiveLock
、NSCondition
NSLocking
协议
public protocol NSLocking {
public func lock()
public func unlock()
}
下面举个多个售票点同时卖票的例子
var ticket = 20
var lock = NSLock()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let thread1 = Thread(target: self, selector: #selector(saleTickets), object: nil)
thread1.name = "售票点A"
thread1.start()
let thread2 = Thread(target: self, selector: #selector(saleTickets), object: nil)
thread2.name = "售票点B"
thread2.start()
}
@objc private func saleTickets() {
while true {
lock.lock()
Thread.sleep(forTimeInterval: 0.5) // 模拟延迟
if ticket > 0 {
ticket = ticket - 1
print("\(String(describing: Thread.current.name!)) 卖出了一张票,当前还剩\(ticket)张票")
lock.unlock()
}else {
print("oh 票已经卖完了")
lock.unlock()
break;
}
}
}
遵守协议后实现的两个方法lock()
和unlock()
,意如其名.
除此之外NSLock
、NSConditionLock
、NSRecursiveLock
、NSCondition
四种互斥锁各有其实现:
// 尝试去锁,如果成功,返回true,否则返回false
open func `try`() -> Bool
// 在limit时间之前获得锁,没有返回NO
open func lock(before limit: Date) -> Bool
// 当前线程挂起
open func wait()
// 当前线程挂起,设置一个唤醒时间
open func wait(until limit: Date) -> Bool
// 唤醒在等待的线程
open func signal()
// 唤醒所有NSCondition挂起的线程
open func broadcast()
当调用wait()
之后,NSCondition
实例会解锁已有锁的当前线程,然后再使线程休眠,当被signal()
通知后,线程被唤醒,然后再给当前线程加锁,所以看起来好像wait()
一直持有该锁,但根据苹果文档中说明,直接把wait()
当线程锁并不能保证线程安全.
NSConditionLock
是借助NSCondition
来实现的,在NSCondition
的基础上加了限定条件,可自定义程度相对NSCondition
会高些.
// 锁的时候还需要满足condition
open func lock(whenCondition condition: Int)
// 同try,同样需要满足condition
open func tryLock(whenCondition condition: Int) -> Bool
// 同unlock,需要满足condition
open func unlock(withCondition condition: Int)
// 同lock,需要满足condition和在limit时间之前
open func lock(whenCondition condition: Int, before limit: Date) -> Bool
定义了可以多次给相同线程上锁并不会造成死锁的锁.
提供的几个方法和NSLock
类似.
DispatchSemaphore
中的信号量,可以解决资源抢占的问题,支持信号的通知和等待.每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1,如果信号量为0则信号会处于等待状态.直到信号量大于0开始执行.所以我们一般将DispatchSemaphore
的value设置为1.
下面给出了DispatchSemaphore
的封装类
class GCDSemaphore {
// MARK: 变量
fileprivate var dispatchSemaphore: DispatchSemaphore!
// MARK: 初始化
public init() {
dispatchSemaphore = DispatchSemaphore(value: 0)
}
public init(withValue: Int) {
dispatchSemaphore = DispatchSemaphore(value: withValue)
}
// 执行
public func signal() -> Bool {
return dispatchSemaphore.signal() != 0
}
public func wait() {
_ = dispatchSemaphore.wait(timeout: DispatchTime.distantFuture)
}
public func wait(timeoutNanoseconds: DispatchTimeInterval) -> Bool {
if dispatchSemaphore.wait(timeout: DispatchTime.now() + timeoutNanoseconds) == DispatchTimeoutResult.success {
return true
} else {
return false
}
}
}
栅栏函数也可以做线程同步,当然了这个肯定是要并行队列中才能起作用.只有当当前的并行队列执行完毕,才会执行栅栏队列.
/// 创建并发队列
let queue = DispatchQueue(label: "queuename", attributes: .concurrent)
/// 异步函数
queue.async {
for _ in 1...5 {
print(Thread.current)
}
}
queue.async {
for _ in 1...5 {
print(Thread.current)
}
}
/// 栅栏函数
queue.async(flags: .barrier) {
print("barrier")
}
queue.async {
for _ in 1...5 {
print(Thread.current)
}
}
pthread
表示POSIX thread
,跨平台的线程相关的API,pthread_mutex
也是一种互斥锁,互斥锁的实现原理与信号量非常相似,阻塞线程并睡眠,需要进行上下文切换.
一般情况下,一个线程只能申请一次锁,也只能在获得锁的情况下才能释放锁,多次申请锁或释放未获得的锁都会导致崩溃.假设在已经获得锁的情况下再次申请锁,线程会因为等待锁的释放而进入睡眠状态,因此就不可能再释放锁,从而导致死锁.
这边给出了一个基于pthread_mutex_t
(安全的"FIFO"互斥锁)的封装 MutexLock
日常开发中最常用的应该是@synchronized,这个关键字可以用来修饰一个变量,并为其自动加上和解除互斥锁.这样,可以保证变量在作用范围内不会被其他线程改变.但是在swift中它已经不存在了.其实@synchronized在幕后做的事情是调用了objc_sync
中的objc_sync_enter
和objc_sync_exit
方法,并且加入了一些异常判断.
因此我们可以利用闭包自己封装一套.
func synchronized(lock: AnyObject, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
// 使用
synchronized(lock: AnyObject) {
// 此处AnyObject不会被其他线程改变
}
OSSpinLock
是执行效率最高的锁,不过在iOS10.0以后已经被废弃了.
详见大神ibireme的不再安全的 OSSpinLock
它能够保证不同优先级的线程申请锁的时候不会发生优先级反转问题.这是苹果为了取代OSSPinLock
新出的一个能够避免优先级带来的死锁问题的一个锁,OSSPinLock
就是有由于优先级造成死锁的问题.
注意: 这个锁适用于小场景下的一个高效锁,否则会大量消耗cpu资源.
var unsafeMutex = os_unfair_lock()
os_unfair_lock_lock(&unsafeMutex)
os_unfair_lock_trylock(&unsafeMutex)
os_unfair_lock_unlock(&unsafeMutex)
这边给出了基于os_unfair_lock
的封装 MutexLock
这边贴一张大神ibireme在iPhone6、iOS9对各种锁的性能测试图