Доступ к NSCache между контроллерами просмотра в контроллере просмотра вкладок

#swift

#swift

Вопрос:

Я хочу получить доступ к NSCache из нескольких мест в моем приложении, поскольку я использую его для кэширования изображений с конечной точки API.

Например, table view 4 и viewcontroller 6 на диаграмме ниже используют одни и те же изображения, поэтому я не хочу загружать их дважды.

введите описание изображения здесь

Возможные решения:

  1. Синглтон

     class Cache {  
    
        private static var sharedCache: NSCache<AnyObject, AnyObject>?        
        static public func getCache () -> NSCache<AnyObject, AnyObject> {
    
            if sharedCache == nil {
                self.sharedCache = NSCache()
            }
            return sharedCache!
        } 
    }
      

Кажется, работает нормально, но «одиночные файлы плохие», так что…

  1. Сохраните кэш в TabViewController

Это позволит тесно связать представления с контроллером представления таким образом…

  1. Каким-то образом сохранить в AppDelegate. Но разве это не то же самое, что 1? Итак…

  2. Используйте внедрение зависимостей. Но мы находимся в контроллере просмотра вкладок, так разве это не то же самое, что 2?

Я не уверен, что здесь правильная стратегия, поэтому спрашиваю, есть ли другой метод, который можно использовать здесь.

Что я сделал, создал приложение с примером использования NSCache и исследовал одноэлементное решение. Я пытался использовать внедрение зависимостей, но думаю, что это не имеет смысла. Я просмотрел Stack overflow и документацию, но для этого конкретного обстоятельства я не нашел потенциальных решений.

То, что я привел, является минимальным примером со схемой и протестированным решением, которым я недоволен.

Что не помогает, так это ответы, в которых говорится, что NSCache неверен, или использовать библиотеки. Я пытаюсь использовать NSCache для собственного обучения, это не домашнее задание, и я хочу решить этот конкретный случай этой проблемы в этой структуре приложения.

В чем вопрос, как избежать использования синглтона в этом случае, контроллеры просмотра в контроллере просмотра вкладок.

Комментарии:

1. почему «одиночные файлы плохие»?

2. Аргумент antipattern — hackingwithswift.com/articles/88 /… medium.com/@johnsundell / … — плюсы и минусы medium.freecodecamp.org / … — но проблема скорее в том, какова альтернатива?

3. Если единственной целью вашего кэша является хранение изображения в кэше, то вам следует рассмотреть возможность использования SDWebImage framework, который широко известен и используется для кэширования изображений

4. Что не помогает, так это ответы, в которых говорится, что NSCache неверен, или использовать библиотеки. Я пытаюсь использовать NSCache для собственного обучения, это не домашнее задание, и я хочу решить этот конкретный случай этой проблемы в этой структуре приложения.

Ответ №1:

Сначала. Синглтоны не являются изначально плохими. Они могут затруднить тестирование вашего кода и действуют как магниты зависимости.

Синглтоны хороши для классов, которые являются инструментами, например, NSFileManager иначе FileManger , то есть чем-то, что не переносит состояние или данные.

Хорошей альтернативой является внедрение зависимости, но с контроллерами просмотра и раскадровками это может быть сложно и казаться очень шаблонным. В конечном итоге вы передаете все по строке в prepareForSegue .

Один из возможных методов — объявить protocol , который описывает интерфейс, подобный кешу.

 protocol CacheProtocol: class {
    func doCacheThing()
}

class Cache: CacheProtocol {
    func doCacheThing() {
        //
    }
}
  

Затем объявите protocol , что все объекты, которые хотят использовать этот кэш, могут использовать.

 protocol CacheConsumer: class {
    var cache: CacheProtocol? { get set }
    func injectCache(to object: AnyObject)
}

extension CacheConsumer {
    func injectCache(to object: AnyObject) {
        if let consumer = object as? CacheConsumer {
            consumer.cache = cache
        }
    }
}
  

Наконец, создайте конкретный экземпляр этого кэша на верхнем уровне.

 /// Top most controller
class RootLevelViewController: UIViewController, CacheConsumer {
    var cache: CacheProtocol? = Cache()

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        injectCache(to: segue.destination)
    }

}
  

Вы могли бы передать кэш по строке в prepareForSegue .

Или вы можете использовать тонкие подклассы для создания соответствия.

 class MyTabBarController: UITabBarController, CacheConsumer {
    var cache: CacheProtocol?
}
  

Или вы можете использовать методы делегирования, чтобы заставить объект кэша транслироваться вниз по склону.

 extension RootLevelViewController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        injectCache(to: viewController)
    }
}
  

Теперь у вас есть система, в которой любой CacheConsumer может использовать кэш и передавать его по умолчанию любому другому объекту.

Ответ №2:

Если вы используете шаблон координатора, вы можете сохранить кэш в координаторе для вашего потока навигации и получить к нему доступ оттуда / инициализировать с помощью кэша. Это также работает хорошо, поскольку при удалении потока навигации кэш также удаляется.

 final class SomeCoordinator: NSObject, Coordinator {
    var rootViewController: UINavigationController
    var myCache = NSCache<AnyObject, AnyObject>()
    
    override init() {
        self.rootViewController = UINavigationController()
        super.init()
    }
    
    func start() {
        let vc = VC1(cache: myCache)
        vc.coordinator = self
        rootViewController.setViewControllers([vc], animated: false)
        parentCoordinator?.rootViewController.present(rootViewController, animated: true)
    }
    
    func goToVC2() {
        let vc = VC2(cache: myCache)
        vc.coordinator = self
        rootViewController.pushViewController(vc, animated: true)
    }
    
    func goToVC3() {
        let vc = VC3(cache: myCache)
        vc.coordinator = self
        rootViewController.present(vc, animated: true)
    }
    
    func goToVC4() {
        let vc = VC4(cache: myCache)
        vc.coordinator = self
        rootViewController.present(vc, animated: true)
    }
    
    deinit {
        print("✅ Deinit SomeCoordinator")
    }
    
}