| | |
| | | /* |
| | | 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}"); |
| | | // } |
| | | // }); |
| | | // } |
| | | |
| | | |
| | | |
| | | } |
| | | } |
| | | // } |
| | | //} |