CacheTracker 1.6.2

CacheTracker 1.6.2

Siarhei Ladzeika 维护。



  • 作者:
  • Siarhei Ladzeika

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文件