wxr
2021-11-29 dd7e4794fd611de967c6322dd0bb7ffda41c2f7b
SiriKit/Data/DataManager.cs
@@ -1,188 +1,188 @@
/*
See LICENSE folder for this sample’s licensing information.
///*
//See LICENSE folder for this sample’s licensing information.
Abstract:
A data manager that manages data conforming to `Codable` and stores it in `UserDefaults`.
*/
//Abstract:
//A data manager that manages data conforming to `Codable` and stores it in `UserDefaults`.
//*/
using System;
using Foundation;
using CoreFoundation;
//using System;
//using Foundation;
//using CoreFoundation;
namespace Other.Siri
{
    public struct UserDefaultsStorageDescriptor
    {
        public string Key { get; set; }
        public UserDefaultsStorageDescriptor(string key)
        {
            Key = key;
        }
    }
//namespace SiriKit
//{
//    public struct UserDefaultsStorageDescriptor
//    {
//        public string Key { get; set; }
//        public UserDefaultsStorageDescriptor(string key)
//        {
//            Key = key;
//        }
//    }
    public static class NotificationKeys
    {
        // Clients of `DataManager` that want to know when the data changes can
        // listen for this notification.
        public const string DataChanged = "DataChangedNotification";
    }
//    public static class NotificationKeys
//    {
//        // Clients of `DataManager` that want to know when the data changes can
//        // listen for this notification.
//        public const string DataChanged = "DataChangedNotification";
//    }
    public class DataManager<ManagedDataType> : NSObject
        where ManagedDataType : NSObject, INSCoding
    {
        // This sample uses App Groups to share a suite of data between the
        // main app and the different extensions.
        protected NSUserDefaults UserDefaults = NSUserDefaultsHelper.DataSuite;
//    public class DataManager<ManagedDataType> : NSObject
//        where ManagedDataType : NSObject, INSCoding
//    {
//        // This sample uses App Groups to share a suite of data between the
//        // main app and the different extensions.
//        protected NSUserDefaults UserDefaults = NSUserDefaultsHelper.DataSuite;
        // To prevent data races, all access to `UserDefaults` uses this queue.
        protected DispatchQueue UserDefaultsAccessQueue = new DispatchQueue("User Defaults Access Queue");
//        // To prevent data races, all access to `UserDefaults` uses this queue.
//        protected DispatchQueue UserDefaultsAccessQueue = new DispatchQueue("User Defaults Access Queue");
        // Storage and observation information.
        protected UserDefaultsStorageDescriptor StorageDescriptor;
//        // Storage and observation information.
//        protected UserDefaultsStorageDescriptor StorageDescriptor;
        // A flag to avoid receiving notifications about data this instance just
        // wrote to `UserDefaults`.
        protected bool IgnoreLocalUserDefaultsChanges = false;
//        // A flag to avoid receiving notifications about data this instance just
//        // wrote to `UserDefaults`.
//        protected bool IgnoreLocalUserDefaultsChanges = false;
        // The observer object handed back after registering to observe a
        // property.
        IDisposable UserDefaultsObserver;
//        // The observer object handed back after registering to observe a
//        // property.
//        IDisposable UserDefaultsObserver;
        // The data managed by this `DataManager`.
        //此“DataManager”管理的数据。
        protected ManagedDataType ManagedDataBackingInstance;
//        // The data managed by this `DataManager`.
//        //此“DataManager”管理的数据。
//        protected ManagedDataType ManagedDataBackingInstance;
        // Access to `managedDataBackingInstance` needs to occur on a dedicated
        // queue to avoid data races.
        protected DispatchQueue DataAccessQueue = new DispatchQueue("Data Access Queue");
//        // Access to `managedDataBackingInstance` needs to occur on a dedicated
//        // queue to avoid data races.
//        protected DispatchQueue DataAccessQueue = new DispatchQueue("Data Access Queue");
        // Public access to the managed data for clients of `DataManager`
        public ManagedDataType ManagedData
        {
            get
            {
                ManagedDataType data = null;
                DataAccessQueue.DispatchSync(() => data = ManagedDataBackingInstance);
                return data;
            }
        }
//        // Public access to the managed data for clients of `DataManager`
//        public ManagedDataType ManagedData
//        {
//            get
//            {
//                ManagedDataType data = null;
//                DataAccessQueue.DispatchSync(() => data = ManagedDataBackingInstance);
//                return data;
//            }
//        }
        // See note below about createInitialData and initialData
        public DataManager(UserDefaultsStorageDescriptor storageDescriptor, ManagedDataType initialData)
        {
            StorageDescriptor = storageDescriptor;
            LoadData();
            if (ManagedDataBackingInstance is null)
            {
                ManagedDataBackingInstance = initialData;
                WriteData();
            }
            ObserveChangesInUserDefaults();
        }
//        // See note below about createInitialData and initialData
//        public DataManager(UserDefaultsStorageDescriptor storageDescriptor, ManagedDataType initialData)
//        {
//            StorageDescriptor = storageDescriptor;
//            LoadData();
//            if (ManagedDataBackingInstance is null)
//            {
//                ManagedDataBackingInstance = initialData;
//                WriteData();
//            }
//            ObserveChangesInUserDefaults();
//        }
        // createInitialData
        //
        // The Swift version of this app has a createInitialData method.
        // Each child class of the DataManager class overrides this method, and
        // then the DataManager base class calls the derived versions to get
        // the initial data. C# gives a compiler warning for this ("Virtual
        // member call in constructor"). Since in C# the base class constructor
        // is run before the child class constructor, having the base clas
        // constructor call out to a method on the derived class is calling
        // a method on an object that has not yet been fully constructed.
        // The C# version of this sample works around this problem by passing
        // in the initial data to the constructor.
//        // createInitialData
//        //
//        // The Swift version of this app has a createInitialData method.
//        // Each child class of the DataManager class overrides this method, and
//        // then the DataManager base class calls the derived versions to get
//        // the initial data. C# gives a compiler warning for this ("Virtual
//        // member call in constructor"). Since in C# the base class constructor
//        // is run before the child class constructor, having the base clas
//        // constructor call out to a method on the derived class is calling
//        // a method on an object that has not yet been fully constructed.
//        // The C# version of this sample works around this problem by passing
//        // in the initial data to the constructor.
        void ObserveChangesInUserDefaults()
        {
            var weakThis = new WeakReference<DataManager<ManagedDataType>>(this);
            Action<NSObservedChange> changeHandler = (change) =>
            {
                if (weakThis.TryGetTarget(out var dataManager))
                {
                    // Ignore any change notifications coming from data this
                    // instance just saved to `NSUserDefaults`.
                    if (dataManager is null || dataManager.IgnoreLocalUserDefaultsChanges)
                    {
                        return;
                    }
//        void ObserveChangesInUserDefaults()
//        {
//            var weakThis = new WeakReference<DataManager<ManagedDataType>>(this);
//            Action<NSObservedChange> changeHandler = (change) =>
//            {
//                if (weakThis.TryGetTarget(out var dataManager))
//                {
//                    // Ignore any change notifications coming from data this
//                    // instance just saved to `NSUserDefaults`.
//                    if (dataManager is null || dataManager.IgnoreLocalUserDefaultsChanges)
//                    {
//                        return;
//                    }
                    // The underlying data changed in `NSUserDefaults`, so
                    // update this instance with the change and notify clients
                    // of the change.
                    dataManager.LoadData();
                    dataManager.NotifyClientsDataChanged();
                }
            };
            UserDefaultsObserver = UserDefaults.AddObserver(
                StorageDescriptor.Key,
                NSKeyValueObservingOptions.Initial | NSKeyValueObservingOptions.New,
                changeHandler
            );
        }
//                    // The underlying data changed in `NSUserDefaults`, so
//                    // update this instance with the change and notify clients
//                    // of the change.
//                    dataManager.LoadData();
//                    dataManager.NotifyClientsDataChanged();
//                }
//            };
//            UserDefaultsObserver = UserDefaults.AddObserver(
//                StorageDescriptor.Key,
//                NSKeyValueObservingOptions.Initial | NSKeyValueObservingOptions.New,
//                changeHandler
//            );
//        }
        // Notifies clients the data changed by posting an `NSNotification` with
        // the key `NotificationKeys.DataChanged`
        void NotifyClientsDataChanged()
        {
            var notification = NSNotification.FromName(NotificationKeys.DataChanged, this);
            NSNotificationCenter.DefaultCenter.PostNotification(notification);
        }
//        // Notifies clients the data changed by posting an `NSNotification` with
//        // the key `NotificationKeys.DataChanged`
//        void NotifyClientsDataChanged()
//        {
//            var notification = NSNotification.FromName(NotificationKeys.DataChanged, this);
//            NSNotificationCenter.DefaultCenter.PostNotification(notification);
//        }
        protected virtual void FinishUnarchiving(NSObject unarchivedData)
        {
            throw new NotImplementedException();
        }
//        protected virtual void FinishUnarchiving(NSObject unarchivedData)
//        {
//            throw new NotImplementedException();
//        }
        // Loads the data from `NSUserDefaults`.
        void LoadData()
        {
            UserDefaultsAccessQueue.DispatchSync(() =>
            {
                NSData archivedData = UserDefaults.DataForKey(StorageDescriptor.Key);
                try
                {
                    // Let the derived classes handle the specifics of
                    // putting the unarchived data in the correct format.
                    // This is necessary because the derived classes
                    // (SoupMenuManager, SoupOrderMenuManager) are using
                    // generic data formats (NSMutableSet<T> or NSMutableArray<T>)
                    // and these types cannot be casted directly from the
                    // deserialized data.
                    NSObject unarchivedData = NSKeyedUnarchiver.UnarchiveObject(archivedData);
                    FinishUnarchiving(unarchivedData);
                }
                catch (Exception e)
                {
                    if (!(e is null))
                    {
                        Console.WriteLine($"Error: {e.Message}");
                    }
                }
            });
        }
//        // Loads the data from `NSUserDefaults`.
//        void LoadData()
//        {
//            UserDefaultsAccessQueue.DispatchSync(() =>
//            {
//                NSData archivedData = UserDefaults.DataForKey(StorageDescriptor.Key);
//                try
//                {
//                    // Let the derived classes handle the specifics of
//                    // putting the unarchived data in the correct format.
//                    // This is necessary because the derived classes
//                    // (SoupMenuManager, SoupOrderMenuManager) are using
//                    // generic data formats (NSMutableSet<T> or NSMutableArray<T>)
//                    // and these types cannot be casted directly from the
//                    // deserialized data.
//                    NSObject unarchivedData = NSKeyedUnarchiver.UnarchiveObject(archivedData);
//                    FinishUnarchiving(unarchivedData);
//                }
//                catch (Exception e)
//                {
//                    if (!(e is null))
//                    {
//                        Console.WriteLine($"Error: {e.Message}");
//                    }
//                }
//            });
//        }
        // Writes the data to `NSUserDefaults`
        protected void WriteData()
        {
            UserDefaultsAccessQueue.DispatchAsync(() =>
            {
                try
                {
                    NSData encodedData = NSKeyedArchiver.ArchivedDataWithRootObject(ManagedDataBackingInstance);
                    IgnoreLocalUserDefaultsChanges = true;
                    UserDefaults.SetValueForKey(encodedData, (NSString)StorageDescriptor.Key);
                    IgnoreLocalUserDefaultsChanges = false;
                    NotifyClientsDataChanged();
                }
                catch (Exception e)
                {
                    throw new Exception($"Could not save data. Reason: {e.Message}");
                }
            });
        }
//        // Writes the data to `NSUserDefaults`
//        protected void WriteData()
//        {
//            UserDefaultsAccessQueue.DispatchAsync(() =>
//            {
//                try
//                {
//                    NSData encodedData = NSKeyedArchiver.ArchivedDataWithRootObject(ManagedDataBackingInstance);
//                    IgnoreLocalUserDefaultsChanges = true;
//                    UserDefaults.SetValueForKey(encodedData, (NSString)StorageDescriptor.Key);
//                    IgnoreLocalUserDefaultsChanges = false;
//                    NotifyClientsDataChanged();
//                }
//                catch (Exception e)
//                {
//                    throw new Exception($"Could not save data. Reason: {e.Message}");
//                }
//            });
//        }
    }
}
//    }
//}