144 lines
6.2 KiB
Swift
144 lines
6.2 KiB
Swift
//
|
||
// MP_CacheManager.swift
|
||
// MusicPlayer
|
||
//
|
||
// Created by Mr.Zhou on 2024/5/23.
|
||
//
|
||
|
||
import Foundation
|
||
///缓存实体资源
|
||
class CachedMedia: NSObject, Codable {
|
||
//数据
|
||
let data: Data
|
||
//数据块
|
||
let dataBlocks:[MediaDataBlock]
|
||
//是否完整
|
||
let isComplete: Bool
|
||
//最大长度
|
||
let maxCount:Int64
|
||
init(data: Data, dataBlocks:[MediaDataBlock], isComplete: Bool, maxCount:Int64) {
|
||
self.data = data
|
||
self.dataBlocks = dataBlocks
|
||
self.isComplete = isComplete
|
||
self.maxCount = maxCount
|
||
}
|
||
}
|
||
///缓存管理工具
|
||
class MP_CacheManager {
|
||
// 单例模式,提供全局访问点
|
||
static let shared = MP_CacheManager()
|
||
// 缓存实体,字典形式,键值1为videoID,键值2为对应的资源数据
|
||
private let memoryCache = NSCache<NSString, CachedMedia>()
|
||
//文件管理器
|
||
private let fileManager = FileManager.default
|
||
//缓存文件地址
|
||
private let cacheDirectory: URL
|
||
//缓存专用线程
|
||
private var cacheQueue = DispatchQueue(label: "com.MP_CacheManager.cacheQueue")
|
||
//缓存专用
|
||
private var cacheOperations: [String: DispatchWorkItem] = [:]
|
||
//专用节流时间间隔
|
||
private let throttleInterval: TimeInterval = 1.0
|
||
//固定缓存时间
|
||
private let expirationInterval: TimeInterval = 86400 // 24小时
|
||
private init() {
|
||
//获取全部文件地址
|
||
let urls = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)
|
||
//设置缓存文件地址
|
||
cacheDirectory = urls[0].appendingPathComponent("MyAssetCacheManager")
|
||
//检查缓存文件地址是否存在
|
||
if !fileManager.fileExists(atPath: cacheDirectory.path) {
|
||
//不存在就创建一个
|
||
try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true, attributes: nil)
|
||
}
|
||
}
|
||
|
||
/// 保存缓存数据
|
||
/// - Parameters:
|
||
/// - data: 数据本身
|
||
/// - key: 使用videoId作为对应的键值
|
||
func cacheData(_ data: Data, dataBlocks:[MediaDataBlock], forKey key: String, isComplete: Bool = false, maxCount:Int64) {
|
||
// 在你的 cacheQueue 里执行操作来确保线程安全
|
||
cacheQueue.async { [weak self] in
|
||
// 取消这个 key 的任何现有缓存操作
|
||
self?.cacheOperations[key]?.cancel()
|
||
|
||
// 创建一个新的缓存操作
|
||
let operation = DispatchWorkItem {
|
||
self?.performCacheData(data, dataBlocks: dataBlocks, forKey: key, isComplete: isComplete, maxCount: maxCount)
|
||
// 甚至删除值也包含在cacheQueue的异步调用中
|
||
self?.cacheQueue.async {
|
||
self?.cacheOperations.removeValue(forKey: key)
|
||
}
|
||
}
|
||
|
||
// 把 operation 保存进字典
|
||
self?.cacheOperations[key] = operation
|
||
|
||
// 安排 operation 在节流间隔后运行
|
||
self?.cacheQueue.asyncAfter(deadline: .now() + self!.throttleInterval, execute: operation)
|
||
}
|
||
}
|
||
//保存缓存数据
|
||
private func performCacheData(_ data: Data, dataBlocks:[MediaDataBlock], forKey key: String, isComplete: Bool = false, maxCount:Int64) {
|
||
let contentToCache = CachedMedia(data: data, dataBlocks: dataBlocks, isComplete: isComplete, maxCount: maxCount)
|
||
memoryCache.setObject(contentToCache, forKey: key as NSString)
|
||
//文件路径
|
||
let fileURL = self.cacheDirectory.appendingPathComponent(key)
|
||
//临时文件
|
||
let tempURL = fileURL.appendingPathExtension("tmp")
|
||
let encoder = JSONEncoder()
|
||
do {
|
||
let newData = try encoder.encode(contentToCache)
|
||
// 写入到文件
|
||
try newData.write(to: tempURL, options: .atomicWrite)
|
||
// 检查目标位置是否已经存在文件,如果存在,则删除
|
||
if self.fileManager.fileExists(atPath: fileURL.path) {
|
||
try self.fileManager.removeItem(at: fileURL)
|
||
}
|
||
// 然后将临时文件移动到最终位置
|
||
try self.fileManager.moveItem(at: tempURL, to: fileURL)
|
||
// 设置过期时间元数据
|
||
let expirationDate = Date().addingTimeInterval(self.expirationInterval)
|
||
try self.fileManager.setAttributes([.modificationDate: expirationDate], ofItemAtPath: fileURL.path)
|
||
} catch {
|
||
// 如果发生错误,删除临时文件
|
||
try? self.fileManager.removeItem(at: tempURL)
|
||
print("无法将密钥数据写入磁盘 \(key) - 错误: \(error)")
|
||
}
|
||
}
|
||
/// 取出数据
|
||
/// - Parameter key: 使用videoId作为对应的键值
|
||
/// - Returns: 返回的数据
|
||
func data(forKey key: String) -> CachedMedia? {
|
||
if let cachedContent = memoryCache.object(forKey: key as NSString){
|
||
return cachedContent
|
||
}
|
||
return cacheQueue.sync {
|
||
[weak self] in
|
||
guard let self = self else { return nil }
|
||
let fileURL = self.cacheDirectory.appendingPathComponent(key)
|
||
guard let jsonData = try? Data(contentsOf: fileURL) else { return nil }
|
||
// 将从磁盘中读取到的 JSON 数据反序列化为 CachedMedia 对象
|
||
let decoder = JSONDecoder()
|
||
do{
|
||
let cachedContent = try decoder.decode(CachedMedia.self, from: jsonData)
|
||
// 检查数据是否过期(同时可以在此处加入完整性检查)
|
||
let attributes = try fileManager.attributesOfItem(atPath: fileURL.path)
|
||
guard let modificationDate = attributes[.modificationDate] as? Date else { return nil }
|
||
if Date().timeIntervalSince(modificationDate) < expirationInterval{
|
||
// 数据未过期,返回数据并更新内存缓存
|
||
memoryCache.setObject(cachedContent, forKey: key as NSString)
|
||
return cachedContent
|
||
} else {
|
||
// 数据已过期,删除缓存文件
|
||
try fileManager.removeItem(at: fileURL)
|
||
}
|
||
} catch {
|
||
print("读取缓存数据失败 \(key) - 错误: \(error)")
|
||
}
|
||
return nil
|
||
}
|
||
}
|
||
}
|