Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  EntityObserver.swift   Sprache: JAVA

 

import CoreData
import Foundation

public final class EntityObserver: NSObject {

    public static let shared = EntityObserver()

    public typealias EntityChangedAction = (NSManagedObject?, EntityChangedReason) -> Void

    public enum EntityChangedReason {
        case updated
        case deleted
    }
    
    private let queue: DispatchQueue

    private struct Subscriber: Hashable {
        static func == (lhs: EntityObserver.Subscriber, rhs: EntityObserver.Subscriber) -> Bool {
            lhs.uuid == rhs.uuid
        }

        func hash(into hasher: inout Hasher) {
            hasher.combine(uuid)
        }

        let uuid: UUID
        var managedObjectFetchPermanentID: NSFetchedResultsController<NSManagedObject>?
        let managedObjectID: NSManagedObjectID? // Is nullable because maybe Core Data set it to null
        let entityChangedReason: [EntityChangedReason]
        let entityChangedAction: EntityChangedAction
    }

    private var subscribersForDelete = Set<Subscriber>()
    private var subscribersForUpdate = Set<Subscriber>()

    override private init() {
        self.queue = DispatchQueue(label: "ch.threema.EntityObserver.queue")
        super.init()
        initializeObserver()
    }

    /// Subscribe managed object to get changes.
    /// - Parameters:
    ///     - managedObjectID: Subscription for managed object
    ///     - for: Reason of DB changes
    ///     - do: Action when managed object did change
    /// - Returns: Token to unsubscribe
    public func subscribe(
        managedObject: NSManagedObject,
        for entityChangedReason: [EntityChangedReason] = [.deleted, .updated],
        do entityChangedAction: @escaping EntityChangedAction
    ) -> SubscriptionToken {
        assert(isEntityTypeAllowed(managedObject))

        let subscriber = Subscriber(
            uuid: UUID(),
            managedObjectFetchPermanentID: managedObject.objectID
                .isTemporaryID ? fetchPermanentID(of: managedObject) : nil,
            managedObjectID: managedObject.objectID,
            entityChangedReason: entityChangedReason,
            entityChangedAction: entityChangedAction
        )

        queue.sync {
            try? subscriber.managedObjectFetchPermanentID?.performFetch()

            if entityChangedReason.contains(.deleted) {
                subscribersForDelete.insert(subscriber)
            }
            if entityChangedReason.contains(.updated) {
                subscribersForUpdate.insert(subscriber)
            }
        }
        return SubscriptionToken {
            self.queue.sync {
                self.subscribersForDelete.remove(subscriber)
                self.subscribersForUpdate.remove(subscriber)
            }
        }
    }

    /// Initialize observer to get Core Data changes.
    private func initializeObserver() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(managedObjectContextObjectsDidChange),
            name: .NSManagedObjectContextObjectsDidChange,
            object: nil
        )
    }

    /// Called when managed object has changed.
    /// - Parameter notification: Notification with changed managed objects
    @objc private func managedObjectContextObjectsDidChange(notification: NSNotification) {
        if let allDeletedObjects = notification.userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject> {
            let allowedDeletedObjects = allDeletedObjects.filter { isEntityTypeAllowed($0) }
            if !allowedDeletedObjects.isEmpty {
                let deletedObjectIDs = Set<NSManagedObjectID>(allowedDeletedObjects.map(\.objectID))
                informSubscribers(
                    updatedObjectIDs: deletedObjectIDs,
                    updatedObjects: allowedDeletedObjects,
                    subscribers: subscribersForDelete,
                    reason: .deleted
                )
            }
        }

        if let allUpdatedObjects = notification.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject> {
            let allowedUpdatedObjects = allUpdatedObjects.filter { isEntityTypeAllowed($0) }

            if !allowedUpdatedObjects.isEmpty {
                let updatedObjectIDs = Set<NSManagedObjectID>(allowedUpdatedObjects.map(\.objectID))
                informSubscribers(
                    updatedObjectIDs: updatedObjectIDs,
                    updatedObjects: allowedUpdatedObjects,
                    subscribers: subscribersForUpdate,
                    reason: .updated
                )
            }
        }
    }

    private func informSubscribers(
        updatedObjectIDs: Set<NSManagedObjectID?>,
        updatedObjects: Set<NSManagedObject>?,
        subscribers: Set<Subscriber>,
        reason: EntityChangedReason
    ) {
        let (localSubscribers, subscribersForObjectIDs) = queue.sync {
            let localSubscribers = Set(subscribers)

            var subscribersForObjectIDs = Set<NSManagedObjectID?>()
            for subscriber in localSubscribers {
                subscribersForObjectIDs.insert(subscriber.managedObjectID)
            }

            return (localSubscribers, subscribersForObjectIDs)
        }

        let intersection = Set<NSManagedObjectID?>(updatedObjectIDs.intersection(subscribersForObjectIDs))

        for subscriber in localSubscribers.filter({ intersection.contains($0.managedObjectID) }) {
            subscriber.entityChangedAction(
                updatedObjects?
                    .first(where: { $0.objectID == subscriber.managedObjectID }),
                reason
            )
        }
    }

    private func isEntityTypeAllowed(_ managedObject: NSManagedObject) -> Bool {
        managedObject is ContactEntity ||
            managedObject is ConversationEntity ||
            managedObject is GroupEntity ||
            managedObject is DistributionListEntity
    }

    // MARK: Subscription token

    /// Token handed out when registering an subscription closure
    public final class SubscriptionToken {
        private var closure: (() -> Void)?

        init(closure: @escaping () -> Void) {
            self.closure = closure
        }

        deinit {
            cancel()
        }

        /// Remove registered closure. (Automatically called when token is deallocated.)
        func cancel() {
            closure?()
            closure = nil
        }
    }

    private func fetchPermanentID<T: NSManagedObject>(of managedObject: T) -> NSFetchedResultsController<T> {
        let fetchRequest = NSFetchRequest<T>()
        fetchRequest.entity = managedObject.entity
        fetchRequest.fetchBatchSize = 1
        fetchRequest.predicate = NSPredicate(format: "self == %@", argumentArray: [managedObject])
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "objectID", ascending: true)]
        let fetchController = NSFetchedResultsController(
            fetchRequest: fetchRequest,
            managedObjectContext: managedObject.managedObjectContext!,
            sectionNameKeyPath: nil,
            cacheName: nil
        )
        fetchController.delegate = self
        return fetchController
    }
}

// MARK: - NSFetchedResultsControllerDelegate

extension EntityObserver: NSFetchedResultsControllerDelegate {

    /// Get changes of Core Data managed objects. Has temporary object ID changed to a permanent object ID, than replace
    /// the subscription.
    public func controller(
        _ controller: NSFetchedResultsController<NSFetchRequestResult>,
        didChangeContentWith diff: CollectionDifference<NSManagedObjectID>
    ) {
        struct ObjectIDPosition {
            let objectID: NSManagedObjectID
            let index: Int
        }

        var temporaryIDs = [ObjectIDPosition]()
        var tempraryIDIndex = 0
        var permanentIDs = [ObjectIDPosition]()
        var permanentIDIndex = 0

        for removal in diff.removals {
            switch removal {
            case .insert(offset: _, element: _, associatedWith: .some(_)):
                break
            case .insert(offset: _, element: _, associatedWith: .none):
                break
            case .remove(offset: _, element: let element, associatedWith: _):
                if element.isTemporaryID {
                    temporaryIDs.append(ObjectIDPosition(objectID: element, index: tempraryIDIndex))
                }
            }
            tempraryIDIndex += 1
        }

        if !temporaryIDs.isEmpty {
            for insertion in diff.insertions {
                switch insertion {
                case .insert(offset: _, element: let element, associatedWith: .some(_)):
                    if temporaryIDs.first(where: { $0.index == permanentIDIndex }) != nil {
                        permanentIDs.append(ObjectIDPosition(objectID: element, index: permanentIDIndex))
                    }
                case .insert(offset: _, element: _, associatedWith: .none):
                    break
                case .remove(offset: _, element: _, associatedWith: _):
                    break
                }
                permanentIDIndex += 1
            }

            if temporaryIDs.count == permanentIDs.count {
                queue.async {
                    for index in 0..<temporaryIDs.count {
                        if let subscriber = self.subscribersForUpdate.first(where: { subscriber in
                            subscriber.managedObjectID == temporaryIDs[index].objectID
                        }) {
                            // Replace subscription with temporary objectID, with subscription with permanent objectID
                            self.subscribersForUpdate.insert(
                                Subscriber(
                                    uuid: UUID(),
                                    managedObjectFetchPermanentID: nil,
                                    managedObjectID: permanentIDs[index].objectID,
                                    entityChangedReason: subscriber.entityChangedReason,
                                    entityChangedAction: subscriber.entityChangedAction
                                )
                            )
                            self.subscribersForUpdate.remove(subscriber)
                        }
                    }
                }
            }
        }
    }
}

Messung
CHZG
839510092

¤ Dauer der Verarbeitung: 0.7 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104842
#Domains=499168