diff --git a/MKiCloudSync.h b/MKiCloudSync.h index db98c7b..c8e4eb8 100644 --- a/MKiCloudSync.h +++ b/MKiCloudSync.h @@ -1,20 +1,21 @@ // // MKiCloudSync.h -// iCloud1 // -// Created by Mugunth Kumar (@mugunthkumar) on 20/11/11. +// Created by Mugunth Kumar on 11/20//11. +// Modified by Alexsander Akers on 1/4/12. +// // Copyright (C) 2011-2020 by Steinlogic - +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,23 +24,22 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -// As a side note, you might also consider -// 1) tweeting about this mentioning @mugunthkumar -// 2) A paypal donation to mugunth.kumar@gmail.com +#import -#ifdef DEBUG -# define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); -#else -# define DLog(...) -#endif +// Change to 1 to enable logging +#define MKiCloudSyncDebug 0 -// ALog always displays output regardless of the DEBUG setting -#define ALog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); - -#import -#define kMKiCloudSyncNotification @"MKiCloudSyncDidUpdateToLatest" +// Posted +extern NSString *const MKiCloudSyncDidUpdateNotification; @interface MKiCloudSync : NSObject -+(void) start; ++ (BOOL) isSyncing; ++ (BOOL) start; + ++ (NSMutableSet *) ignoredKeys; + ++ (void) cleanUbiquitousStore; ++ (void) stop; + @end diff --git a/MKiCloudSync.m b/MKiCloudSync.m index b42f9db..b8a4a98 100644 --- a/MKiCloudSync.m +++ b/MKiCloudSync.m @@ -1,20 +1,21 @@ // // MKiCloudSync.m -// iCloud1 // -// Created by Mugunth Kumar (@mugunthkumar) on 20/11/11. +// Created by Mugunth Kumar on 11/20//11. +// Modified by Alexsander Akers on 1/4/12. +// // Copyright (C) 2011-2020 by Steinlogic - +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,86 +24,176 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -// As a side note, you might also consider -// 1) tweeting about this mentioning @mugunthkumar -// 2) A paypal donation to mugunth.kumar@gmail.com +#import "MKiCloudSync.h" +NSString *const MKiCloudSyncDidUpdateNotification = @"MKiCloudSyncDidUpdateNotification"; -#import "MKiCloudSync.h" +static BOOL _isSyncing; +static dispatch_queue_t _queue; + +@interface MKiCloudSync () + ++ (BOOL) tryToStartSync; + ++ (void) pullFromICloud: (NSNotification *) note; ++ (void) pushToICloud; + +@end @implementation MKiCloudSync -+(void) updateToiCloud:(NSNotification*) notificationObject { - - NSDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]; - - [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - - [[NSUbiquitousKeyValueStore defaultStore] setObject:obj forKey:key]; - }]; - - [[NSUbiquitousKeyValueStore defaultStore] synchronize]; ++ (BOOL) isSyncing +{ + __block BOOL isSyncing = NO; + + dispatch_sync(_queue, ^{ + isSyncing = _isSyncing; + }); + + return isSyncing; } ++ (BOOL) start +{ + if ([NSUbiquitousKeyValueStore class] && [NSUbiquitousKeyValueStore defaultStore] && [self tryToStartSync]) + { +#if MKiCloudSyncDebug + NSLog(@"MKiCloudSync: Will start sync"); +#endif + + // Force push + [MKiCloudSync pushToICloud]; + + // Force pull + NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; + NSDictionary *dict = [store dictionaryRepresentation]; + + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + [dict enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) { + [userDefaults setObject: obj forKey: key]; + }]; + [userDefaults synchronize]; + + NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; + + // Post notification + [dnc postNotificationName: MKiCloudSyncDidUpdateNotification object: self]; -+(void) updateFromiCloud:(NSNotification*) notificationObject { - - NSUbiquitousKeyValueStore *iCloudStore = [NSUbiquitousKeyValueStore defaultStore]; - NSDictionary *dict = [iCloudStore dictionaryRepresentation]; - - // prevent NSUserDefaultsDidChangeNotification from being posted while we update from iCloud - - [[NSNotificationCenter defaultCenter] removeObserver:self - name:NSUserDefaultsDidChangeNotification - object:nil]; - - [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - - [[NSUserDefaults standardUserDefaults] setObject:obj forKey:key]; - }]; - - [[NSUserDefaults standardUserDefaults] synchronize]; - - // enable NSUserDefaultsDidChangeNotification notifications again - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateToiCloud:) - name:NSUserDefaultsDidChangeNotification - object:nil]; - - [[NSNotificationCenter defaultCenter] postNotificationName:kMKiCloudSyncNotification object:nil]; + // Add self as observer + [dnc addObserver: self selector: @selector(pullFromICloud:) name: NSUbiquitousKeyValueStoreDidChangeExternallyNotification object: store]; + [dnc addObserver: self selector: @selector(pushToICloud) name: NSUserDefaultsDidChangeNotification object: nil]; + +#if MKiCloudSyncDebug + NSLog(@"MKiCloudSync: Did start sync"); +#endif + return YES; + } + + return NO; } ++ (BOOL) tryToStartSync +{ + __block BOOL didSucceed = NO; + + dispatch_sync(_queue, ^{ + if (!_isSyncing) + { + _isSyncing = YES; + didSucceed = YES; + } + }); -+(void) start { - - if(NSClassFromString(@"NSUbiquitousKeyValueStore")) { // is iOS 5? - - if([NSUbiquitousKeyValueStore defaultStore]) { // is iCloud enabled - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateFromiCloud:) - name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification - object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateToiCloud:) - name:NSUserDefaultsDidChangeNotification object:nil]; - } else { - DLog(@"iCloud not enabled"); - } - } - else { - DLog(@"Not an iOS 5 device"); - } + return didSucceed; } -+ (void) dealloc { - - [[NSNotificationCenter defaultCenter] removeObserver:self - name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification - object:nil]; ++ (NSMutableSet *) ignoredKeys +{ + static NSMutableSet *ignoredKeys; + static dispatch_once_t token; + dispatch_once(&token, ^{ + ignoredKeys = [NSMutableSet new]; + }); + + return ignoredKeys; +} - [[NSNotificationCenter defaultCenter] removeObserver:self - name:NSUserDefaultsDidChangeNotification - object:nil]; ++ (void) cleanUbiquitousStore +{ + [self stop]; + + NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; + NSDictionary *dict = [store dictionaryRepresentation]; + + NSMutableSet *keys = [NSMutableSet setWithArray: [dict allKeys]]; + [keys minusSet: [self ignoredKeys]]; + + [keys enumerateObjectsUsingBlock: ^(NSString *key, BOOL *stop) { + [store removeObjectForKey: key]; + }]; + [store synchronize]; + +#if MKiCloudSyncDebug + NSLog(@"MKiCloudSync: Cleaned ubiquitous store"); +#endif +} ++ (void) initialize +{ + if (self == [MKiCloudSync class]) + { + _isSyncing = NO; + _queue = dispatch_queue_create("com.mugunthkumar.MKiCloudSync", DISPATCH_QUEUE_SERIAL); + } } ++ (void) pullFromICloud: (NSNotification *) note +{ + NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; + [dnc removeObserver: self name: NSUserDefaultsDidChangeNotification object: nil]; + + NSUbiquitousKeyValueStore *store = note.object; + NSArray *changedKeys = [note.userInfo objectForKey: NSUbiquitousKeyValueStoreChangedKeysKey]; + +#if MKiCloudSyncDebug + NSLog(@"MKiCloudSync: Pulled from iCloud"); +#endif + + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + [changedKeys enumerateObjectsUsingBlock: ^(NSString *key, NSUInteger idx, BOOL *stop) { + id obj = [store objectForKey: key]; + [userDefaults setObject: obj forKey: key]; + }]; + [userDefaults synchronize]; + + [dnc addObserver: self selector: @selector(pushToICloud) name: NSUserDefaultsDidChangeNotification object: nil]; + [dnc postNotificationName: MKiCloudSyncDidUpdateNotification object: nil]; +} ++ (void) pushToICloud +{ + NSString *identifier = [[NSBundle mainBundle] bundleIdentifier]; + + NSMutableDictionary *persistentDomain = [[[NSUserDefaults standardUserDefaults] persistentDomainForName: identifier] mutableCopy]; + + NSArray *ignoredKeys = [[self ignoredKeys] allObjects]; + [persistentDomain removeObjectsForKeys: ignoredKeys]; + + NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; + [persistentDomain enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) { + [store setObject: obj forKey: key]; + }]; + [store synchronize]; + +#if MKiCloudSyncDebug + NSLog(@"MKiCloudSync: Pushed to iCloud"); +#endif +} ++ (void) stop +{ + dispatch_sync(_queue, ^{ + _isSyncing = NO; + [[NSNotificationCenter defaultCenter] removeObserver: self]; + +#if MKiCloudSyncDebug + NSLog(@"MKiCloudSync: Stopped syncing with iCloud"); +#endif + }); +} + @end diff --git a/MKiCloudSync.podspec b/MKiCloudSync.podspec new file mode 100644 index 0000000..2787003 --- /dev/null +++ b/MKiCloudSync.podspec @@ -0,0 +1,13 @@ +Pod::Spec.new do |s| + s.name = 'MKiCloudSync' + s.version = '1.0.2' + s.license = 'MIT' + s.summary = 'Sync your NSUserDefaults to iCloud automatically.' + s.homepage = 'https://github.com/pandamonia/MKiCloudSync' + s.author = { 'Alexsander Akers' => 'a2@pandamonia.us', + 'Mugunth Kumar' => 'contact@mk.sg' } + s.source = { :git => 'https://github.com/pandamonia/MKiCloudSync.git', :tag => 'v1.0.2' } + s.platform = :ios + s.source_files = 'MKiCloudSync.{h,m}' + s.requires_arc = true +end diff --git a/README.mdown b/README.mdown index 26ccd53..278310c 100644 --- a/README.mdown +++ b/README.mdown @@ -1,34 +1,33 @@ -### What is this? +# MKiCloudSync + +## What is it? A clean and simple class to sync NSUserDefaults to iCloud. -### How to use? -1. Drag the two classes and #import the header file in your Application Delegate -2. Call [MKiCloudSync start]; -3. There is no Step 3. +## How do you use it? +1. Copy *MKiCloudSync.h* and *MKiCloudSync.m* into your Xcode project. +2. Call `[MKiCloudSync start]`. +3. There is no step 3. -Read the [blog post](http://blog.mugunthkumar.com/coding/ios-code-mkicloudsync-sync-your-nsuserdefaults-to-icloud-with-a-single-line-of-code) for more -###License -// Copyright (C) 2011-2020 by Steinlogic +Read the [blog post](http://blog.mugunthkumar.com/coding/ios-code-mkicloudsync-sync-your-nsuserdefaults-to-icloud-with-a-single-line-of-code) for more information. -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. +## License -// As a side note, you might also consider -// 1) tweeting about this mentioning @mugunthkumar -// 2) A paypal donation to mugunth.kumar@gmail.com - +> Copyright (c) 2011–2012 by Steinlogic and MKiCloudSync Contributors +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. \ No newline at end of file