From 7d1c150364be77fb6b111e81e4f8f47c1c9a9c75 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 15 Jun 2021 16:36:42 +0800 Subject: [PATCH] fix: timer update leaking raise crash in notification scene --- CoreDataStack/Entity/Notification.swift | 4 +++ .../Section/NotificationSection.swift | 30 +++++++++++-------- .../NotificationViewModel+diffable.swift | 25 +++++++--------- .../NotificationStatusTableViewCell.swift | 16 +++++----- .../Entity/Mastodon+Entity+Notification.swift | 12 ++++++++ 5 files changed, 51 insertions(+), 36 deletions(-) diff --git a/CoreDataStack/Entity/Notification.swift b/CoreDataStack/Entity/Notification.swift index 31c361aa..04f8e9fd 100644 --- a/CoreDataStack/Entity/Notification.swift +++ b/CoreDataStack/Entity/Notification.swift @@ -101,6 +101,10 @@ extension MastodonNotification { ]) } } + + public static func predicate(validTypesRaws types: [String]) -> NSPredicate { + return NSPredicate(format: "%K IN %@", #keyPath(MastodonNotification.typeRaw), types) + } } diff --git a/Mastodon/Diffiable/Section/NotificationSection.swift b/Mastodon/Diffiable/Section/NotificationSection.swift index 155ec3fc..47e3b5bb 100644 --- a/Mastodon/Diffiable/Section/NotificationSection.swift +++ b/Mastodon/Diffiable/Section/NotificationSection.swift @@ -30,14 +30,16 @@ extension NotificationSection { guard let dependency = dependency else { return nil } switch notificationItem { case .notification(let objectID, let attribute): - let notification = managedObjectContext.object(with: objectID) as! MastodonNotification guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: notification.typeRaw) else { + // filter out invalid type using predicate assertionFailure() - return nil + return UITableViewCell() } - let timeText = notification.createAt.slowedTimeAgoSinceNow - + + let createAt = notification.createAt + let timeText = createAt.slowedTimeAgoSinceNow + let actionText = type.actionText let actionImageName = type.actionImageName let color = type.color @@ -57,23 +59,24 @@ extension NotificationSection { requestUserID: requestUserID, statusItemAttribute: attribute ) + cell.actionImageBackground.backgroundColor = color + cell.nameLabel.text = notification.account.displayName.isEmpty ? notification.account.username : notification.account.displayName + cell.actionLabel.text = actionText + " · " + timeText timestampUpdatePublisher - .sink { _ in - let timeText = notification.createAt.slowedTimeAgoSinceNow + .sink { [weak cell] _ in + guard let cell = cell else { return } + let timeText = createAt.slowedTimeAgoSinceNow cell.actionLabel.text = actionText + " · " + timeText } .store(in: &cell.disposeBag) - cell.actionImageBackground.backgroundColor = color - cell.actionLabel.text = actionText + " · " + timeText - cell.nameLabel.text = notification.account.displayName.isEmpty ? notification.account.username : notification.account.displayName if let url = notification.account.avatarImageURL() { - cell.avatatImageView.af.setImage( + cell.avatarImageView.af.setImage( withURL: url, placeholderImage: UIImage.placeholder(color: .systemFill), imageTransition: .crossDissolve(0.2) ) } - cell.avatatImageView.gesture().sink { [weak cell] _ in + cell.avatarImageView.gesture().sink { [weak cell] _ in cell?.delegate?.userAvatarDidPressed(notification: notification) } .store(in: &cell.disposeBag) @@ -86,8 +89,9 @@ extension NotificationSection { let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationTableViewCell.self), for: indexPath) as! NotificationTableViewCell cell.delegate = delegate timestampUpdatePublisher - .sink { _ in - let timeText = notification.createAt.slowedTimeAgoSinceNow + .sink { [weak cell] _ in + guard let cell = cell else { return } + let timeText = createAt.slowedTimeAgoSinceNow cell.actionLabel.text = actionText + " · " + timeText } .store(in: &cell.disposeBag) diff --git a/Mastodon/Scene/Notification/NotificationViewModel+diffable.swift b/Mastodon/Scene/Notification/NotificationViewModel+diffable.swift index cd28c5f5..4e0d9b6d 100644 --- a/Mastodon/Scene/Notification/NotificationViewModel+diffable.swift +++ b/Mastodon/Scene/Notification/NotificationViewModel+diffable.swift @@ -9,6 +9,7 @@ import CoreData import CoreDataStack import os.log import UIKit +import MastodonSDK extension NotificationViewModel { func setupDiffableDataSource( @@ -16,7 +17,7 @@ extension NotificationViewModel { delegate: NotificationTableViewCellDelegate, dependency: NeedsDependency ) { - let timestampUpdatePublisher = Timer.publish(every: 30.0, on: .main, in: .common) + let timestampUpdatePublisher = Timer.publish(every: 1.0, on: .main, in: .common) .autoconnect() .share() .eraseToAnyPublisher() @@ -44,7 +45,14 @@ extension NotificationViewModel: NSFetchedResultsControllerDelegate { guard let diffableDataSource = self.diffableDataSource else { return } - let predicate = fetchedResultsController.fetchRequest.predicate + let predicate: NSPredicate = { + let notificationTypePredicate = MastodonNotification.predicate( + validTypesRaws: Mastodon.Entity.Notification.NotificationType.knownCases.map { $0.rawValue } + ) + return fetchedResultsController.fetchRequest.predicate.flatMap { + NSCompoundPredicate(andPredicateWithSubpredicates: [$0, notificationTypePredicate]) + } ?? notificationTypePredicate + }() let parentManagedObjectContext = fetchedResultsController.managedObjectContext let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) managedObjectContext.parent = parentManagedObjectContext @@ -73,19 +81,6 @@ extension NotificationViewModel: NSFetchedResultsControllerDelegate { newSnapshot.appendSections([.main]) let items: [NotificationItem] = notifications.map { notification in let attribute: Item.StatusAttribute = oldSnapshotAttributeDict[notification.objectID] ?? Item.StatusAttribute() - -// let attribute: Item.StatusAttribute = { -// if let attribute = oldSnapshotAttributeDict[notification.objectID] { -// return attribute -// } else if let status = notification.status { -// let attribute = Item.StatusAttribute() -// let isSensitive = status.sensitive || !(status.spoilerText ?? "").isEmpty -// attribute.isRevealing.value = !isSensitive -// return attribute -// } else { -// return Item.StatusAttribute() -// } -// }() return NotificationItem.notification(objectID: notification.objectID, attribute: attribute) } newSnapshot.appendItems(items, toSection: .main) diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift index a950ede4..d4bd7b8f 100644 --- a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift +++ b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift @@ -17,7 +17,7 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { var pollCountdownSubscription: AnyCancellable? var delegate: NotificationTableViewCellDelegate? - let avatatImageView: UIImageView = { + let avatarImageView: UIImageView = { let imageView = UIImageView() imageView.layer.cornerRadius = 4 imageView.layer.cornerCurve = .continuous @@ -86,7 +86,7 @@ final class NotificationStatusTableViewCell: UITableViewCell, StatusCell { override func prepareForReuse() { super.prepareForReuse() - avatatImageView.af.cancelImageRequest() + avatarImageView.af.cancelImageRequest() statusView.updateContentWarningDisplay(isHidden: true, animated: false) statusView.pollTableView.dataSource = nil statusView.playerContainerView.reset() @@ -142,13 +142,13 @@ extension NotificationStatusTableViewCell { avatarContainer.widthAnchor.constraint(equalToConstant: 47).priority(.required - 1) ]) - avatarContainer.addSubview(avatatImageView) - avatatImageView.translatesAutoresizingMaskIntoConstraints = false + avatarContainer.addSubview(avatarImageView) + avatarImageView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ - avatatImageView.heightAnchor.constraint(equalToConstant: 35).priority(.required - 1), - avatatImageView.widthAnchor.constraint(equalToConstant: 35).priority(.required - 1), - avatatImageView.topAnchor.constraint(equalTo: avatarContainer.topAnchor), - avatatImageView.leadingAnchor.constraint(equalTo: avatarContainer.leadingAnchor) + avatarImageView.heightAnchor.constraint(equalToConstant: 35).priority(.required - 1), + avatarImageView.widthAnchor.constraint(equalToConstant: 35).priority(.required - 1), + avatarImageView.topAnchor.constraint(equalTo: avatarContainer.topAnchor), + avatarImageView.leadingAnchor.constraint(equalTo: avatarContainer.leadingAnchor) ]) avatarContainer.addSubview(actionImageBackground) diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Notification.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Notification.swift index 0cdcc2e7..f7b3147b 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Notification.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Notification.swift @@ -49,6 +49,18 @@ extension Mastodon.Entity.Notification { case _other(String) + public static var knownCases: [NotificationType] { + return [ + .follow, + .followRequest, + .mention, + .reblog, + .favourite, + .poll, + .status + ] + } + public init?(rawValue: String) { switch rawValue { case "follow": self = .follow