Утечка памяти при использовании многоадресного делегата с mvvm-c?
Я не реализовывал шаблон многоадресного делегата ни в одном из своих проектов (до сих пор), поэтому я довольно новичок в этом. Я использую архитектуру MVVM-C и получаю утечку памяти всякий раз, когда пытаюсь вызвать метод делегата из моей модели при изменении данных.
Вот моя реализация многоадресного делегата...
class MulticastDelegate<T> {
// MARK: - Private Properties
private var delegates = [Weak]()
// MARK: - Methods
func add(_ delegate: T) {
delegates.append(Weak(value: delegate as AnyObject))
}
func remove(_ delegate: T) {
let weak = Weak(value: delegate as AnyObject)
if let index = delegates.index(of: weak) {
delegates.remove(at: index)
}
}
func invoke(_ invocation: @escaping (T) -> ()) {
delegates = delegates.filter({$0.value != nil})
delegates.forEach({
if let delegate = $0.value as? T {
invocation(delegate)
}
})
}
}
И слабый...
private class Weak {
// MARK: - Properties
weak var value: AnyObject?
// MARK: - Object Lifecycle
init(value: AnyObject) {
self.value = value
}
}
// MARK: - Equatable
extension Weak: Equatable {
static func ==(lhs: Weak, rhs: Weak) -> Bool {
return lhs.value === rhs.value
}
}
А вот и моя модель...
protocol UserModelDelegate: class {
func userDidChange(user: UserData)
}
protocol UserModel {
var delegates: MulticastDelegate<UserModelDelegate> {get}
func fetchUser(completionHandler: @escaping (_ user: UserData?) -> ())
...
}
class MWUserModel: UserModel {
// MARK: - Delegates
var delegates = MulticastDelegate<UserModelDelegate>()
// MARK: - Private Properties
fileprivate var moc: NSManagedObjectContext!
fileprivate var user: UserData? {
didSet {
// This is causing the leak...
delegates.invoke { [weak self] vm in
if let user = self?.user {
vm.userDidChange(user: user)
}
}
}
}
// MARK: - Object Lifecycle
init(moc: NSManagedObjectContext) {
self.moc = moc
}
// MARK: - UserModel
func fetchUser(completionHandler: @escaping (UserData?) -> ()) {
guard user == nil else {completionHandler(user); return}
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = User.fetchRequest()
fetchRequest.fetchLimit = 1
do {
let objects = try? moc.fetch(fetchRequest)
// This triggers didSet on the user property which then invokes the UserModel delegate method 'userDidChange'
self.user = objects?.first as? UserData
completionHandler(objects?.first as? UserData)
}
}
И моя ViewModel...
protocol ProfileViewModelViewDelegate: class {
func userDidChange()
}
protocol ProfileViewModelView {
var model: UserModel? {get set}
var viewDelegate: ProfileViewModelViewDelegate? {get set}
}
class ProfileViewModel: ProfileViewModelView {
// MARK: - Private Properties
fileprivate var user: UserData? {
didSet {
viewDelegate?.userDidChange()
}
}
// MARK: - ProfileViewModelView
weak var viewDelegate: ProfileViewModelViewDelegate?
// This is being injected via the coordinator...
var model: UserModel? {
didSet {
model?.delegates.add(self)
model?.fetchUser(completionHandler: { (user) in
self.user = user
})
}
}
}
// MARK: - UserModelDelegate
extension ProfileViewModel: UserModelDelegate {
func userDidChange(user: UserData) {
self.user = user
}
}