CacheTracker
定义了将 UI 层与存储层分离的接口。还包含了 CoreData 和 Realm 的默认实现。
追踪器还可以将数据库模型转换为平面模型(所谓 PONSO)。您只需在数据库模型和平面模型类中实现一些协议即可。
可以用在 VIPER 架构中的 interactor 中。在这种情况下,您的代码将仅与 PONSO 对象一起工作。
注意:所有接口都只与单个分区一起工作,所有索引都作为单个线性数字传递,而不是 IndexPath。
注意:原始想法来自 https://github.com/akantsevoi/CacheTracker
安装
Cocoapods
仅使用接口
pod 'CacheTracker'
使用 CoreData
pod 'CacheTracker/CoreData'
使用 Realm
pod 'CacheTracker/Realm'
用法
定义平面模型类
import Foundation
import CacheTracker
class PlainItem: CacheTrackerPlainModel {
let name: String
init(name: String) {
self.name = name
}
// MARK: - CacheTrackerPlainModel
required init() {
self.name = ""
}
}
定义数据库实体类
import CoreData
import CacheTracker
@objc(CoreDataItem)
class CoreDataItem: NSManagedObject, CacheTrackerDatabaseModel {
@NSManaged var name: String?
// MARK: - CacheTrackerDatabaseModel
static func entityName() -> String {
return NSStringFromClass(self)
}
func toPlainModel<P>() -> P? {
return PlainItem(name: self.name!) as? P
}
}
只需创建缓存请求
let cacheRequest = CacheRequest(predicate: NSPredicate(value: true), sortDescriptors: [
NSSortDescriptor(key: #keyPath(CoreDataItem.name), ascending: true)
])
使用数据库和 PONSO 模型类实例化缓存追踪器
cacheTracker = CoreDataCacheTracker<CoreDataItem, PlainItem>(context: NSManagedObjectContext.mr_default())
cacheTracker.delegate = self
并开始获取
cacheTracker.fetchWithRequest(cacheRequest)
CoreData + UITableViewDelegate
import UIKit
import CacheTracker
import MagicalRecord
import RandomKit
class CoreDataTableViewController: UITableViewController, CacheTrackerDelegate {
typealias P = PlainItem
var context: NSManagedObjectContext!
var cacheTracker: CoreDataCacheTracker<CoreDataItem, PlainItem>!
var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
if timer == nil {
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { (timer) in
let value = Int.random(using: &Xoroshiro.default) % 4
switch value {
case 0:
self.context.mr_save( blockAndWait: { (context) in
if CoreDataItem.mr_countOfEntities(with: context) > 10 {
return
}
let value = UInt.random(using: &Xoroshiro.default)
let item = CoreDataItem.mr_createEntity(in: context)
item?.name = "\(value)"
})
case 1:
self.context.mr_save( blockAndWait: { (context) in
let items = CoreDataItem.mr_findAll(with: NSPredicate(value: true), in: context)!
let count = items.count
if count > 1 {
let target = abs(Int.random(using: &Xoroshiro.default)) % count
items[target].mr_deleteEntity(in: context)
}
})
case 2:
break
default:
self.context.mr_save( blockAndWait: { (context) in
let items = CoreDataItem.mr_findAll(with: NSPredicate(value: true), in: context)!
let count = items.count
if count > 1 {
let target = abs(Int.random(using: &Xoroshiro.default)) % count
let value = UInt.random(using: &Xoroshiro.default)
let item = items[target] as! CoreDataItem
item.name = "\(value)"
}
})
}
self.tabBarItem.badgeValue = String(CoreDataItem.mr_countOfEntities())
})
}
if context == nil {
context = NSManagedObjectContext.mr_default()
}
context.mr_save( blockAndWait: { (context) in
CoreDataItem.mr_deleteAll(matching: NSPredicate(value: true), in: context)
for _ in 0..<3 {
let item = CoreDataItem.mr_createEntity(in: context)
let value = UInt.random(using: &Xoroshiro.default)
item?.name = "\(value)"
}
})
cacheTracker = CoreDataCacheTracker<CoreDataItem, PlainItem>(context: NSManagedObjectContext.mr_default())
cacheTracker.delegate = self
let cacheRequest = CacheRequest(predicate: NSPredicate(value: true), sortDescriptors: [
NSSortDescriptor(key: #keyPath(CoreDataItem.name), ascending: true)
// ,fetchLimit: 10 // example
])
// enable soft normalizer if you want to use fetchLimit
// cacheTracker.enableFetchLimitOverflowSoftNormalizer = true
cacheTracker.fetchWithRequest(cacheRequest)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cacheTracker.numberOfObjects()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(withIdentifier: "Default")!
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let item = cacheTracker.object(at: indexPath.row)
cell.textLabel?.text = item?.name
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
// MARK: - CacheTrackerDelegate
func cacheTrackerShouldMakeInitialReload() {
guard isViewLoaded else {
return
}
tableView.reloadData()
}
func cacheTrackerBeginUpdates() {
guard isViewLoaded else {
return
}
tableView.beginUpdates()
}
func cacheTrackerEndUpdates() {
guard isViewLoaded else {
return
}
tableView.endUpdates()
}
func cacheTrackerDidGenerate<P>(transactions: [CacheTransaction<P>]) {
guard isViewLoaded else {
return
}
for transaction in transactions {
switch transaction.type {
case .insert:
self.tableView.insertRows(at: [IndexPath(row: transaction.newIndex!, section: 0)], with: .fade)
case .delete:
self.tableView.deleteRows(at: [IndexPath(row: transaction.index!, section: 0)], with: .fade)
case .update:
self.tableView.reloadRows(at: [IndexPath(row: transaction.index!, section: 0)], with: .fade)
case .move:
self.tableView.deleteRows(at: [IndexPath(row: transaction.index!, section: 0)], with: .fade)
self.tableView.insertRows(at: [IndexPath(row: transaction.newIndex!, section: 0)], with: .fade)
}
}
}
}
CoreData + UICollectionView
import UIKit
import CacheTracker
import MagicalRecord
import RandomKit
class CoreDataCollectionViewController: UICollectionViewController, CacheTrackerDelegate {
typealias P = PlainItem
var context: NSManagedObjectContext!
var cacheTracker: CoreDataCacheTracker<CoreDataItem, PlainItem>!
var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
if timer == nil {
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { (timer) in
let value = Int.random(using: &Xoroshiro.default) % 4
switch value {
case 0:
self.context.mr_save( blockAndWait: { (context) in
if CoreDataItem.mr_countOfEntities(with: context) > 10 {
return
}
let value = UInt.random(using: &Xoroshiro.default)
let item = CoreDataItem.mr_createEntity(in: context)
item?.name = "\(value)"
})
case 1:
self.context.mr_save( blockAndWait: { (context) in
let items = CoreDataItem.mr_findAll(with: NSPredicate(value: true), in: context)!
let count = items.count
if count > 1 {
let target = abs(Int.random(using: &Xoroshiro.default)) % count
items[target].mr_deleteEntity(in: context)
}
})
case 2:
break
default:
self.context.mr_save( blockAndWait: { (context) in
let items = CoreDataItem.mr_findAll(with: NSPredicate(value: true), in: context)!
let count = items.count
if count > 1 {
let target = abs(Int.random(using: &Xoroshiro.default)) % count
let value = UInt.random(using: &Xoroshiro.default)
let item = items[target] as! CoreDataItem
item.name = "\(value)"
}
})
}
self.tabBarItem.badgeValue = String(CoreDataItem.mr_countOfEntities())
})
}
if context == nil {
context = NSManagedObjectContext.mr_default()
}
context.mr_save( blockAndWait: { (context) in
CoreDataItem.mr_deleteAll(matching: NSPredicate(value: true), in: context)
for _ in 0..<3 {
let item = CoreDataItem.mr_createEntity(in: context)
let value = UInt.random(using: &Xoroshiro.default)
item?.name = "\(value)"
}
})
cacheTracker = CoreDataCacheTracker<CoreDataItem, PlainItem>(context: NSManagedObjectContext.mr_default())
cacheTracker.delegate = self
let cacheRequest = CacheRequest(predicate: NSPredicate(value: true), sortDescriptors: [
NSSortDescriptor(key: #keyPath(CoreDataItem.name), ascending: true)
])
cacheTracker.fetchWithRequest(cacheRequest)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK:
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cacheTracker.numberOfObjects()
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: "Default", for: indexPath)
}
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let item = cacheTracker.object(at: indexPath.row)
let label = cell.viewWithTag(1) as! UILabel
label.text = item?.name
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
}
// MARK: - CacheTrackerDelegate
func cacheTrackerShouldMakeInitialReload() {
guard isViewLoaded else {
return
}
collectionView!.performBatchUpdates({
collectionView!.reloadSections(IndexSet(integer: 0))
}, completion: nil)
}
func cacheTrackerBeginUpdates() {
guard isViewLoaded else {
return
}
}
func cacheTrackerEndUpdates() {
guard isViewLoaded else {
return
}
}
func cacheTrackerDidGenerate<P>(transactions: [CacheTransaction<P>]) {
guard isViewLoaded else {
return
}
collectionView?.performBatchUpdates({
for transaction in transactions {
switch transaction.type {
case .insert:
self.collectionView!.insertItems(at: [IndexPath(row: transaction.newIndex!, section: 0)])
case .delete:
self.collectionView!.deleteItems(at: [IndexPath(row: transaction.index!, section: 0)])
case .update:
self.collectionView?.reloadItems(at: [IndexPath(row: transaction.index!, section: 0)])
case .move:
self.collectionView!.deleteItems(at: [IndexPath(row: transaction.index!, section: 0)])
self.collectionView!.insertItems(at: [IndexPath(row: transaction.newIndex!, section: 0)])
}
}
}, completion: { (completed) in
})
}
}
Realm + UITableViewDelegate
import CacheTracker
import RealmSwift
import RandomKit
class RealmTableViewController: UITableViewController, CacheTrackerDelegate {
var context: Realm!
var cacheTracker: RealmCacheTracker<RealmItem, PlainItem>!
var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
if timer == nil {
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { (timer) in
let value = Int.random(using: &Xoroshiro.default) % 4
switch value {
case 0:
try! self.context.write {
let count = self.context.objects(RealmItem.self).count
if count < 5 {
return
}
let target = abs(Int.random(using: &Xoroshiro.default)) % count
let objects = self.context.objects(RealmItem.self)
if target >= objects.count {
return
}
self.context.delete(objects[target])
}
case 1: // update
try! self.context.write {
let count = self.context.objects(RealmItem.self).count
if count < 5 {
return
}
let target = abs(Int.random(using: &Xoroshiro.default)) % count
let objects = self.context.objects(RealmItem.self)
if target >= objects.count {
return
}
let object = objects[target]
object.name = String(abs(Int.random(using: &Xoroshiro.default)))
}
default:
try! self.context.write {
let count = self.context.objects(RealmItem.self).count
if count > 10 {
return
}
let item = RealmItem()
item.idKey = String(Int.random(using: &Xoroshiro.default))
item.name = String(abs(Int.random(using: &Xoroshiro.default)))
self.context.add(item)
}
}
self.tabBarItem.badgeValue = String(self.context.objects(RealmItem.self).count)
})
}
if context == nil {
context = try! Realm()
}
try! context.write {
context.deleteAll()
}
cacheTracker = RealmCacheTracker<RealmItem, PlainItem>(realm: context)
cacheTracker.delegate = self
let cacheRequest = CacheRequest(predicate: NSPredicate(value: true), sortDescriptors: [
NSSortDescriptor(key: #keyPath(CoreDataItem.name), ascending: true)
])
cacheTracker.fetchWithRequest(cacheRequest)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cacheTracker.numberOfObjects()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(withIdentifier: "Default")!
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let item = cacheTracker.object(at: indexPath.row)
cell.textLabel?.text = item?.name
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
// MARK: - CacheTrackerDelegate
func cacheTrackerShouldMakeInitialReload() {
guard isViewLoaded else {
return
}
tableView.reloadData()
}
func cacheTrackerBeginUpdates() {
guard isViewLoaded else {
return
}
tableView.beginUpdates()
}
func cacheTrackerEndUpdates() {
guard isViewLoaded else {
return
}
tableView.endUpdates()
}
func cacheTrackerDidGenerate<P>(transactions: [CacheTransaction<P>]) {
guard isViewLoaded else {
return
}
for transaction in transactions {
switch transaction.type {
case .insert:
self.tableView.insertRows(at: [IndexPath(row: transaction.newIndex!, section: 0)], with: .fade)
case .delete:
self.tableView.deleteRows(at: [IndexPath(row: transaction.index!, section: 0)], with: .fade)
case .update:
self.tableView.reloadRows(at: [IndexPath(row: transaction.index!, section: 0)], with: .fade)
case .move:
self.tableView.deleteRows(at: [IndexPath(row: transaction.index!, section: 0)], with: .fade)
self.tableView.insertRows(at: [IndexPath(row: transaction.newIndex!, section: 0)], with: .fade)
}
}
}
}
Realm + UICollectionView
import CacheTracker
import RealmSwift
import RandomKit
class RealmCollectionViewController: UICollectionViewController, CacheTrackerDelegate {
var context: Realm!
var cacheTracker: RealmCacheTracker<RealmItem, PlainItem>!
var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
if timer == nil {
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { (timer) in
let value = Int.random(using: &Xoroshiro.default) % 4
switch value {
case 0:
try! self.context.write {
let count = self.context.objects(RealmItem.self).count
if count < 5 {
return
}
let target = abs(Int.random(using: &Xoroshiro.default)) % count
let objects = self.context.objects(RealmItem.self)
if target >= objects.count {
return
}
self.context.delete(objects[target])
}
case 1: // update
try! self.context.write {
let count = self.context.objects(RealmItem.self).count
if count < 5 {
return
}
let target = abs(Int.random(using: &Xoroshiro.default)) % count
let objects = self.context.objects(RealmItem.self)
if target >= objects.count {
return
}
let object = objects[target]
object.name = String(abs(Int.random(using: &Xoroshiro.default)))
}
default:
try! self.context.write {
let count = self.context.objects(RealmItem.self).count
if count > 10 {
return
}
let item = RealmItem()
item.idKey = String(Int.random(using: &Xoroshiro.default))
item.name = String(abs(Int.random(using: &Xoroshiro.default)))
self.context.add(item)
}
}
self.tabBarItem.badgeValue = String(self.context.objects(RealmItem.self).count)
})
}
if context == nil {
context = try! Realm()
}
try! context.write {
context.deleteAll()
}
cacheTracker = RealmCacheTracker<RealmItem, PlainItem>(realm: context)
cacheTracker.delegate = self
let cacheRequest = CacheRequest(predicate: NSPredicate(value: true), sortDescriptors: [
NSSortDescriptor(key: #keyPath(CoreDataItem.name), ascending: true)
])
cacheTracker.fetchWithRequest(cacheRequest)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK:
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cacheTracker.numberOfObjects()
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: "Default", for: indexPath)
}
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let item = cacheTracker.object(at: indexPath.row)
let label = cell.viewWithTag(1) as! UILabel
label.text = item?.name
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
}
// MARK: - CacheTrackerDelegate
func cacheTrackerShouldMakeInitialReload() {
guard isViewLoaded else {
return
}
collectionView!.performBatchUpdates({
collectionView!.reloadSections(IndexSet(integer: 0))
}, completion: nil)
}
func cacheTrackerBeginUpdates() {
guard isViewLoaded else {
return
}
}
func cacheTrackerEndUpdates() {
guard isViewLoaded else {
return
}
}
func cacheTrackerDidGenerate<P>(transactions: [CacheTransaction<P>]) {
guard isViewLoaded else {
return
}
collectionView?.performBatchUpdates({
for transaction in transactions {
switch transaction.type {
case .insert:
self.collectionView!.insertItems(at: [IndexPath(row: transaction.newIndex!, section: 0)])
case .delete:
self.collectionView!.deleteItems(at: [IndexPath(row: transaction.index!, section: 0)])
case .update:
self.collectionView?.reloadItems(at: [IndexPath(row: transaction.index!, section: 0)])
case .move:
self.collectionView!.deleteItems(at: [IndexPath(row: transaction.index!, section: 0)])
self.collectionView!.insertItems(at: [IndexPath(row: transaction.newIndex!, section: 0)])
}
}
}, completion: { (completed) in
})
}
}
COPYRIGHT
本作品采用MIT许可协议授权 - 有关详细信息,请参阅COPYRIGHT文件