需求: 从 plist 文件中读取数据并解码成模型对象使用, 模型定义协议如下:
protocol WhatsNewProtocol {
var icon: UIImage { get }
var title: String { get }
var shortDescription: String { get }
var fullDescription: String { get }
var video: Any { get }
}
protocol WorkspaceReleaseProtocol {
var version: String { get }
var date: Date { get }
var includes: [WhatsNewProtocol] { get }
}
protocol WhatsNewManagerProtocol {
func getWorkspaceReleases -> [WorkspaceReleaseProtocol]
}
Swift 中不支持协议嵌套, 因此使用关联类型实现.
同时注意到 WhatsNewProtocol 中定义的 icon 是 UIImage 类型.
import UIKit
protocol WhatsNewProtocol {
var icon: UIImage? { mutating get }
var video: Any? { get }
var tittle: String { get }
var shortDescription: String { get }
var fullDescription: String { get }
}
protocol WorkspaceReleaseProtocol {
associatedtype WhatsNew: WhatsNewProtocol
var version: String { get }
var date: Date { get }
var includes: [WhatsNew] { get }
}
protocol WhatsNewManagerProtocol {
associatedtype WorkspaceRelease: WorkspaceReleaseProtocol
func getWorkspaceReleases() -> [WorkspaceRelease]
}
1 使用关联类型表示协议中使用的需要遵守其他协议的对象;
2 因为 UIImage 的加载需要耗费一定的内存资源, 因此提供可以更改的 get 方法;
import UIKit
struct WhatsNewItem: WhatsNewProtocol, Codable {
lazy var icon: UIImage? = {
return UIImage(named: iconUrl)
}()
var video: Any? {
return videoUrl as Any
}
var iconUrl: String
var videoUrl: String
var tittle: String
var shortDescription: String
var fullDescription: String
enum CodingKeys: CodingKey {
case iconUrl
case videoUrl
case tittle
case shortDescription
case fullDescription
}
}
struct WorkspaceReleaseItem: WorkspaceReleaseProtocol, Codable {
typealias WhatsNew = WhatsNewItem
var version: String
var date: Date
var includes: [WhatsNewItem]
}
在 struct WhatsNewItem 中提供对 icon 的懒加载属性, 尽量减少内存占用时间. 因为可能以后支持从模型写入 plist 文件, 因此提供支持的 iconUrl 写入到 plist 中, 提供自定义的 CodingKeys 枚举.
因为对 WhatsNew 数据的操作 WhatsNewManager 是一个单例, 需要考虑线程安全, 此时使用一个自定义 DispatchQueue 队列让所有操作同步分发.
class WhatsNewManager: WhatsNewManagerProtocol {
typealias WorkspaceRelease = WorkspaceReleaseItem
private let queue = DispatchQueue.init(label: "WhatsNewManager")
func getWorkspaceReleases() -> [WorkspaceReleaseItem] {
var releaseItems: [WorkspaceReleaseItem]? = [WorkspaceReleaseItem]()
queue.sync {
guard let filePath = Bundle.main.path(forResource: "WhatsNew", ofType: "plist"),
let versionsData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else { return }
releaseItems = try? PropertyListDecoder().decode([WorkspaceReleaseItem].self, from: versionsData)
}
return releaseItems ?? []
}
}
1 将任务同步分发到自定义的串行队列中是否会造成死锁? 不会, 因为分发任务时是在主线程, 同时执行的线程也是在主线程.
2 在主线程中将任务同步分发到 main 队列, 是否造成死锁? 是. 区别在于是否阻塞当前线程.
3 将任务异步分发到自定义串行队列中: 创建一个新的线程, 数据返回时机不确定.
4 将 queue.sync {} return []; 换成 queue.async {} return []; 有什么不同: 异步分发时, 任务可能没有结束, 函数就返回了.
5 queue.async {}; print("test") 顺序: 不确定;
开发要注意线程安全!!!