├── .gitignore ├── CHANGELOG.md ├── Example └── MMWormhole │ ├── MMWormhole Watch App │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── Info.plist │ └── Interface.storyboard │ ├── MMWormhole WatchKit Extension │ ├── Images.xcassets │ │ └── MyImage.imageset │ │ │ └── Contents.json │ ├── Info.plist │ ├── InterfaceController.h │ ├── InterfaceController.m │ └── MMWormhole WatchKit Extension.entitlements │ ├── MMWormhole.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ ├── MMWormhole Today Extension.xcscheme │ │ ├── MMWormhole Watch App.xcscheme │ │ ├── MMWormhole watchOS App.xcscheme │ │ └── MMWormhole.xcscheme │ ├── MMWormhole │ ├── .DS_Store │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── MMWormhole.entitlements │ ├── ViewController.h │ ├── ViewController.m │ └── main.m │ ├── MMWormholeTests │ ├── Info.plist │ ├── MMWormholeCoordinatedFileTransitingTests.m │ ├── MMWormholeFileTransitingTests.m │ └── MMWormholeTests.m │ ├── Today Extension │ ├── Info.plist │ ├── MainInterface.storyboard │ ├── Today Extension.entitlements │ ├── TodayViewController.h │ └── TodayViewController.m │ ├── watchOS Extension │ ├── Assets.xcassets │ │ └── Complication.complicationset │ │ │ ├── Circular.imageset │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Modular.imageset │ │ │ └── Contents.json │ │ │ └── Utilitarian.imageset │ │ │ └── Contents.json │ ├── ExtensionDelegate.h │ ├── ExtensionDelegate.m │ ├── Info.plist │ ├── InterfaceController.h │ ├── InterfaceController.m │ ├── PushNotificationPayload.apns │ └── watchOS Extension.entitlements │ └── watchOS │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ └── Interface.storyboard │ └── Info.plist ├── LICENSE ├── MMWormhole.gif ├── MMWormhole.podspec ├── MMWormhole ├── MMWormhole.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ ├── MMWormhole-Mac.xcscheme │ │ └── MMWormhole-iOS.xcscheme └── MMWormhole │ ├── Info.plist │ ├── MMWormholeUmbrella.h │ └── module.modulemap ├── MMWormhole_correct.png ├── MMWormhole_incorrect.png ├── README.md └── Source ├── MMWormhole.h ├── MMWormhole.m ├── MMWormholeCoordinatedFileTransiting.h ├── MMWormholeCoordinatedFileTransiting.m ├── MMWormholeFileTransiting.h ├── MMWormholeFileTransiting.m ├── MMWormholeSession.h ├── MMWormholeSession.m ├── MMWormholeSessionContextTransiting.h ├── MMWormholeSessionContextTransiting.m ├── MMWormholeSessionFileTransiting.h ├── MMWormholeSessionFileTransiting.m ├── MMWormholeSessionMessageTransiting.h ├── MMWormholeSessionMessageTransiting.m └── MMWormholeTransiting.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | # CocoaPods 20 | # 21 | # We recommend against adding the Pods directory to your .gitignore. However 22 | # you should judge for yourself, the pros and cons are mentioned at: 23 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 24 | # 25 | # Pods/ 26 | .DS_Store 27 | Example/.DS_Store 28 | Example/MMWormhole/.DS_Store -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ##[2.0.0](https://github.com/mutualmobile/MMWormhole/milestones/2.0.0) (Tuesday, September 15th, 2015) 2 | **NEW** 3 | * Added support for the WatchConnectivity framework. (Conrad Stoll) 4 | 5 | **Fixed** 6 | * **FIXED** an issue ([#53](https://github.com/mutualmobile/MMWormhole/pull/53)) with an assertion warning on release builds. (Timothy Sanders) 7 | * **FIXED** an issue ([#59](https://github.com/mutualmobile/MMWormhole/pull/59)) with the Carthage build configuration. (Thomas Guthrie, Stephen Wu) 8 | 9 | 10 | ##[1.2.0](https://github.com/mutualmobile/MMWormhole/milestones/1.2.0) (Tuesday, June 2nd, 2015) 11 | **NEW** 12 | * Added support for sending a notification by passing nil as the message. (Felix Lamouroux) 13 | * Added support for Carthage dependency management. (Lei Wang) 14 | * Added support for subclassing MMWormhole's message passing system via MMWormholeTransiting. (Conrad Stoll) 15 | * Added support for NSFileCoordinator message file writing. (Conrad Stoll 16 | * Added new troubleshooting checks for App Group configuration. (Ernesto Torres) 17 | * Added nullability annotations for better Swift support. (Timothy Sanders) 18 | * Silenced a c function declaration warning. (Wes Ostler) 19 | * Updated README to include Swift examples. (Nate McGuire) 20 | 21 | **Fixed** 22 | * **FIXED** an issue ([#36](https://github.com/mutualmobile/MMWormhole/pull/36)) where a listener block could be called multiple times. (Naldikt) 23 | * **FIXED** an issue ([#43](https://github.com/mutualmobile/MMWormhole/issues/43)) where a listener block could be registered and called multiple times. (Peter De Bock) 24 | * **FIXED** a typo in the README. (Marcus Mattsson) 25 | 26 | 27 | ##[1.1.1](https://github.com/mutualmobile/MMWormhole/milestones/1.1.1) (Friday, February 13th, 2015) 28 | **NEW** 29 | * Added support for OS X in CocoaPods. (ConfusedVorlon) 30 | * Cleaned up the public headers and init method. (Jérôme Morissard) 31 | * Removed duplicated space. (Christian Sampaio) 32 | * Updated for latest beta of WatchKit. (Fadhel Chaabane) 33 | 34 | 35 | ##[1.1.0](https://github.com/mutualmobile/MMWormhole/milestones/1.1.0) (Saturday, December 13th, 2014) 36 | **NEW** 37 | * Added support for iOS 7 deployment targets. (Orta Therox) 38 | * Added full support for NSCoding and NSKeyedArchiver. (Martin Blech) 39 | * Added Unit Tests! (Conrad Stoll) 40 | 41 | **Fixed** 42 | * **FIXED** an issue([#6](https://github.com/mutualmobile/MMWormhole/pull/6)) where clear all message contents wasn't working properly. (Conrad Stoll) 43 | 44 | 45 | ##1.0.0 (Monday, December 8th, 2014) 46 | * Initial Library Release -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole Watch App/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "24x24", 5 | "idiom" : "watch", 6 | "scale" : "2x", 7 | "role" : "notificationCenter", 8 | "subtype" : "38mm" 9 | }, 10 | { 11 | "size" : "27.5x27.5", 12 | "idiom" : "watch", 13 | "scale" : "2x", 14 | "role" : "notificationCenter", 15 | "subtype" : "42mm" 16 | }, 17 | { 18 | "size" : "29x29", 19 | "idiom" : "watch", 20 | "role" : "companionSettings", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "size" : "29x29", 25 | "idiom" : "watch", 26 | "role" : "companionSettings", 27 | "scale" : "3x" 28 | }, 29 | { 30 | "size" : "40x40", 31 | "idiom" : "watch", 32 | "scale" : "2x", 33 | "role" : "appLauncher", 34 | "subtype" : "38mm" 35 | }, 36 | { 37 | "size" : "44x44", 38 | "idiom" : "watch", 39 | "scale" : "2x", 40 | "role" : "longLook", 41 | "subtype" : "42mm" 42 | }, 43 | { 44 | "size" : "86x86", 45 | "idiom" : "watch", 46 | "scale" : "2x", 47 | "role" : "quickLook", 48 | "subtype" : "38mm" 49 | }, 50 | { 51 | "size" : "98x98", 52 | "idiom" : "watch", 53 | "scale" : "2x", 54 | "role" : "quickLook", 55 | "subtype" : "42mm" 56 | } 57 | ], 58 | "info" : { 59 | "version" : 1, 60 | "author" : "xcode" 61 | } 62 | } -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole Watch App/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | 4 | ], 5 | "info" : { 6 | "version" : 1, 7 | "author" : "xcode" 8 | } 9 | } -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole Watch App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | MMWormhole 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 2.0.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | UISupportedInterfaceOrientations 26 | 27 | UIInterfaceOrientationPortrait 28 | UIInterfaceOrientationPortraitUpsideDown 29 | 30 | WKCompanionAppBundleIdentifier 31 | com.mutualmobile.MMWormhole 32 | WKWatchKitApp 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole Watch App/Interface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole WatchKit Extension/Images.xcassets/MyImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x" 14 | } 15 | ], 16 | "info" : { 17 | "version" : 1, 18 | "author" : "xcode" 19 | } 20 | } -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole WatchKit Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | MMWormhole WatchKit Extension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 2.0.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | WKAppBundleIdentifier 30 | com.mutualmobile.MMWormhole.watchosapp 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.watchkit 34 | 35 | RemoteInterfacePrincipalClass 36 | InterfaceController 37 | 38 | 39 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole WatchKit Extension/InterfaceController.h: -------------------------------------------------------------------------------- 1 | // 2 | // InterfaceController.h 3 | // MMWormhole WatchKit Extension 4 | // 5 | // Created by Conrad Stoll on 12/6/14. 6 | // Copyright (c) 2014 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface InterfaceController : WKInterfaceController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole WatchKit Extension/InterfaceController.m: -------------------------------------------------------------------------------- 1 | // 2 | // InterfaceController.m 3 | // MMWormhole WatchKit Extension 4 | // 5 | // Created by Conrad Stoll on 12/6/14. 6 | // Copyright (c) 2014 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import "InterfaceController.h" 10 | 11 | #import "MMWormhole.h" 12 | 13 | @interface InterfaceController() 14 | 15 | @property (nonatomic, strong) MMWormhole *wormhole; 16 | @property (nonatomic, weak) IBOutlet WKInterfaceLabel *selectionLabel; 17 | @end 18 | 19 | 20 | @implementation InterfaceController 21 | 22 | // 23 | // Change for compatibility with XCode 6.2 Beta 3 24 | // The WKInterfaceController method initWithContext: has been deprecated. 25 | // Please use awakeWithContext: instead. 26 | // 27 | - (void)awakeWithContext:(id)context { 28 | [super awakeWithContext:context]; 29 | 30 | // Initialize the wormhole 31 | self.wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole" 32 | optionalDirectory:@"wormhole"]; 33 | 34 | // Obtain an initial value for the selection message from the wormhole 35 | id messageObject = [self.wormhole messageWithIdentifier:@"selection"]; 36 | NSString *string = [messageObject valueForKey:@"selectionString"]; 37 | 38 | if (string != nil) { 39 | [self.selectionLabel setText:string]; 40 | } 41 | 42 | // Listen for changes to the selection message. The selection message contains a string value 43 | // identified by the selectionString key. Note that the type of the key is included in the 44 | // name of the key. 45 | [self.wormhole listenForMessageWithIdentifier:@"selection" listener:^(id messageObject) { 46 | NSString *string = [messageObject valueForKey:@"selectionString"]; 47 | 48 | if (string != nil) { 49 | [self.selectionLabel setText:string]; 50 | } 51 | }]; 52 | } 53 | 54 | 55 | // Pass messages each time a button is tapped using the identifier button 56 | // The messages contain a single number value with the buttonNumber key 57 | - (IBAction)didTapOne:(id)sender { 58 | [self.wormhole passMessageObject:@{@"buttonNumber" : @(1)} identifier:@"button"]; 59 | } 60 | 61 | - (IBAction)didTapTwo:(id)sender { 62 | [self.wormhole passMessageObject:@{@"buttonNumber" : @(2)} identifier:@"button"]; 63 | } 64 | 65 | - (IBAction)didTapThree:(id)sender { 66 | [self.wormhole passMessageObject:@{@"buttonNumber" : @(3)} identifier:@"button"]; 67 | } 68 | 69 | @end 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole WatchKit Extension/MMWormhole WatchKit Extension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.com.mutualmobile.wormhole 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole.xcodeproj/xcshareddata/xcschemes/MMWormhole Today Extension.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 70 | 72 | 78 | 79 | 80 | 81 | 82 | 83 | 90 | 92 | 98 | 99 | 100 | 101 | 103 | 104 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole.xcodeproj/xcshareddata/xcschemes/MMWormhole Watch App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 71 | 72 | 83 | 87 | 93 | 94 | 95 | 96 | 102 | 103 | 104 | 105 | 106 | 107 | 113 | 117 | 123 | 124 | 125 | 126 | 132 | 133 | 134 | 135 | 137 | 138 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole.xcodeproj/xcshareddata/xcschemes/MMWormhole watchOS App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 57 | 63 | 64 | 65 | 66 | 67 | 72 | 73 | 74 | 75 | 81 | 82 | 83 | 84 | 85 | 86 | 96 | 100 | 106 | 107 | 108 | 109 | 115 | 116 | 117 | 118 | 119 | 120 | 126 | 130 | 136 | 137 | 138 | 139 | 145 | 146 | 147 | 148 | 150 | 151 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole.xcodeproj/xcshareddata/xcschemes/MMWormhole.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mutualmobile/MMWormhole/1405cb1dd7b07f1023bb81e445002bcc5e7ece56/Example/MMWormhole/MMWormhole/.DS_Store -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // MMWormhole 4 | // 5 | // Created by Conrad Stoll on 12/6/14. 6 | // Copyright (c) 2014 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // MMWormhole 4 | // 5 | // Created by Conrad Stoll on 12/6/14. 6 | // Copyright (c) 2014 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 29 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 2.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole/MMWormhole.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.com.mutualmobile.wormhole 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // MMWormhole 4 | // 5 | // Created by Conrad Stoll on 12/6/14. 6 | // Copyright (c) 2014 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // MMWormhole 4 | // 5 | // Created by Conrad Stoll on 12/6/14. 6 | // Copyright (c) 2014 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | #import "MMWormhole.h" 12 | #import "MMWormholeSession.h" 13 | 14 | @interface ViewController () 15 | 16 | @property (nonatomic, weak) IBOutlet UILabel *numberLabel; 17 | @property (nonatomic, weak) IBOutlet UISegmentedControl *segmentedControl; 18 | 19 | @property (nonatomic, strong) MMWormhole *traditionalWormhole; 20 | @property (nonatomic, strong) MMWormhole *watchConnectivityWormhole; 21 | @property (nonatomic, strong) MMWormholeSession *watchConnectivityListeningWormhole; 22 | 23 | @end 24 | 25 | @implementation ViewController 26 | 27 | - (void)viewDidLoad { 28 | [super viewDidLoad]; 29 | 30 | // Initialize the wormhole 31 | self.traditionalWormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole" 32 | optionalDirectory:@"wormhole"]; 33 | 34 | // Initialize the MMWormholeSession listening wormhole. 35 | // You are required to do this before creating a Wormhole with the Session Transiting Type, as we are below. 36 | self.watchConnectivityListeningWormhole = [MMWormholeSession sharedListeningSession]; 37 | 38 | // Initialize the wormhole using the WatchConnectivity framework's application context transiting type 39 | self.watchConnectivityWormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole" 40 | optionalDirectory:@"wormhole" 41 | transitingType:MMWormholeTransitingTypeSessionContext]; 42 | 43 | // Become a listener for changes to the wormhole for the button message 44 | [self.traditionalWormhole listenForMessageWithIdentifier:@"button" listener:^(id messageObject) { 45 | // The number is identified with the buttonNumber key in the message object 46 | NSNumber *number = [messageObject valueForKey:@"buttonNumber"]; 47 | self.numberLabel.text = [number stringValue]; 48 | }]; 49 | 50 | // Become a listener for changes to the wormhole for the button message 51 | [self.watchConnectivityListeningWormhole listenForMessageWithIdentifier:@"button" listener:^(id messageObject) { 52 | // The number is identified with the buttonNumber key in the message object 53 | NSNumber *number = [messageObject valueForKey:@"buttonNumber"]; 54 | self.numberLabel.text = [number stringValue]; 55 | }]; 56 | 57 | // Make sure we are activating the listening wormhole so that it will receive new messages from 58 | // the WatchConnectivity framework. 59 | [self.watchConnectivityListeningWormhole activateSessionListening]; 60 | 61 | [self segmentedControlValueDidChange:self.segmentedControl]; 62 | } 63 | 64 | - (void)viewDidAppear:(BOOL)animated { 65 | [super viewDidAppear:animated]; 66 | 67 | // Obtain an initial message from the wormhole 68 | id messageObject = [self.traditionalWormhole messageWithIdentifier:@"button"]; 69 | NSNumber *number = [messageObject valueForKey:@"buttonNumber"]; 70 | 71 | self.numberLabel.text = [number stringValue]; 72 | 73 | // Obtain an initial message from the wormhole 74 | messageObject = [self.watchConnectivityWormhole messageWithIdentifier:@"button"]; 75 | number = [messageObject valueForKey:@"buttonNumber"]; 76 | 77 | self.numberLabel.text = [number stringValue]; 78 | } 79 | 80 | - (IBAction)segmentedControlValueDidChange:(UISegmentedControl *)segmentedControl { 81 | NSString *title = [segmentedControl titleForSegmentAtIndex:segmentedControl.selectedSegmentIndex]; 82 | 83 | // Pass a message for the selection identifier. The message itself is a NSCoding compliant object 84 | // with a single value and key called selectionString. 85 | [self.traditionalWormhole passMessageObject:@{@"selectionString" : title} identifier:@"selection"]; 86 | [self.watchConnectivityWormhole passMessageObject:@{@"selectionString" : title} identifier:@"selection"]; 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormhole/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // MMWormhole 4 | // 5 | // Created by Conrad Stoll on 12/6/14. 6 | // Copyright (c) 2014 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormholeTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormholeTests/MMWormholeCoordinatedFileTransitingTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeCoordinatedFileTransitingTests.m 3 | // MMWormhole 4 | // 5 | // Created by Conrad Stoll on 5/27/15. 6 | // Copyright (c) 2015 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "MMWormhole.h" 12 | 13 | #define ApplicationGroupIdentifier @"group.com.mutualmobile.wormhole" 14 | 15 | @interface MMWormholeCoordinatedFileTransitingTests : XCTestCase 16 | 17 | @end 18 | 19 | @implementation MMWormholeCoordinatedFileTransitingTests 20 | 21 | - (void)setUp { 22 | [super setUp]; 23 | // Put setup code here. This method is called before the invocation of each test method in the class. 24 | } 25 | 26 | - (void)tearDown { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | [super tearDown]; 29 | } 30 | 31 | - (void)testMessagePassingDirectory { 32 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 33 | optionalDirectory:@"testDirectory"]; 34 | MMWormholeCoordinatedFileTransiting *transiting = [[MMWormholeCoordinatedFileTransiting alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier optionalDirectory:@"testDirectory"]; 35 | 36 | wormhole.wormholeMessenger = transiting; 37 | 38 | NSString *messagePassingDirectoryPath = [transiting messagePassingDirectoryPath]; 39 | 40 | NSString *lastComponent = [[messagePassingDirectoryPath pathComponents] lastObject]; 41 | 42 | XCTAssert([lastComponent isEqualToString:@"testDirectory"], @"Message Passing Directory path should contain optional directory."); 43 | } 44 | 45 | - (void)testFilePathForIdentifier { 46 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 47 | optionalDirectory:@"testDirectory"]; 48 | MMWormholeCoordinatedFileTransiting *transiting = [[MMWormholeCoordinatedFileTransiting alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier optionalDirectory:@"testDirectory"]; 49 | 50 | wormhole.wormholeMessenger = transiting; 51 | 52 | NSString *filePathForIdentifier = [transiting filePathForIdentifier:@"testIdentifier"]; 53 | 54 | NSString *lastComponent = [[filePathForIdentifier pathComponents] lastObject]; 55 | 56 | XCTAssert([lastComponent isEqualToString:@"testIdentifier.archive"], @"File Path Identifier should equal the passed identifier with the .archive extension."); 57 | } 58 | 59 | - (void)testFilePathForNilIdentifier { 60 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 61 | optionalDirectory:@"testDirectory"]; 62 | MMWormholeCoordinatedFileTransiting *transiting = [[MMWormholeCoordinatedFileTransiting alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier optionalDirectory:@"testDirectory"]; 63 | 64 | wormhole.wormholeMessenger = transiting; 65 | 66 | NSString *filePathForIdentifier = [transiting filePathForIdentifier:nil]; 67 | 68 | NSString *lastComponent = [[filePathForIdentifier pathComponents] lastObject]; 69 | 70 | XCTAssertNil(lastComponent, @"File Path Identifier should be nil if no identifier is provided."); 71 | } 72 | 73 | - (void)testValidMessagePassing { 74 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 75 | optionalDirectory:@"testDirectory"]; 76 | MMWormholeCoordinatedFileTransiting *transiting = [[MMWormholeCoordinatedFileTransiting alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier optionalDirectory:@"testDirectory"]; 77 | 78 | wormhole.wormholeMessenger = transiting; 79 | 80 | [transiting deleteContentForIdentifier:@"testIdentifier"]; 81 | 82 | id messageObject = [transiting messageObjectForIdentifier:@"testIdentifier"]; 83 | 84 | XCTAssertNil(messageObject, @"Message object should be nil after deleting file."); 85 | 86 | NSString *filePathForIdentifier = [transiting filePathForIdentifier:@"testIdentifier"]; 87 | 88 | [wormhole passMessageObject:@{} identifier:@"testIdentifier"]; 89 | 90 | NSData *data = [NSData dataWithContentsOfFile:filePathForIdentifier]; 91 | 92 | XCTAssertNotNil(data, @"Message contents should not be nil after passing a valid message."); 93 | } 94 | 95 | - (void)testFileWriting { 96 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 97 | optionalDirectory:@"testDirectory"]; 98 | MMWormholeCoordinatedFileTransiting *transiting = [[MMWormholeCoordinatedFileTransiting alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier optionalDirectory:@"testDirectory"]; 99 | 100 | wormhole.wormholeMessenger = transiting; 101 | 102 | [transiting deleteContentForIdentifier:@"testIdentifier"]; 103 | 104 | id messageObject = [transiting messageObjectForIdentifier:@"testIdentifier"]; 105 | 106 | XCTAssertNil(messageObject, @"Message object should be nil after deleting file."); 107 | 108 | NSString *filePathForIdentifier = [transiting filePathForIdentifier:@"testIdentifier"]; 109 | 110 | [transiting writeMessageObject:@{} forIdentifier:@"testIdentifier"]; 111 | 112 | NSData *data = [NSData dataWithContentsOfFile:filePathForIdentifier]; 113 | 114 | XCTAssertNotNil(data, @"Message contents should not be nil after writing a valid message to disk."); 115 | } 116 | 117 | - (void)testClearingIndividualMessage { 118 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 119 | optionalDirectory:@"testDirectory"]; 120 | MMWormholeCoordinatedFileTransiting *transiting = [[MMWormholeCoordinatedFileTransiting alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier optionalDirectory:@"testDirectory"]; 121 | 122 | wormhole.wormholeMessenger = transiting; 123 | 124 | [wormhole passMessageObject:@{} identifier:@"testIdentifier"]; 125 | 126 | id messageObject = [transiting messageObjectForIdentifier:@"testIdentifier"]; 127 | 128 | XCTAssertNotNil(messageObject, @"Message contents should not be nil after passing a valid message."); 129 | 130 | [wormhole clearMessageContentsForIdentifier:@"testIdentifier"]; 131 | 132 | NSString *filePathForIdentifier = [transiting filePathForIdentifier:@"testIdentifier"]; 133 | 134 | NSData *data = [NSData dataWithContentsOfFile:filePathForIdentifier]; 135 | 136 | XCTAssertNil(data, @"Message file should be gone after deleting message."); 137 | 138 | id deletedMessageObject = [transiting messageObjectForIdentifier:@"testIdentifier"]; 139 | 140 | XCTAssertNil(deletedMessageObject, @"Message object should be nil after deleting message."); 141 | } 142 | 143 | - (void)testClearingAllMessages { 144 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 145 | optionalDirectory:@"testDirectory"]; 146 | MMWormholeCoordinatedFileTransiting *transiting = [[MMWormholeCoordinatedFileTransiting alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier optionalDirectory:@"testDirectory"]; 147 | 148 | wormhole.wormholeMessenger = transiting; 149 | 150 | [wormhole passMessageObject:@{} identifier:@"testIdentifier1"]; 151 | [wormhole passMessageObject:@{} identifier:@"testIdentifier2"]; 152 | [wormhole passMessageObject:@{} identifier:@"testIdentifier3"]; 153 | 154 | [wormhole clearAllMessageContents]; 155 | 156 | id deletedMessageObject1 = [transiting messageObjectForIdentifier:@"testIdentifier1"]; 157 | id deletedMessageObject2 = [transiting messageObjectForIdentifier:@"testIdentifier2"]; 158 | id deletedMessageObject3 = [transiting messageObjectForIdentifier:@"testIdentifier3"]; 159 | 160 | XCTAssertNil(deletedMessageObject1, @"Message object should be nil after deleting message."); 161 | XCTAssertNil(deletedMessageObject2, @"Message object should be nil after deleting message."); 162 | XCTAssertNil(deletedMessageObject3, @"Message object should be nil after deleting message."); 163 | } 164 | 165 | @end 166 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormholeTests/MMWormholeFileTransitingTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeFileTransitingTests.m 3 | // MMWormhole 4 | // 5 | // Created by Conrad Stoll on 5/27/15. 6 | // Copyright (c) 2015 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "MMWormhole.h" 12 | 13 | #define ApplicationGroupIdentifier @"group.com.mutualmobile.wormhole" 14 | 15 | @interface MMWormholeFileTransitingTests : XCTestCase 16 | 17 | @end 18 | 19 | @implementation MMWormholeFileTransitingTests 20 | 21 | - (void)setUp { 22 | [super setUp]; 23 | // Put setup code here. This method is called before the invocation of each test method in the class. 24 | } 25 | 26 | - (void)tearDown { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | [super tearDown]; 29 | } 30 | 31 | - (void)testMessagePassingDirectory { 32 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 33 | optionalDirectory:@"testDirectory"]; 34 | MMWormholeFileTransiting *transiting = wormhole.wormholeMessenger; 35 | 36 | NSString *messagePassingDirectoryPath = [transiting messagePassingDirectoryPath]; 37 | 38 | NSString *lastComponent = [[messagePassingDirectoryPath pathComponents] lastObject]; 39 | 40 | XCTAssert([lastComponent isEqualToString:@"testDirectory"], @"Message Passing Directory path should contain optional directory."); 41 | } 42 | 43 | - (void)testFilePathForIdentifier { 44 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 45 | optionalDirectory:@"testDirectory"]; 46 | MMWormholeFileTransiting *transiting = wormhole.wormholeMessenger; 47 | 48 | NSString *filePathForIdentifier = [transiting filePathForIdentifier:@"testIdentifier"]; 49 | 50 | NSString *lastComponent = [[filePathForIdentifier pathComponents] lastObject]; 51 | 52 | XCTAssert([lastComponent isEqualToString:@"testIdentifier.archive"], @"File Path Identifier should equal the passed identifier with the .archive extension."); 53 | } 54 | 55 | - (void)testFilePathForNilIdentifier { 56 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 57 | optionalDirectory:@"testDirectory"]; 58 | MMWormholeFileTransiting *transiting = wormhole.wormholeMessenger; 59 | 60 | NSString *filePathForIdentifier = [transiting filePathForIdentifier:nil]; 61 | 62 | NSString *lastComponent = [[filePathForIdentifier pathComponents] lastObject]; 63 | 64 | XCTAssertNil(lastComponent, @"File Path Identifier should be nil if no identifier is provided."); 65 | } 66 | 67 | - (void)testValidMessagePassing { 68 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 69 | optionalDirectory:@"testDirectory"]; 70 | MMWormholeFileTransiting *transiting = wormhole.wormholeMessenger; 71 | 72 | [transiting deleteContentForIdentifier:@"testIdentifier"]; 73 | 74 | id messageObject = [transiting messageObjectForIdentifier:@"testIdentifier"]; 75 | 76 | XCTAssertNil(messageObject, @"Message object should be nil after deleting file."); 77 | 78 | NSString *filePathForIdentifier = [transiting filePathForIdentifier:@"testIdentifier"]; 79 | 80 | [wormhole passMessageObject:@{} identifier:@"testIdentifier"]; 81 | 82 | NSData *data = [NSData dataWithContentsOfFile:filePathForIdentifier]; 83 | 84 | XCTAssertNotNil(data, @"Message contents should not be nil after passing a valid message."); 85 | } 86 | 87 | - (void)testFileWriting { 88 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 89 | optionalDirectory:@"testDirectory"]; 90 | MMWormholeFileTransiting *transiting = wormhole.wormholeMessenger; 91 | 92 | [transiting deleteContentForIdentifier:@"testIdentifier"]; 93 | 94 | id messageObject = [transiting messageObjectForIdentifier:@"testIdentifier"]; 95 | 96 | XCTAssertNil(messageObject, @"Message object should be nil after deleting file."); 97 | 98 | NSString *filePathForIdentifier = [transiting filePathForIdentifier:@"testIdentifier"]; 99 | 100 | [transiting writeMessageObject:@{} forIdentifier:@"testIdentifier"]; 101 | 102 | NSData *data = [NSData dataWithContentsOfFile:filePathForIdentifier]; 103 | 104 | XCTAssertNotNil(data, @"Message contents should not be nil after writing a valid message to disk."); 105 | } 106 | 107 | - (void)testClearingIndividualMessage { 108 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 109 | optionalDirectory:@"testDirectory"]; 110 | MMWormholeFileTransiting *transiting = wormhole.wormholeMessenger; 111 | 112 | [wormhole passMessageObject:@{} identifier:@"testIdentifier"]; 113 | 114 | id messageObject = [transiting messageObjectForIdentifier:@"testIdentifier"]; 115 | 116 | XCTAssertNotNil(messageObject, @"Message contents should not be nil after passing a valid message."); 117 | 118 | [wormhole clearMessageContentsForIdentifier:@"testIdentifier"]; 119 | 120 | NSString *filePathForIdentifier = [transiting filePathForIdentifier:@"testIdentifier"]; 121 | 122 | NSData *data = [NSData dataWithContentsOfFile:filePathForIdentifier]; 123 | 124 | XCTAssertNil(data, @"Message file should be gone after deleting message."); 125 | 126 | id deletedMessageObject = [transiting messageObjectForIdentifier:@"testIdentifier"]; 127 | 128 | XCTAssertNil(deletedMessageObject, @"Message object should be nil after deleting message."); 129 | } 130 | 131 | - (void)testClearingAllMessages { 132 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 133 | optionalDirectory:@"testDirectory"]; 134 | MMWormholeFileTransiting *transiting = wormhole.wormholeMessenger; 135 | 136 | [wormhole passMessageObject:@{} identifier:@"testIdentifier1"]; 137 | [wormhole passMessageObject:@{} identifier:@"testIdentifier2"]; 138 | [wormhole passMessageObject:@{} identifier:@"testIdentifier3"]; 139 | 140 | [wormhole clearAllMessageContents]; 141 | 142 | id deletedMessageObject1 = [transiting messageObjectForIdentifier:@"testIdentifier1"]; 143 | id deletedMessageObject2 = [transiting messageObjectForIdentifier:@"testIdentifier2"]; 144 | id deletedMessageObject3 = [transiting messageObjectForIdentifier:@"testIdentifier3"]; 145 | 146 | XCTAssertNil(deletedMessageObject1, @"Message object should be nil after deleting message."); 147 | XCTAssertNil(deletedMessageObject2, @"Message object should be nil after deleting message."); 148 | XCTAssertNil(deletedMessageObject3, @"Message object should be nil after deleting message."); 149 | } 150 | 151 | 152 | @end 153 | -------------------------------------------------------------------------------- /Example/MMWormhole/MMWormholeTests/MMWormholeTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeTests.m 3 | // MMWormholeTests 4 | // 5 | // Created by Conrad Stoll on 12/6/14. 6 | // Copyright (c) 2014 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "MMWormhole.h" 13 | 14 | #define ApplicationGroupIdentifier @"group.com.mutualmobile.wormhole" 15 | 16 | @interface MMWormhole (TextExtension) 17 | 18 | - (void)writeMessageObject:(id)messageObject toFileWithIdentifier:(NSString *)identifier; 19 | - (id)messageObjectFromFileWithIdentifier:(NSString *)identifier; 20 | - (void)deleteFileForIdentifier:(NSString *)identifier; 21 | - (id)listenerBlockForIdentifier:(NSString *)identifier; 22 | 23 | @end 24 | 25 | @interface MMWormholeTests : XCTestCase 26 | 27 | @end 28 | 29 | @implementation MMWormholeTests 30 | 31 | - (void)setUp { 32 | [super setUp]; 33 | } 34 | 35 | - (void)tearDown { 36 | [super tearDown]; 37 | } 38 | 39 | - (void)testPassingMessageWithNilIdentifier { 40 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 41 | optionalDirectory:@"testDirectory"]; 42 | 43 | [wormhole passMessageObject:@{} identifier:nil]; 44 | 45 | XCTAssertTrue(YES, @"Message Passing should not crash for nil message identifiers."); 46 | } 47 | 48 | - (void)testMessageListening { 49 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 50 | optionalDirectory:@"testDirectory"]; 51 | 52 | XCTestExpectation *expectation = [self expectationWithDescription:@"Listener should hear something"]; 53 | 54 | [wormhole listenForMessageWithIdentifier:@"testIdentifier" listener:^(id messageObject) { 55 | XCTAssertNotNil(messageObject, @"Valid message object should not be nil."); 56 | 57 | [expectation fulfill]; 58 | }]; 59 | 60 | // Simulate a fake notification since Darwin notifications aren't delivered to the sender 61 | 62 | [[NSNotificationCenter defaultCenter] postNotificationName:@"MMWormholeNotificationName" 63 | object:wormhole 64 | userInfo:@{@"identifier" : @"testIdentifier"}]; 65 | 66 | [self waitForExpectationsWithTimeout:2 handler:nil]; 67 | } 68 | 69 | - (void)testMessageListeningWithMultipleInstances { 70 | __block int wormhole1ListenerCounter = 0; 71 | __block int wormhole2ListenerCounter = 0; 72 | 73 | // Instance 1 74 | MMWormhole *wormhole1 = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 75 | optionalDirectory:@"testDirectory"]; 76 | 77 | [wormhole1 listenForMessageWithIdentifier:@"testIdentifier" listener:^(id messageObject) { 78 | wormhole1ListenerCounter++; 79 | }]; 80 | 81 | 82 | // Instance 2 83 | MMWormhole *wormhole2 = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 84 | optionalDirectory:@"testDirectory"]; 85 | 86 | [wormhole2 listenForMessageWithIdentifier:@"testIdentifier" listener:^(id messageObject) { 87 | wormhole2ListenerCounter++; 88 | }]; 89 | 90 | // Simulate a fake notification since Darwin notifications aren't delivered to the sender 91 | 92 | [[NSNotificationCenter defaultCenter] postNotificationName:@"MMWormholeNotificationName" 93 | object:wormhole1 94 | userInfo:@{@"identifier" : @"testIdentifier"}]; 95 | 96 | 97 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 98 | XCTAssertTrue( wormhole1ListenerCounter == 1 && wormhole2ListenerCounter == 0 , @"Valid multiple listener blocks with multiple instances should only be called once, on wormhole1." ); 99 | }); 100 | } 101 | 102 | - (void)testMessagePassingAndListeningWithMultipleInstances { 103 | XCTestExpectation *expectation = [self expectationWithDescription:@"Wormhole should receive message"]; 104 | 105 | __block int wormhole1ListenerCounter = 0; 106 | __block int wormhole2ListenerCounter = 0; 107 | 108 | // Instance 1 109 | MMWormhole *wormhole1 = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 110 | optionalDirectory:@"testDirectory"]; 111 | 112 | [wormhole1 listenForMessageWithIdentifier:@"testIdentifierWormhole1" listener:^(id messageObject) { 113 | wormhole1ListenerCounter++; 114 | [expectation fulfill]; 115 | }]; 116 | 117 | 118 | // Instance 2 119 | MMWormhole *wormhole2 = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 120 | optionalDirectory:@"testDirectory"]; 121 | 122 | [wormhole2 listenForMessageWithIdentifier:@"testIdentifierWormhole2" listener:^(id messageObject) { 123 | wormhole2ListenerCounter++; 124 | }]; 125 | 126 | // Send a message to one wormhole and verify it is received by that one but not the other 127 | [wormhole1 passMessageObject:@"message" identifier:@"testIdentifierWormhole1"]; 128 | 129 | [self waitForExpectationsWithTimeout:2 handler:^(NSError *error) { 130 | XCTAssertTrue(wormhole1ListenerCounter == 1 && wormhole2ListenerCounter == 0 , @"Valid multiple listener blocks with multiple instances should only be called once, on wormhole1." ); 131 | }]; 132 | 133 | } 134 | 135 | - (void)testStopMessageListening { 136 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 137 | optionalDirectory:@"testDirectory"]; 138 | 139 | XCTestExpectation *expectation = [self expectationWithDescription:@"Listener should hear something"]; 140 | 141 | [wormhole listenForMessageWithIdentifier:@"testIdentifier" listener:^(id messageObject) { 142 | XCTAssertNotNil(messageObject, @"Valid message object should not be nil."); 143 | 144 | [expectation fulfill]; 145 | }]; 146 | 147 | // Simulate a fake notification since Darwin notifications aren't delivered to the sender 148 | 149 | [[NSNotificationCenter defaultCenter] postNotificationName:@"MMWormholeNotificationName" 150 | object:wormhole 151 | userInfo:@{@"identifier" : @"testIdentifier"}]; 152 | 153 | [self waitForExpectationsWithTimeout:2 handler:^(NSError *error) { 154 | id listenerBlock = [wormhole listenerBlockForIdentifier:@"testIdentifier"]; 155 | 156 | XCTAssertNotNil(listenerBlock, @"A valid listener block should not be nil."); 157 | 158 | [wormhole stopListeningForMessageWithIdentifier:@"testIdentifier"]; 159 | 160 | id deletedListenerBlock = [wormhole listenerBlockForIdentifier:@"testIdentifier"]; 161 | 162 | XCTAssertNil(deletedListenerBlock, @"The listener block should be nil after you stop listening."); 163 | }]; 164 | } 165 | 166 | - (void)testMessagePassingDuplicateListeners { 167 | __block int wormholeListenerCounter = 0; 168 | 169 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 170 | optionalDirectory:@"testDirectory"]; 171 | 172 | XCTestExpectation *validExpectation = [self expectationWithDescription:@"Listener should hear something only once."]; 173 | XCTestExpectation *invalidExpectation = [self expectationWithDescription:@"Listener should not hear something more than once."]; 174 | 175 | [wormhole listenForMessageWithIdentifier:@"testIdentifier" listener:^(id messageObject) { 176 | XCTAssertTrue(NO, @"This listener should never be called."); 177 | }]; 178 | 179 | [wormhole listenForMessageWithIdentifier:@"testIdentifier" listener:^(id messageObject) { 180 | XCTAssertNotNil(messageObject, @"This listener should be called with a valid message object.."); 181 | 182 | if (wormholeListenerCounter == 0) { 183 | [validExpectation fulfill]; 184 | } 185 | 186 | wormholeListenerCounter++; 187 | 188 | if (wormholeListenerCounter > 1) { 189 | [invalidExpectation fulfill]; 190 | } 191 | }]; 192 | 193 | [wormhole passMessageObject:@"message" identifier:@"testIdentifier"]; 194 | 195 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 196 | [invalidExpectation fulfill]; 197 | }); 198 | 199 | [self waitForExpectationsWithTimeout:3 handler:^(NSError *error) { 200 | XCTAssertTrue(wormholeListenerCounter == 1, @"The listener for a given identifier should only be called once per message." ); 201 | }]; 202 | } 203 | 204 | - (void)testMessagePassingPerformance { 205 | [self measureBlock:^{ 206 | MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:ApplicationGroupIdentifier 207 | optionalDirectory:@"testDirectory"]; 208 | 209 | [wormhole passMessageObject:[self performanceSampleJSONObject] identifier:@"testPerformance"]; 210 | }]; 211 | } 212 | 213 | - (void)testJSONPerformance { 214 | [self measureBlock:^{ 215 | [NSJSONSerialization dataWithJSONObject:[self performanceSampleJSONObject] options:0 error:NULL]; 216 | }]; 217 | } 218 | 219 | - (void)testArchivePerformance { 220 | [self measureBlock:^{ 221 | [NSKeyedArchiver archivedDataWithRootObject:[self performanceSampleJSONObject]]; 222 | }]; 223 | } 224 | 225 | - (id)performanceSampleJSONObject { 226 | return @[@{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}}, 227 | @{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}}, 228 | @{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}}, 229 | @{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}}, 230 | @{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}}, 231 | @{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}}, 232 | @{@"key1" : @"string1", @"key2" : @(1), @"key3" : @{@"innerKey1" : @"innerString1", @"innerKey2" : @(1)}} 233 | ]; 234 | } 235 | 236 | @end 237 | -------------------------------------------------------------------------------- /Example/MMWormhole/Today Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Today Extension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 2.0.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSExtension 26 | 27 | NSExtensionMainStoryboard 28 | MainInterface 29 | NSExtensionPointIdentifier 30 | com.apple.widget-extension 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Example/MMWormhole/Today Extension/MainInterface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 25 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Example/MMWormhole/Today Extension/Today Extension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.com.mutualmobile.wormhole 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/MMWormhole/Today Extension/TodayViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TodayViewController.h 3 | // Today Extension 4 | // 5 | // Created by Conrad Stoll on 12/10/14. 6 | // Copyright (c) 2014 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TodayViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/MMWormhole/Today Extension/TodayViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TodayViewController.m 3 | // Today Extension 4 | // 5 | // Created by Conrad Stoll on 12/10/14. 6 | // Copyright (c) 2014 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import "TodayViewController.h" 10 | #import 11 | 12 | #import "MMWormhole.h" 13 | 14 | @interface TodayViewController () 15 | 16 | @property (nonatomic, strong) MMWormhole *wormhole; 17 | 18 | @end 19 | 20 | @implementation TodayViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | 25 | self.wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole" 26 | optionalDirectory:@"wormhole"]; 27 | } 28 | 29 | - (void)viewWillAppear:(BOOL)animated { 30 | [super viewWillAppear:animated]; 31 | 32 | self.preferredContentSize = CGSizeMake(320.0f, 40.0f); 33 | } 34 | 35 | - (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler { 36 | completionHandler(NCUpdateResultNewData); 37 | } 38 | 39 | // Pass messages each time a button is tapped using the identifier button 40 | // The messages contain a single number value with the buttonNumber key 41 | - (IBAction)didTapOne:(id)sender { 42 | [self.wormhole passMessageObject:@{@"buttonNumber" : @(1)} identifier:@"button"]; 43 | } 44 | 45 | - (IBAction)didTapTwo:(id)sender { 46 | [self.wormhole passMessageObject:@{@"buttonNumber" : @(2)} identifier:@"button"]; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "screenWidth" : "{130,145}", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "screenWidth" : "{146,165}", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS Extension/Assets.xcassets/Complication.complicationset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "idiom" : "watch", 5 | "filename" : "Circular.imageset", 6 | "role" : "circular" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "filename" : "Modular.imageset", 11 | "role" : "modular" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "filename" : "Utilitarian.imageset", 16 | "role" : "utilitarian" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "screenWidth" : "{130,145}", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "screenWidth" : "{146,165}", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "screenWidth" : "{130,145}", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "screenWidth" : "{146,165}", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS Extension/ExtensionDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionDelegate.h 3 | // watchOS Extension 4 | // 5 | // Created by Conrad Stoll on 6/8/15. 6 | // Copyright © 2015 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ExtensionDelegate : NSObject 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS Extension/ExtensionDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionDelegate.m 3 | // watchOS Extension 4 | // 5 | // Created by Conrad Stoll on 6/8/15. 6 | // Copyright © 2015 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import "ExtensionDelegate.h" 10 | 11 | @implementation ExtensionDelegate 12 | 13 | - (void)applicationDidFinishLaunching { 14 | // Perform any final initialization of your application. 15 | } 16 | 17 | - (void)applicationDidBecomeActive { 18 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 19 | } 20 | 21 | - (void)applicationWillResignActive { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, etc. 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | watchOS Extension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 2.0.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | CLKComplicationPrincipalClass 26 | ComplicationController 27 | CLKComplicationSupportedFamilies 28 | 29 | CLKComplicationFamilyModularSmall 30 | CLKComplicationFamilyModularLarge 31 | CLKComplicationFamilyUtilitarianSmall 32 | CLKComplicationFamilyUtilitarianLarge 33 | CLKComplicationFamilyCircularSmall 34 | 35 | NSExtension 36 | 37 | NSExtensionAttributes 38 | 39 | WKAppBundleIdentifier 40 | com.mutualmobile.MMWormhole.watchosapp 41 | 42 | NSExtensionPointIdentifier 43 | com.apple.watchkit 44 | 45 | RemoteInterfacePrincipalClass 46 | InterfaceController 47 | WKExtensionDelegateClassName 48 | ExtensionDelegate 49 | 50 | 51 | -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS Extension/InterfaceController.h: -------------------------------------------------------------------------------- 1 | // 2 | // InterfaceController.h 3 | // watchOS Extension 4 | // 5 | // Created by Conrad Stoll on 6/8/15. 6 | // Copyright © 2015 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface InterfaceController : WKInterfaceController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS Extension/InterfaceController.m: -------------------------------------------------------------------------------- 1 | // 2 | // InterfaceController.m 3 | // watchOS Extension 4 | // 5 | // Created by Conrad Stoll on 6/8/15. 6 | // Copyright © 2015 Conrad Stoll. All rights reserved. 7 | // 8 | 9 | #import "InterfaceController.h" 10 | 11 | #import "MMWormhole.h" 12 | #import "MMWormholeSession.h" 13 | 14 | @interface InterfaceController() 15 | 16 | @property (nonatomic, strong) MMWormhole *wormhole; 17 | @property (nonatomic, strong) MMWormholeSession *listeningWormhole; 18 | 19 | @property (nonatomic, weak) IBOutlet WKInterfaceLabel *selectionLabel; 20 | 21 | @end 22 | 23 | 24 | @implementation InterfaceController 25 | 26 | - (void)awakeWithContext:(id)context { 27 | [super awakeWithContext:context]; 28 | 29 | // You are required to initialize the shared listening wormhole before creating a 30 | // WatchConnectivity session transiting wormhole, as we are below. 31 | self.listeningWormhole = [MMWormholeSession sharedListeningSession]; 32 | 33 | // Initialize the wormhole 34 | self.wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole" 35 | optionalDirectory:@"wormhole" 36 | transitingType:MMWormholeTransitingTypeSessionContext]; 37 | 38 | // Obtain an initial value for the selection message from the wormhole 39 | id messageObject = [self.wormhole messageWithIdentifier:@"selection"]; 40 | NSString *string = [messageObject valueForKey:@"selectionString"]; 41 | 42 | if (string != nil) { 43 | [self.selectionLabel setText:string]; 44 | } 45 | 46 | // Listen for changes to the selection message. The selection message contains a string value 47 | // identified by the selectionString key. Note that the type of the key is included in the 48 | // name of the key. 49 | [self.listeningWormhole listenForMessageWithIdentifier:@"selection" listener:^(id messageObject) { 50 | NSString *string = [messageObject valueForKey:@"selectionString"]; 51 | 52 | if (string != nil) { 53 | [self.selectionLabel setText:string]; 54 | } 55 | }]; 56 | 57 | // Make sure we are activating the listening wormhole so that it will receive new messages from 58 | // the WatchConnectivity framework. 59 | [self.listeningWormhole activateSessionListening]; 60 | } 61 | 62 | // Pass messages each time a button is tapped using the identifier button 63 | // The messages contain a single number value with the buttonNumber key 64 | - (IBAction)didTapOne:(id)sender { 65 | [self.wormhole passMessageObject:@{@"buttonNumber" : @(1)} identifier:@"button"]; 66 | } 67 | 68 | - (IBAction)didTapTwo:(id)sender { 69 | [self.wormhole passMessageObject:@{@"buttonNumber" : @(2)} identifier:@"button"]; 70 | } 71 | 72 | - (IBAction)didTapThree:(id)sender { 73 | [self.wormhole passMessageObject:@{@"buttonNumber" : @(3)} identifier:@"button"]; 74 | } 75 | 76 | @end 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS Extension/PushNotificationPayload.apns: -------------------------------------------------------------------------------- 1 | { 2 | "aps": { 3 | "alert": { 4 | "body": "Test message", 5 | "title": "Optional title" 6 | }, 7 | "category": "myCategory" 8 | }, 9 | 10 | "WatchKit Simulator Actions": [ 11 | { 12 | "title": "First Button", 13 | "identifier": "firstButtonAction" 14 | } 15 | ], 16 | 17 | "customKey": "Use this file to define a testing payload for your notifications. The aps dictionary specifies the category, alert text and title. The WatchKit Simulator Actions array can provide info for one or more action buttons in addition to the standard Dismiss button. Any other top level keys are custom payload. If you have multiple such JSON files in your project, you'll be able to select them when choosing to debug the notification interface of your Watch App." 18 | } 19 | -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS Extension/watchOS Extension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.com.mutualmobile.wormhole 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "24x24", 5 | "idiom" : "watch", 6 | "scale" : "2x", 7 | "role" : "notificationCenter", 8 | "subtype" : "38mm" 9 | }, 10 | { 11 | "size" : "27.5x27.5", 12 | "idiom" : "watch", 13 | "scale" : "2x", 14 | "role" : "notificationCenter", 15 | "subtype" : "42mm" 16 | }, 17 | { 18 | "size" : "29x29", 19 | "idiom" : "watch", 20 | "role" : "companionSettings", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "size" : "29x29", 25 | "idiom" : "watch", 26 | "role" : "companionSettings", 27 | "scale" : "3x" 28 | }, 29 | { 30 | "size" : "40x40", 31 | "idiom" : "watch", 32 | "scale" : "2x", 33 | "role" : "appLauncher", 34 | "subtype" : "38mm" 35 | }, 36 | { 37 | "size" : "44x44", 38 | "idiom" : "watch", 39 | "scale" : "2x", 40 | "role" : "longLook", 41 | "subtype" : "42mm" 42 | }, 43 | { 44 | "size" : "86x86", 45 | "idiom" : "watch", 46 | "scale" : "2x", 47 | "role" : "quickLook", 48 | "subtype" : "38mm" 49 | }, 50 | { 51 | "size" : "98x98", 52 | "idiom" : "watch", 53 | "scale" : "2x", 54 | "role" : "quickLook", 55 | "subtype" : "42mm" 56 | } 57 | ], 58 | "info" : { 59 | "version" : 1, 60 | "author" : "xcode" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS/Base.lproj/Interface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Example/MMWormhole/watchOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | MMWormhole 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 2.0.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | UISupportedInterfaceOrientations 26 | 27 | UIInterfaceOrientationPortrait 28 | UIInterfaceOrientationPortraitUpsideDown 29 | 30 | WKCompanionAppBundleIdentifier 31 | com.mutualmobile.MMWormhole 32 | WKWatchKitApp 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mutual Mobile 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MMWormhole.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mutualmobile/MMWormhole/1405cb1dd7b07f1023bb81e445002bcc5e7ece56/MMWormhole.gif -------------------------------------------------------------------------------- /MMWormhole.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'MMWormhole' 3 | s.version = '2.0.0' 4 | s.license = 'MIT' 5 | s.summary = 'Message passing between apps and extensions.' 6 | s.homepage = 'https://github.com/mutualmobile/MMWormhole' 7 | s.authors = { 'Conrad Stoll' => 'conrad.stoll@mutualmobile.com' } 8 | s.source = { :git => 'https://github.com/mutualmobile/MMWormhole.git', :tag => s.version.to_s } 9 | s.requires_arc = true 10 | 11 | s.default_subspec = 'Core' 12 | 13 | s.ios.deployment_target = '9.0' 14 | s.osx.deployment_target = '10.10' 15 | s.watchos.deployment_target = '2.0' 16 | 17 | s.ios.frameworks = 'Foundation', 'WatchConnectivity' 18 | s.osx.frameworks = 'Foundation' 19 | s.watchos.frameworks = 'Foundation', 'WatchConnectivity' 20 | 21 | s.subspec 'Core' do |core| 22 | core.ios.source_files = 'Source/*.{h,m}' 23 | core.watchos.source_files = 'Source/*.{h,m}' 24 | core.osx.source_files = 'Source/MMWormhole.{h,m}', 'Source/MMWormholeFileTransiting.{h,m}', 'Source/MMWormholeCoordinatedFileTransiting.{h,m}', 'Source/MMWormholeTransiting.h' 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /MMWormhole/MMWormhole.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MMWormhole/MMWormhole.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MMWormhole/MMWormhole.xcodeproj/xcshareddata/xcschemes/MMWormhole-Mac.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 72 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /MMWormhole/MMWormhole.xcodeproj/xcshareddata/xcschemes/MMWormhole-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 72 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /MMWormhole/MMWormhole/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /MMWormhole/MMWormhole/MMWormholeUmbrella.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormhole.h 3 | // MMWormhole 4 | // 5 | // Created by LEI on 5/11/15. 6 | // Copyright (c) 2015 MMWormhole. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for MMWormhole. 12 | FOUNDATION_EXPORT double MMWormholeVersionNumber; 13 | 14 | //! Project version string for MMWormhole. 15 | FOUNDATION_EXPORT const unsigned char MMWormholeVersionString[]; 16 | 17 | #import 18 | #import 19 | #import 20 | 21 | #if ( ( defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 ) || TARGET_OS_WATCH ) 22 | #import 23 | #import 24 | #import 25 | #import 26 | #endif 27 | 28 | #import -------------------------------------------------------------------------------- /MMWormhole/MMWormhole/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module MMWormhole { 2 | umbrella header "MMWormholeUmbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /MMWormhole_correct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mutualmobile/MMWormhole/1405cb1dd7b07f1023bb81e445002bcc5e7ece56/MMWormhole_correct.png -------------------------------------------------------------------------------- /MMWormhole_incorrect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mutualmobile/MMWormhole/1405cb1dd7b07f1023bb81e445002bcc5e7ece56/MMWormhole_incorrect.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MMWormhole 2 | 3 | MMWormhole creates a bridge between an iOS or OS X extension and its containing application. The wormhole is meant to be used to pass data or commands back and forth between the two locations. Messages are archived to files which are written to the application's shared App Group. The effect closely resembles interprocess communication between the app and the extension, though true interprocess communication does not exist between extensions and containing apps. 4 | 5 | The wormhole also supports CFNotificationCenter Darwin Notifications in an effort to support realtime change notifications. When a message is passed to the wormhole, interested parties can listen and be notified of these changes on either side of the wormhole. The effect is nearly instant updates on either side when a message is sent through the wormhole. 6 | 7 |

8 | Example App 9 |

10 | 11 | ## Example 12 | 13 | ```objective-c 14 | [self.wormhole passMessageObject:@{@"buttonNumber" : @(1)} identifier:@"button"]; 15 | 16 | [self.wormhole listenForMessageWithIdentifier:@"button" 17 | listener:^(id messageObject) { 18 | self.numberLabel.text = [messageObject[@"buttonNumber"] stringValue]; 19 | }]; 20 | ``` 21 | 22 | ## Getting Started 23 | 24 | - Install MMWormhole via CocoaPods or by downloading the Source files 25 | - [Configure your App and Extension to support App Groups](https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html) 26 | - Begin using MMWormhole to pass messages between your App and Extension 27 | 28 | ### Note 29 | 30 | The MMWormhole Example app will only work with your shared App Group identifiers and Entitlements and is meant purely for reference 31 | 32 | --- 33 | ## Installing MMWormhole 34 |
35 | You can install Wormhole in your project by using [CocoaPods](https://github.com/cocoapods/cocoapods): 36 | 37 | ```Ruby 38 | pod 'MMWormhole', '~> 2.0.0' 39 | ``` 40 | 41 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
42 | MMWormhole also supports Carthage. 43 | 44 | ## Overview 45 | 46 | MMWormhole is designed to make it easy to share very basic information and commands between an extension and it's containing application. The wormhole should remain stable whether the containing app is running or not, but notifications will only be triggered in the containing app if the app is awake in the background. This makes MMWormhole ideal for cases where the containing app is already running via some form of background modes. 47 | 48 | A good way to think of the wormhole is a collection of shared mailboxes. An identifier is essentially a unique mailbox you can send messages to. You know where a message will be delivered to because of the identifier you associate with it, but not necessarily when the message will be picked up by the recipient. If the app or extension are in the background, they may not receive the message immediately. By convention, sending messages should be done from one side to another, not necessarily from yourself to yourself. It's also a good practice to check the contents of your mailbox when your app or extension wakes up, in case any messages have been left there while you were away. 49 | 50 | MMWormhole uses NSKeyedArchiver as a serialization medium, so any object that is NSCoding compliant can work as a message. For many apps, sharing simple strings, numbers, or JSON objects is sufficient to drive the UI of a Widget or Apple Watch app. Messages can be sent and persisted easily as archive files and read later when the app or extension is woken up later. 51 | 52 | Using MMWormhole is extremely straightforward. The only real catch is that your app and it's extensions must support shared app groups. The group will be used for writing the archive files that represent each message. While larger files and structures, including a whole Core Data database, can be shared using App Groups, MMWormhole is designed to use it's own directory simply to pass messages. Because of that, a best practice is to initialize MMWormhole with a directory name that it will use within your app's shared App Group. 53 | 54 | ### Initialization 55 | 56 | Initialize MMWormhole with your App Group identifier and an optional directory name 57 | 58 | Objective-C: 59 | ```objective-c 60 | self.wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole" 61 | optionalDirectory:@"wormhole"]; 62 | ``` 63 | Swift: 64 | ```swift 65 | let wormhole = MMWormhole(applicationGroupIdentifier: "group.com.mutualmobile.wormhole", optionalDirectory: "wormhole") 66 | ``` 67 | 68 | ### Passing a Message 69 | 70 | Pass a message with an identifier for the message and a NSCoding compliant object as the message itself 71 | 72 | Objective-C: 73 | ```objective-c 74 | [self.wormhole passMessageObject:@{@"titleString" : title} 75 | identifier:@"messageIdentifier"]; 76 | ``` 77 | 78 | Swift: 79 | ```swift 80 | wormhole.passMessageObject("titleString", identifier: "messageIdentifier") 81 | ``` 82 | 83 | ### Reading a Message 84 | 85 | You have two options for reading a message. You can obtain the message for an identifier at any time by asking the wormhole for the message. 86 | 87 | Objective-C: 88 | ```objective-c 89 | id messageObject = [self.wormhole messageWithIdentifier:@"messageIdentifier"]; 90 | ``` 91 | 92 | You can also listen for changes to that message and be notified when that message is updated. 93 | 94 | Objective-C: 95 | ```objective-c 96 | [self.wormhole listenForMessageWithIdentifier:@"messageIdentifier" 97 | listener:^(id messageObject) { 98 | // Do Something 99 | }]; 100 | 101 | ``` 102 | Swift: 103 | ```swift 104 | wormhole.listenForMessageWithIdentifier("messageIdentifier", listener: { (messageObject) -> Void in 105 | if let message: AnyObject = messageObject { 106 | // Do something 107 | } 108 | }) 109 | ``` 110 | 111 | ### Designing Your Communication Scheme 112 | 113 | You can think of message passing between apps and extensions sort of like a web service. The web service has endpoints that you can read and write. The message identifiers for your MMWormhole messages can be thought of in much the same way. A great practice is to design very clear message identifiers so that you immediately know when reading your code who sent the message and why, and what the possible contents of the message might be. Just like you would design a web service with clear semantics, you should do the same with your wormhole messaging scheme. 114 | 115 | ### Communication with WatchConnectivity 116 | 117 | The design of your communication scheme is even more important when you need to support watchOS 2. MMWormhole supports the [WatchConnectivity](https://developer.apple.com/library/prerelease/watchos/documentation/WatchConnectivity/Reference/WatchConnectivity_framework/index.html#//apple_ref/doc/uid/TP40015269) framework provided by Apple as an easy way to get up and running quickly with a basic implementation of WatchConnectivity. This support is not intended to replace WatchConnectivity entirely, and it's important to carefully consider your watch app's communication system to see where MMWormhole will fit best. 118 | 119 | Here are two things you need to know if you want to use WatchConnectivity support in your app: 120 | 121 | - [MMWormholeSession](http://cocoadocs.org/docsets/MMWormhole/2.0.0/Classes/MMWormholeSession.html) is a singleton subclass of MMWormhole that supports listening for WatchConnectivity messages. It should be used as the listener for all MMWormhole messages you expect to receive from the WatchConnectivity framework. Be sure to activate the session once your listeners are set so that you can begin receiving message notifications. 122 | 123 | - Use the MMWormholeSessionTransiting types described below when creating your wormholes, but be careful not to send too many messages at once. You can easily overload the pipeline by sending too many messages at once. 124 | 125 | ### Message Transiting Options 126 | 127 | The mechanism by which data flows through MMWormhole is defined by the [MMWormholeTransiting](http://cocoadocs.org/docsets/MMWormhole/2.0.0/Classes/MMWormholeTransiting.html) protocol. The default implementation of the protocol is called [MMWormholeFileTransiting](http://cocoadocs.org/docsets/MMWormhole/2.0.0/Classes/MMWormholeFileTransiting.html), which reads and writes messages as archived data files in the app groups shared container. Users of MMWormhole can implement their own version of this protocol to change the message passing behavior. 128 | 129 | There are three new implementations of the MMWormholeTransiting protocol that support the WCSession application context, message, and file transfer systems. You may only use one form of transiting with a wormhole at a time, so you need to consider which type of messaging system best fits a given part of your application. 130 | 131 | Most apps will find the application context system to be a good balance between real time messaging and simple persistence, so we recommend [MMWormholeSessionContextTransiting](http://cocoadocs.org/docsets/MMWormhole/2.0.0/Classes/MMWormholeSessionContextTransiting.html) as the best place to start. Check out the [documentation](https://developer.apple.com/library/prerelease/watchos/documentation/WatchConnectivity/Reference/WatchConnectivity_framework/index.html#//apple_ref/doc/uid/TP40015269) and header comments for descriptions about the other messaging types. 132 | 133 | You can get started quickly with a wormhole using one of the built in transiting types by calling the optional initializer to set up an instance with the right transiting type for your use case. 134 | 135 | Objective-C: 136 | ```objective-c 137 | self.wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.mutualmobile.wormhole" 138 | optionalDirectory:@"wormhole" 139 | transitingType:MMWormholeTransitingTypeSessionContext]; 140 | ``` 141 | 142 | Swift: 143 | ```swift 144 | let wormhole = MMWormhole(applicationGroupIdentifier: "group.com.mutualmobile.wormhole", 145 | optionalDirectory: "wormhole", 146 | transitingType: .SessionContext) 147 | ``` 148 | 149 | ## Requirements 150 | 151 | MMWormhole requires iOS 7.0 or higher or OS X 10.10 or higher. 152 | MMWormholeSession requires iOS 9.0 or higher. 153 | 154 | ## Troubleshooting 155 | 156 | If messages are not received on the other end, check Project->Capabilities->App Groups.
157 | Three checkmarks should be displayed in the steps section. 158 | 159 |

160 | Correct App Group Capabilities 161 |

162 | 163 |

164 | Incorrect App Group Capabilities 165 |

166 | 167 | ## Credits 168 | 169 | MMWormhole was created by [Conrad Stoll](http://conradstoll.com) at [Mutual Mobile](http://www.mutualmobile.com). 170 | 171 | Credit also to [Wade Spires](https://devforums.apple.com/people/mindsaspire), [Tom Harrington](https://twitter.com/atomicbird), and [Rene Cacheaux](https://twitter.com/rcachatx) for work and inspiration surrounding notifications between the containing app and it's extensions. 172 | 173 | ## License 174 | 175 | MMWormhole is available under the MIT license. See the LICENSE file for more info. 176 | -------------------------------------------------------------------------------- /Source/MMWormhole.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormhole.h 3 | // 4 | // Copyright (c) 2014 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import 25 | 26 | #import "MMWormholeCoordinatedFileTransiting.h" 27 | #import "MMWormholeFileTransiting.h" 28 | 29 | #if ( defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 ) 30 | #import "MMWormholeSessionContextTransiting.h" 31 | #import "MMWormholeSessionFileTransiting.h" 32 | #import "MMWormholeSessionMessageTransiting.h" 33 | #endif 34 | 35 | #import "MMWormholeTransiting.h" 36 | 37 | typedef NS_ENUM(NSInteger, MMWormholeTransitingType) { 38 | MMWormholeTransitingTypeFile = 0, 39 | MMWormholeTransitingTypeCoordinatedFile, 40 | MMWormholeTransitingTypeSessionContext, 41 | MMWormholeTransitingTypeSessionMessage, 42 | MMWormholeTransitingTypeSessionFile 43 | }; 44 | 45 | 46 | NS_ASSUME_NONNULL_BEGIN 47 | 48 | /** 49 | This class creates a wormhole between a containing iOS application and an extension. The wormhole 50 | is meant to be used to pass data or commands back and forth between the two locations. The effect 51 | closely resembles interprocess communication between the app and the extension, though this is not 52 | really the case. The wormhole does have some disadvantages, including the fact that a contract must 53 | be determined in advance between the app and the extension that defines the interchange format. 54 | 55 | A good way to think of the wormhole is a collection of shared mailboxes. An identifier is 56 | essentially a unique mailbox you can send messages to. You know where a message will be delivered 57 | to because of the identifier you associate with it, but not necessarily when the message will be 58 | picked up by the recipient. If the app or extension are in the background, they may not receive the 59 | message immediately. By convention, sending messages should be done from one side to another, not 60 | necessarily from yourself to yourself. It's also a good practice to check the contents of your 61 | mailbox when your app or extension wakes up, in case any messages have been left there while you 62 | were away. 63 | 64 | Passing a message to the wormhole can be inferred as a data transfer package or as a command. In 65 | both cases, the passed message is archived using NSKeyedArchiver to a .archive file named with the 66 | included identifier. Once passed, the contents of the written .archive file can be queried using 67 | the messageWithIdentifier: method. As a command, the simple existence of the message in the shared 68 | app group should be taken as proof of the command's invocation. The contents of the message then 69 | become parameters to be evaluated along with the command. Of course, to avoid confusion later, it 70 | may be best to clear the contents of the message after recognizing the command. The 71 | -clearMessageContentsForIdentifier: method is provided for this purpose. 72 | 73 | A good wormhole includes wormhole aliens who listen for message changes. This class supports 74 | CFNotificationCenter Darwin Notifications, which act as a bridge between the containing app and the 75 | extension. When a message is passed with an identifier, a notification is fired to the Darwin 76 | Notification Center with the given identifier. If you have indicated your interest in the message 77 | by using the -listenForMessageWithIdentifier:completion: method then your completion block will be 78 | called when this notification is received, and the contents of the message will be unarchived and 79 | passed as an object to the completion block. 80 | 81 | It's worth noting that as a best practice to avoid confusing issues or deadlock that messages 82 | should be passed one way only for a given identifier. The containing app should pass messages to 83 | one set of identifiers, which are only ever read or listened for by the extension, and vic versa. 84 | The extension should not then write messages back to the same identifier. Instead, the extension 85 | should use it's own set of identifiers to associate with it's messages back to the application. 86 | Passing messages to the same identifier from two locations should be done only at your own risk. 87 | */ 88 | @interface MMWormhole : NSObject 89 | 90 | /** 91 | The wormhole messenger is an object that conforms to the MMWormholeTransiting protocol. By default 92 | this object will be set to a default implementation of this protocol which handles archiving and 93 | unarchiving the message to the shared app group in a file named after the identifier of the 94 | message. 95 | 96 | Users of this class may create their own implementation of the MMWormholeTransiting protocol to use 97 | for the purpose of defining the means by which messages transit the wormhole. You could use this to 98 | change the way that MMWormhole stores messages as files, to read and write messages to a database, 99 | or otherwise be notified in other ways when messages are changed. 100 | 101 | @warning While changing this property is optional, the value of the wormhole messenger should 102 | not be nil and is required for the class to work. 103 | */ 104 | @property (nonatomic, strong) id wormholeMessenger; 105 | 106 | /** 107 | Designated Initializer. This method must be called with an application group identifier that will 108 | be used to contain passed messages. It is also recommended that you include a directory name for 109 | messages to be read and written, but this parameter is optional. 110 | 111 | @param identifier An application group identifier 112 | @param directory An optional directory to read/write messages 113 | */ 114 | 115 | - (instancetype)initWithApplicationGroupIdentifier:(nullable NSString *)identifier 116 | optionalDirectory:(nullable NSString *)directory NS_DESIGNATED_INITIALIZER; 117 | 118 | /** 119 | Optional Initializer. This method is provided for convenience while creating MMWormhole instances 120 | with custom message transiting options. By default MMWormhole will use the 121 | MMWormholeTransitingTypeFile option when creating a Wormhole, however, this method can be used to 122 | easily choose a different transiting class at initialization time. You can always initialize a 123 | different class that implements the MMWormholeTransiting class later and replace the Wormhole's 124 | 'wormholeMessenger' property to change the transiting type at a later time. 125 | 126 | @param identifier An application group identifier 127 | @param directory An optional directory to read/write messages 128 | @param transitingType A type of wormhole message transiting that will be used for message passing. 129 | */ 130 | - (instancetype)initWithApplicationGroupIdentifier:(nullable NSString *)identifier 131 | optionalDirectory:(nullable NSString *)directory 132 | transitingType:(MMWormholeTransitingType)transitingType; 133 | 134 | /** 135 | This method passes a message object associated with a given identifier. This is the primary means 136 | of passing information through the wormhole. 137 | 138 | @warning You should avoid situations where you need to pass messages to the same identifier in 139 | rapid succession. If a message's contents will be changing rapidly then consider modifying your 140 | workflow to write bulk changes without listening on the other side of the wormhole, and then add a 141 | listener for a "finished changing" message to let the other side know it's safe to read the 142 | contents of your message. 143 | 144 | @param messageObject The message object to be passed. 145 | This object may be nil. In this case only a notification is posted. 146 | @param identifier The identifier for the message 147 | */ 148 | - (void)passMessageObject:(nullable id )messageObject 149 | identifier:(nullable NSString *)identifier; 150 | 151 | /** 152 | This method returns the value of a message with a specific identifier as an object. 153 | 154 | @param identifier The identifier for the message 155 | */ 156 | - (nullable id)messageWithIdentifier:(nullable NSString *)identifier; 157 | 158 | /** 159 | This method clears the contents of a specific message with a given identifier. 160 | 161 | @param identifier The identifier for the message 162 | */ 163 | - (void)clearMessageContentsForIdentifier:(nullable NSString *)identifier; 164 | 165 | /** 166 | This method clears the contents of your optional message directory to give you a clean state. 167 | 168 | @warning This method will delete all messages passed to your message directory. Use with care. 169 | */ 170 | - (void)clearAllMessageContents; 171 | 172 | /** 173 | This method begins listening for notifications of changes to a message with a specific identifier. 174 | If notifications are observed then the given listener block will be called along with the actual 175 | message object. 176 | 177 | @discussion This class only supports one listener per message identifier, so calling this method 178 | repeatedly for the same identifier will update the listener block that will be called when a 179 | message is heard. 180 | 181 | @param identifier The identifier for the message 182 | @param listener A listener block called with the messageObject parameter when a notification 183 | is observed. 184 | */ 185 | - (void)listenForMessageWithIdentifier:(nullable NSString *)identifier 186 | listener:(nullable void (^)(__nullable id messageObject))listener; 187 | 188 | /** 189 | This method stops listening for change notifications for a given message identifier. 190 | 191 | NOTE: This method is NOT required to be called. If the wormhole is deallocated then all listeners 192 | will go away as well. 193 | 194 | @param identifier The identifier for the message 195 | */ 196 | - (void)stopListeningForMessageWithIdentifier:(nullable NSString *)identifier; 197 | 198 | @end 199 | 200 | NS_ASSUME_NONNULL_END 201 | -------------------------------------------------------------------------------- /Source/MMWormhole.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormhole.m 3 | // 4 | // Copyright (c) 2014 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "MMWormhole.h" 25 | 26 | #if !__has_feature(objc_arc) 27 | #error This class requires automatic reference counting 28 | #endif 29 | 30 | #include 31 | 32 | NS_ASSUME_NONNULL_BEGIN 33 | 34 | static NSString * const MMWormholeNotificationName = @"MMWormholeNotificationName"; 35 | 36 | void wormholeNotificationCallback(CFNotificationCenterRef center, 37 | void * observer, 38 | CFStringRef name, 39 | void const * object, 40 | CFDictionaryRef userInfo); 41 | 42 | @interface MMWormhole () 43 | 44 | @property (nonatomic, strong) NSMutableDictionary *listenerBlocks; 45 | 46 | @end 47 | 48 | @implementation MMWormhole 49 | 50 | #pragma clang diagnostic push 51 | #pragma clang diagnostic ignored "-Wobjc-designated-initializers" 52 | 53 | - (id)init { 54 | return nil; 55 | } 56 | 57 | #pragma clang diagnostic pop 58 | 59 | - (instancetype)initWithApplicationGroupIdentifier:(nullable NSString *)identifier 60 | optionalDirectory:(nullable NSString *)directory { 61 | if ((self = [super init])) { 62 | 63 | if (NO == [[NSFileManager defaultManager] respondsToSelector:@selector(containerURLForSecurityApplicationGroupIdentifier:)]) { 64 | //Protect the user of a crash because of iOSVersion < iOS7 65 | return nil; 66 | } 67 | 68 | self.wormholeMessenger = [[MMWormholeFileTransiting alloc] initWithApplicationGroupIdentifier:[identifier copy] 69 | optionalDirectory:[directory copy]]; 70 | 71 | _listenerBlocks = [NSMutableDictionary dictionary]; 72 | 73 | // Only respects notification coming from self. 74 | [[NSNotificationCenter defaultCenter] addObserver:self 75 | selector:@selector(didReceiveMessageNotification:) 76 | name:MMWormholeNotificationName 77 | object:self]; 78 | } 79 | 80 | return self; 81 | } 82 | 83 | - (instancetype)initWithApplicationGroupIdentifier:(nullable NSString *)identifier 84 | optionalDirectory:(nullable NSString *)directory 85 | transitingType:(MMWormholeTransitingType)transitingType { 86 | if ((self = [self initWithApplicationGroupIdentifier:identifier optionalDirectory:directory])) { 87 | switch (transitingType) { 88 | case MMWormholeTransitingTypeFile: 89 | // Default 90 | break; 91 | case MMWormholeTransitingTypeCoordinatedFile: 92 | self.wormholeMessenger = [[MMWormholeCoordinatedFileTransiting alloc] initWithApplicationGroupIdentifier:identifier 93 | optionalDirectory:directory]; 94 | break; 95 | case MMWormholeTransitingTypeSessionContext: 96 | #if ( defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 ) 97 | self.wormholeMessenger = [[MMWormholeSessionContextTransiting alloc] initWithApplicationGroupIdentifier:identifier 98 | optionalDirectory:directory]; 99 | #endif 100 | break; 101 | case MMWormholeTransitingTypeSessionFile: 102 | #if ( defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 ) 103 | self.wormholeMessenger = [[MMWormholeSessionFileTransiting alloc] initWithApplicationGroupIdentifier:identifier 104 | optionalDirectory:directory]; 105 | #endif 106 | break; 107 | case MMWormholeTransitingTypeSessionMessage: 108 | #if ( defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 ) 109 | self.wormholeMessenger = [[MMWormholeSessionMessageTransiting alloc] initWithApplicationGroupIdentifier:identifier 110 | optionalDirectory:directory]; 111 | #endif 112 | break; 113 | default: 114 | break; 115 | } 116 | } 117 | 118 | return self; 119 | } 120 | 121 | - (void)dealloc { 122 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 123 | 124 | CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter(); 125 | CFNotificationCenterRemoveEveryObserver(center, (__bridge const void *)(self)); 126 | } 127 | 128 | 129 | #pragma mark - Private Notification Methods 130 | 131 | - (void)sendNotificationForMessageWithIdentifier:(nullable NSString *)identifier { 132 | CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter(); 133 | CFDictionaryRef const userInfo = NULL; 134 | BOOL const deliverImmediately = YES; 135 | CFStringRef str = (__bridge CFStringRef)identifier; 136 | CFNotificationCenterPostNotification(center, str, NULL, userInfo, deliverImmediately); 137 | } 138 | 139 | - (void)registerForNotificationsWithIdentifier:(nullable NSString *)identifier { 140 | [self unregisterForNotificationsWithIdentifier:identifier]; 141 | 142 | CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter(); 143 | CFStringRef str = (__bridge CFStringRef)identifier; 144 | CFNotificationCenterAddObserver(center, 145 | (__bridge const void *)(self), 146 | wormholeNotificationCallback, 147 | str, 148 | NULL, 149 | CFNotificationSuspensionBehaviorDeliverImmediately); 150 | } 151 | 152 | - (void)unregisterForNotificationsWithIdentifier:(nullable NSString *)identifier { 153 | CFNotificationCenterRef const center = CFNotificationCenterGetDarwinNotifyCenter(); 154 | CFStringRef str = (__bridge CFStringRef)identifier; 155 | CFNotificationCenterRemoveObserver(center, 156 | (__bridge const void *)(self), 157 | str, 158 | NULL); 159 | } 160 | 161 | void wormholeNotificationCallback(CFNotificationCenterRef center, 162 | void * observer, 163 | CFStringRef name, 164 | void const * object, 165 | CFDictionaryRef userInfo) { 166 | NSString *identifier = (__bridge NSString *)name; 167 | NSObject *sender = (__bridge NSObject *)(observer); 168 | [[NSNotificationCenter defaultCenter] postNotificationName:MMWormholeNotificationName 169 | object:sender 170 | userInfo:@{@"identifier" : identifier}]; 171 | } 172 | 173 | - (void)didReceiveMessageNotification:(NSNotification *)notification { 174 | NSDictionary *userInfo = notification.userInfo; 175 | NSString *identifier = [userInfo valueForKey:@"identifier"]; 176 | 177 | if (identifier != nil) { 178 | id messageObject = [self.wormholeMessenger messageObjectForIdentifier:identifier]; 179 | 180 | [self notifyListenerForMessageWithIdentifier:identifier message:messageObject]; 181 | } 182 | } 183 | 184 | - (id)listenerBlockForIdentifier:(NSString *)identifier { 185 | return [self.listenerBlocks valueForKey:identifier]; 186 | } 187 | 188 | - (void)notifyListenerForMessageWithIdentifier:(nullable NSString *)identifier message:(nullable id)message { 189 | typedef void (^MessageListenerBlock)(id messageObject); 190 | 191 | MessageListenerBlock listenerBlock = [self listenerBlockForIdentifier:identifier]; 192 | 193 | if (listenerBlock) { 194 | dispatch_async(dispatch_get_main_queue(), ^{ 195 | listenerBlock(message); 196 | }); 197 | } 198 | } 199 | 200 | 201 | #pragma mark - Public Interface Methods 202 | 203 | - (void)passMessageObject:(nullable id )messageObject identifier:(nullable NSString *)identifier { 204 | if ([self.wormholeMessenger writeMessageObject:messageObject forIdentifier:identifier]) { 205 | [self sendNotificationForMessageWithIdentifier:identifier]; 206 | } 207 | } 208 | 209 | 210 | - (nullable id)messageWithIdentifier:(nullable NSString *)identifier { 211 | id messageObject = [self.wormholeMessenger messageObjectForIdentifier:identifier]; 212 | 213 | return messageObject; 214 | } 215 | 216 | - (void)clearMessageContentsForIdentifier:(nullable NSString *)identifier { 217 | [self.wormholeMessenger deleteContentForIdentifier:identifier]; 218 | } 219 | 220 | - (void)clearAllMessageContents { 221 | [self.wormholeMessenger deleteContentForAllMessages]; 222 | } 223 | 224 | - (void)listenForMessageWithIdentifier:(nullable NSString *)identifier 225 | listener:(nullable void (^)(__nullable id messageObject))listener { 226 | if (identifier != nil) { 227 | [self.listenerBlocks setValue:listener forKey:identifier]; 228 | [self registerForNotificationsWithIdentifier:identifier]; 229 | } 230 | } 231 | 232 | - (void)stopListeningForMessageWithIdentifier:(nullable NSString *)identifier { 233 | if (identifier != nil) { 234 | [self.listenerBlocks setValue:nil forKey:identifier]; 235 | [self unregisterForNotificationsWithIdentifier:identifier]; 236 | } 237 | } 238 | 239 | @end 240 | 241 | NS_ASSUME_NONNULL_END 242 | -------------------------------------------------------------------------------- /Source/MMWormholeCoordinatedFileTransiting.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeCoordinatedFileTransiting.h 3 | // 4 | // Copyright (c) 2015 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "MMWormholeFileTransiting.h" 25 | 26 | /** 27 | This class inherits from the default implementation of the MMWormholeTransiting protocol 28 | and implements message transiting in a similar way but using NSFileCoordinator for its file 29 | reading and writing. 30 | */ 31 | @interface MMWormholeCoordinatedFileTransiting : MMWormholeFileTransiting 32 | 33 | /** 34 | The default file writing option is NSDataWritingAtomic. It may be important for your app to use 35 | additional file writing options to control the specific data protection class for message files 36 | being written by your application. When you create your file transiting object, set this property 37 | to the additional writing options you want to use. 38 | */ 39 | @property (nonatomic, assign) NSDataWritingOptions additionalFileWritingOptions; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Source/MMWormholeCoordinatedFileTransiting.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeCoordinatedFileTransiting.h 3 | // 4 | // Copyright (c) 2015 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "MMWormholeCoordinatedFileTransiting.h" 25 | 26 | @implementation MMWormholeCoordinatedFileTransiting 27 | 28 | #pragma mark - MMWormholeTransiting Methods 29 | 30 | - (BOOL)writeMessageObject:(id)messageObject forIdentifier:(NSString *)identifier { 31 | if (identifier == nil) { 32 | return NO; 33 | } 34 | 35 | if (messageObject) { 36 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:messageObject]; 37 | NSString *filePath = [self filePathForIdentifier:identifier]; 38 | NSURL *fileURL = [NSURL fileURLWithPath:filePath]; 39 | 40 | if (data == nil || filePath == nil || fileURL == nil) { 41 | return NO; 42 | } 43 | 44 | NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; 45 | NSError *error = nil; 46 | __block BOOL success = NO; 47 | 48 | [fileCoordinator 49 | coordinateWritingItemAtURL:fileURL 50 | options:0 51 | error:&error 52 | byAccessor:^(NSURL *newURL) { 53 | NSError *writeError = nil; 54 | 55 | success = [data writeToURL:newURL 56 | options:NSDataWritingAtomic | self.additionalFileWritingOptions 57 | error:&writeError]; 58 | }]; 59 | 60 | if (!success) { 61 | return NO; 62 | } 63 | } 64 | 65 | return YES; 66 | } 67 | 68 | - (id)messageObjectForIdentifier:(NSString *)identifier { 69 | if (identifier == nil) { 70 | return nil; 71 | } 72 | 73 | NSString *filePath = [self filePathForIdentifier:identifier]; 74 | NSURL *fileURL = [NSURL fileURLWithPath:filePath]; 75 | 76 | if (filePath == nil || fileURL == nil) { 77 | return nil; 78 | } 79 | 80 | NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; 81 | NSError *error = nil; 82 | __block NSData *data = nil; 83 | 84 | [fileCoordinator 85 | coordinateReadingItemAtURL:fileURL 86 | options:0 87 | error:&error 88 | byAccessor:^(NSURL *newURL) { 89 | data = [NSData dataWithContentsOfURL:newURL]; 90 | }]; 91 | 92 | if (data == nil) { 93 | return nil; 94 | } 95 | 96 | id messageObject = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 97 | 98 | return messageObject; 99 | } 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /Source/MMWormholeFileTransiting.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeFileTransiting.h 3 | // 4 | // Copyright (c) 2015 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "MMWormholeTransiting.h" 25 | 26 | NS_ASSUME_NONNULL_BEGIN 27 | 28 | /** 29 | This class is a default implementation of the MMWormholeTransiting protocol that implements 30 | message transiting by archiving and unarchiving messages that are written and read to files on 31 | disk in an optional directory in the given app group. This default implementation has a relatively 32 | naive implementation of file writing, and simply uses the built in NSData file operations. 33 | 34 | This class is able to be subclassed to provide slightly different file reading and writing behavior 35 | while still maintaining the logic for naming a file within the given directory and app group. 36 | */ 37 | @interface MMWormholeFileTransiting : NSObject 38 | 39 | /** 40 | Designated Initializer. This method must be called with an application group identifier that will 41 | be used to contain passed messages. It is also recommended that you include a directory name for 42 | messages to be read and written, but this parameter is optional. 43 | 44 | @param identifier An application group identifier 45 | @param directory An optional directory to read/write messages 46 | */ 47 | - (instancetype)initWithApplicationGroupIdentifier:(nullable NSString *)identifier 48 | optionalDirectory:(nullable NSString *)directory NS_DESIGNATED_INITIALIZER; 49 | 50 | /** 51 | The File Manager associated with this transiting implementation. You can use this property for 52 | implementing your own variant of file transiting that needs to customize where and how files are 53 | stored. 54 | */ 55 | @property (nonatomic, strong, readonly) NSFileManager *fileManager; 56 | 57 | /** 58 | This method returns the full file path for the message passing directory, including the optional 59 | directory passed in the designated initializer. Subclasses can use this method to provide custom 60 | implementations. 61 | 62 | @return The full path to the message passing directory. 63 | */ 64 | - (nullable NSString *)messagePassingDirectoryPath; 65 | 66 | /** 67 | This method returns the full file path for the file associated with the given message identifier. 68 | It includes the optional directory passed in the designated initializer if there is one. Subclasses 69 | can use this method to provide custom implementations. 70 | 71 | @return The full path to the file associated with the given message identifier. 72 | */ 73 | - (nullable NSString *)filePathForIdentifier:(nullable NSString *)identifier; 74 | 75 | @end 76 | 77 | NS_ASSUME_NONNULL_END 78 | -------------------------------------------------------------------------------- /Source/MMWormholeFileTransiting.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeFileTransiting.m 3 | // 4 | // Copyright (c) 2015 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import 25 | 26 | #import "MMWormholeFileTransiting.h" 27 | 28 | @interface MMWormholeFileTransiting () 29 | 30 | @property (nonatomic, copy) NSString *applicationGroupIdentifier; 31 | @property (nonatomic, copy) NSString *directory; 32 | @property (nonatomic, strong, readwrite) NSFileManager *fileManager; 33 | 34 | @end 35 | 36 | @implementation MMWormholeFileTransiting 37 | 38 | - (instancetype)init { 39 | return [self initWithApplicationGroupIdentifier:@"dev.assertion.nonDesignatedInitializer" 40 | optionalDirectory:nil]; 41 | } 42 | 43 | - (instancetype)initWithApplicationGroupIdentifier:(nullable NSString *)identifier 44 | optionalDirectory:(nullable NSString *)directory { 45 | if ((self = [super init])) { 46 | _applicationGroupIdentifier = [identifier copy]; 47 | _directory = [directory copy]; 48 | _fileManager = [[NSFileManager alloc] init]; 49 | 50 | if (_applicationGroupIdentifier) { 51 | [self checkAppGroupCapabilities]; 52 | } 53 | } 54 | 55 | return self; 56 | } 57 | 58 | 59 | #pragma mark - Private Check App Group Capabilities 60 | 61 | - (void)checkAppGroupCapabilities { 62 | NSAssert([self.fileManager containerURLForSecurityApplicationGroupIdentifier:self.applicationGroupIdentifier] != nil, @"App Group Capabilities may not be correctly configured for your project, or your appGroupIdentifier may not match your project settings. Check Project->Capabilities->App Groups. Three checkmarks should be displayed in the steps section, and the value passed in for your appGroupIdentifier should match the setting in your project file."); 63 | } 64 | 65 | 66 | #pragma mark - Private File Operation Methods 67 | 68 | - (nullable NSString *)messagePassingDirectoryPath { 69 | NSURL *appGroupContainer = [self.fileManager containerURLForSecurityApplicationGroupIdentifier:self.applicationGroupIdentifier]; 70 | NSString *appGroupContainerPath = [appGroupContainer path]; 71 | NSString *directoryPath = appGroupContainerPath; 72 | 73 | if (self.directory != nil) { 74 | directoryPath = [appGroupContainerPath stringByAppendingPathComponent:self.directory]; 75 | } 76 | 77 | [self.fileManager createDirectoryAtPath:directoryPath 78 | withIntermediateDirectories:YES 79 | attributes:nil 80 | error:NULL]; 81 | 82 | return directoryPath; 83 | } 84 | 85 | - (nullable NSString *)filePathForIdentifier:(nullable NSString *)identifier { 86 | if (identifier == nil || identifier.length == 0) { 87 | return nil; 88 | } 89 | 90 | NSString *directoryPath = [self messagePassingDirectoryPath]; 91 | NSString *fileName = [NSString stringWithFormat:@"%@.archive", identifier]; 92 | NSString *filePath = [directoryPath stringByAppendingPathComponent:fileName]; 93 | 94 | return filePath; 95 | } 96 | 97 | 98 | #pragma mark - Public Protocol Methods 99 | 100 | - (BOOL)writeMessageObject:(id)messageObject forIdentifier:(NSString *)identifier { 101 | if (identifier == nil) { 102 | return NO; 103 | } 104 | 105 | if (messageObject) { 106 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:messageObject]; 107 | NSString *filePath = [self filePathForIdentifier:identifier]; 108 | 109 | if (data == nil || filePath == nil) { 110 | return NO; 111 | } 112 | 113 | BOOL success = [data writeToFile:filePath atomically:YES]; 114 | 115 | if (!success) { 116 | return NO; 117 | } 118 | } 119 | 120 | return YES; 121 | } 122 | 123 | - (id)messageObjectForIdentifier:(NSString *)identifier { 124 | if (identifier == nil) { 125 | return nil; 126 | } 127 | 128 | NSString *filePath = [self filePathForIdentifier:identifier]; 129 | 130 | if (filePath == nil) { 131 | return nil; 132 | } 133 | 134 | NSData *data = [NSData dataWithContentsOfFile:filePath]; 135 | 136 | if (data == nil) { 137 | return nil; 138 | } 139 | 140 | id messageObject = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 141 | 142 | return messageObject; 143 | } 144 | 145 | - (void)deleteContentForIdentifier:(NSString *)identifier { 146 | [self.fileManager removeItemAtPath:[self filePathForIdentifier:identifier] error:NULL]; 147 | } 148 | 149 | - (void)deleteContentForAllMessages { 150 | if (self.directory != nil) { 151 | NSArray *messageFiles = [self.fileManager contentsOfDirectoryAtPath:[self messagePassingDirectoryPath] error:NULL]; 152 | 153 | NSString *directoryPath = [self messagePassingDirectoryPath]; 154 | 155 | for (NSString *path in messageFiles) { 156 | NSString *filePath = [directoryPath stringByAppendingPathComponent:path]; 157 | 158 | [self.fileManager removeItemAtPath:filePath error:NULL]; 159 | } 160 | } 161 | } 162 | 163 | @end 164 | -------------------------------------------------------------------------------- /Source/MMWormholeSession.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeSession.h 3 | // 4 | // Copyright (c) 2015 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "MMWormhole.h" 25 | 26 | #import 27 | 28 | NS_ASSUME_NONNULL_BEGIN 29 | 30 | @interface MMWormholeSession : MMWormhole 31 | 32 | /** 33 | This method returns a specific instance of MMWormholeSession that should be used for listening. You 34 | may create your own instances of MMWormholeSession for sending messages, but this is the only object 35 | that will be able to receive messages. 36 | 37 | The reason for this is that MMWormholeSession is based on the WCSession class that is part of the 38 | WatchConnectivity framework provided by Apple, and WCSession is itself a singleton with a single 39 | delegate. Therefore, to receive callbacks, only one MMWormholeSession object may register itself 40 | as a listener. 41 | */ 42 | + (instancetype)sharedListeningSession; 43 | 44 | /** 45 | This method should be called after all of your initial listeners have been set and you are ready to 46 | begin listening for messages. There are likely some listeners that your application requires to be 47 | active so that it won't miss critical messages. You should set up these listeners before calling 48 | this method so that any already queued messages will be delivered immediately when you activate the 49 | session. Any listeners you set up after calling this method may miss messages that were already 50 | queued and waiting to be delivered. 51 | */ 52 | - (void)activateSessionListening; 53 | 54 | @end 55 | 56 | NS_ASSUME_NONNULL_END -------------------------------------------------------------------------------- /Source/MMWormholeSession.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeSession.m 3 | // 4 | // Copyright (c) 2015 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "MMWormholeSession.h" 25 | 26 | @interface MMWormholeSession () 27 | @property (nonatomic, strong) WCSession *session; 28 | @end 29 | 30 | @implementation MMWormholeSession 31 | 32 | + (instancetype)sharedListeningSession { 33 | static MMWormholeSession *sharedSession = nil; 34 | 35 | static dispatch_once_t onceToken; 36 | dispatch_once(&onceToken, ^{ 37 | sharedSession = [[self alloc] initWithApplicationGroupIdentifier:nil 38 | optionalDirectory:nil]; 39 | 40 | sharedSession.session = [WCSession defaultSession]; 41 | sharedSession.session.delegate = sharedSession; 42 | }); 43 | 44 | return sharedSession; 45 | } 46 | 47 | 48 | #pragma mark - Public Interface Methods 49 | 50 | - (void)activateSessionListening { 51 | [self.session activateSession]; 52 | } 53 | 54 | 55 | #pragma mark - Subclass Methods 56 | 57 | - (void)passMessageObject:(nullable id )messageObject identifier:(nullable NSString *)identifier { 58 | NSAssert(NO, @"Message passing is not supported in MMWormholeSession. Please use MMWormhole with an MMWormholeSessionTransiting type to pass messages using WatchConnectivity."); 59 | } 60 | 61 | 62 | - (nullable id)messageWithIdentifier:(nullable NSString *)identifier { 63 | NSAssert(NO, @"Message passing is not supported in MMWormholeSession. Please use MMWormhole with an MMWormholeSessionTransiting type to pass messages using WatchConnectivity."); 64 | return nil; 65 | } 66 | 67 | - (void)clearMessageContentsForIdentifier:(nullable NSString *)identifier { 68 | NSAssert(NO, @"Message passing is not supported in MMWormholeSession. Please use MMWormhole with an MMWormholeSessionTransiting type to pass messages using WatchConnectivity."); 69 | } 70 | 71 | - (void)clearAllMessageContents { 72 | NSAssert(NO, @"Message passing is not supported in MMWormholeSession. Please use MMWormhole with an MMWormholeSessionTransiting type to pass messages using WatchConnectivity."); 73 | } 74 | 75 | 76 | #pragma mark - Private Subclass Methods 77 | 78 | - (void)registerForNotificationsWithIdentifier:(nullable NSString *)identifier { 79 | // MMWormholeSession uses WatchConnectivity delegate callbacks and does not support Darwin Notification Center notifications. 80 | } 81 | 82 | - (void)unregisterForNotificationsWithIdentifier:(nullable NSString *)identifier { 83 | // MMWormholeSession uses WatchConnectivity delegate callbacks and does not support Darwin Notification Center notifications. 84 | } 85 | 86 | 87 | #pragma mark - WCSessionDelegate Methods 88 | 89 | - (void)session:(nonnull WCSession *)session didReceiveMessage:(nonnull NSDictionary *)message { 90 | for (NSString *identifier in message.allKeys) { 91 | NSData *data = message[identifier]; 92 | id messageObject = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 93 | 94 | [self notifyListenerForMessageWithIdentifier:identifier message:messageObject]; 95 | } 96 | } 97 | 98 | - (void)session:(WCSession *)session didReceiveApplicationContext:(NSDictionary *)applicationContext { 99 | for (NSString *identifier in applicationContext.allKeys) { 100 | NSData *data = applicationContext[identifier]; 101 | id messageObject = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 102 | 103 | [self notifyListenerForMessageWithIdentifier:identifier message:messageObject]; 104 | } 105 | } 106 | 107 | - (void)session:(nonnull WCSession *)session didReceiveFile:(nonnull WCSessionFile *)file { 108 | NSString *identifier = file.metadata[@"identifier"]; 109 | 110 | NSData *data = [NSData dataWithContentsOfURL:file.fileURL]; 111 | id messageObject = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 112 | 113 | [self notifyListenerForMessageWithIdentifier:identifier message:messageObject]; 114 | 115 | MMWormholeFileTransiting *wormholeMessenger = self.wormholeMessenger; 116 | 117 | if ([wormholeMessenger respondsToSelector:@selector(filePathForIdentifier:)] == NO) { 118 | return; 119 | } 120 | 121 | if (messageObject) { 122 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:messageObject]; 123 | 124 | NSString *filePath = [wormholeMessenger filePathForIdentifier:identifier]; 125 | 126 | if (data == nil || filePath == nil) { 127 | return; 128 | } 129 | 130 | [data writeToFile:filePath atomically:YES]; 131 | } 132 | } 133 | 134 | @end 135 | 136 | -------------------------------------------------------------------------------- /Source/MMWormholeSessionContextTransiting.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeSessionContextTransiting.h 3 | // 4 | // Copyright (c) 2015 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "MMWormholeFileTransiting.h" 25 | 26 | /** 27 | This class provides support for the WatchConnectivity framework's Application Context message 28 | reading and writing ability. This class will pass it's messages directly via the 29 | -updateApplicationContext method, and read message values from application context. 30 | 31 | This class also uses a local mutable dictionary for maintaining a more consistent version of your 32 | wormhole-based application context. The contents of the local dictionary are merged with the 33 | application context for passing messages. Clearing message contents on a wormhole using this 34 | transiting implementation will clear both the applicationContext as well as the local mutable 35 | dictionary. 36 | 37 | @discussion This class should be treated as the default MMWormholeTransiting implementation for 38 | applications wanting to leverage the WatchConnectivity framework within MMWormhole. The application 39 | context provides the best of both real time message passing and baked in state persistence for 40 | setting up your UI. 41 | */ 42 | @interface MMWormholeSessionContextTransiting : MMWormholeFileTransiting 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /Source/MMWormholeSessionContextTransiting.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeSessionContextTransiting.m 3 | // 4 | // Copyright (c) 2015 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "MMWormholeSessionContextTransiting.h" 25 | 26 | #import 27 | 28 | @interface MMWormholeSessionContextTransiting () 29 | @property (nonatomic, strong) WCSession *session; 30 | @property (nonatomic, strong) NSMutableDictionary *lastContext; 31 | @end 32 | 33 | @implementation MMWormholeSessionContextTransiting 34 | 35 | - (instancetype)initWithApplicationGroupIdentifier:(nullable NSString *)identifier 36 | optionalDirectory:(nullable NSString *)directory { 37 | if ((self = [super initWithApplicationGroupIdentifier:identifier optionalDirectory:directory])) { 38 | // Setup transiting with the default session 39 | _session = [WCSession defaultSession]; 40 | 41 | // Ensure that the MMWormholeSession's delegate is set to enable message sending 42 | NSAssert(_session.delegate != nil, @"WCSession's delegate is required to be set before you can send messages. Please initialize the MMWormholeSession sharedListeningSession object prior to creating a separate wormhole using the MMWormholeSessionTransiting classes."); 43 | } 44 | 45 | return self; 46 | } 47 | 48 | - (BOOL)writeMessageObject:(id)messageObject forIdentifier:(NSString *)identifier { 49 | if (identifier == nil) { 50 | return NO; 51 | } 52 | 53 | if ([WCSession isSupported] == false) { 54 | return NO; 55 | } 56 | 57 | if (messageObject) { 58 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:messageObject]; 59 | 60 | if (data == nil) { 61 | return NO; 62 | } 63 | 64 | NSMutableDictionary *applicationContext = [self.session.applicationContext mutableCopy]; 65 | 66 | if (applicationContext == nil) { 67 | applicationContext = [NSMutableDictionary new]; 68 | } 69 | 70 | if (self.lastContext == nil) { 71 | self.lastContext = applicationContext; 72 | } 73 | 74 | NSMutableDictionary *currentContext = applicationContext; 75 | [currentContext addEntriesFromDictionary:self.lastContext]; 76 | currentContext[identifier] = data; 77 | 78 | self.lastContext = currentContext; 79 | 80 | [self.session updateApplicationContext:currentContext error:nil]; 81 | } 82 | 83 | return NO; 84 | } 85 | 86 | - (nullable id)messageObjectForIdentifier:(nullable NSString *)identifier { 87 | NSDictionary *receivedContext = self.session.receivedApplicationContext; 88 | NSData *data = receivedContext[identifier]; 89 | 90 | if (data == nil) { 91 | NSDictionary *currentContext = self.session.applicationContext; 92 | data = currentContext[identifier]; 93 | 94 | if (data == nil) { 95 | return nil; 96 | } 97 | } 98 | 99 | id messageObject = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 100 | 101 | return messageObject; 102 | } 103 | 104 | - (void)deleteContentForIdentifier:(nullable NSString *)identifier { 105 | [self.lastContext removeObjectForKey:identifier]; 106 | 107 | NSMutableDictionary *currentContext = [self.session.applicationContext mutableCopy]; 108 | [currentContext removeObjectForKey:identifier]; 109 | 110 | [self.session updateApplicationContext:currentContext error:nil]; 111 | } 112 | 113 | - (void)deleteContentForAllMessages { 114 | [self.lastContext removeAllObjects]; 115 | [self.session updateApplicationContext:@{} error:nil]; 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /Source/MMWormholeSessionFileTransiting.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeSessionFileTransiting.h 3 | // 4 | // Copyright (c) 2015 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "MMWormholeFileTransiting.h" 25 | 26 | /** 27 | This class provides support for the WatchConnectivity framework's file transfer ability. This class 28 | will behave very similar to the MMWormholeFileTransiting implementation, meaning it will archive 29 | messages to disk as files and send them via the WatchConnectivity framework's -transferFile API. 30 | 31 | @warning In order for your Wormhole to support reading the contents of transferred file messages, 32 | you will need to set this object as the 'wormholeMessenger' property on the 33 | [MMWormholeSession sharedListeningSession]. The reason for this is that the sharedListeningSession 34 | requires a configured application group and optional file directory in order to know where to save 35 | received files. If you don't set this object as the wormholeMessenger you will still be notified 36 | when your receive files, but you will be responsible for storing the contents yourself and they 37 | won't be persisted for you. 38 | 39 | @discussion This class should only be used in very specific circumstances. Typically speaking, if 40 | you find yourself needing to use the WatchConnectivity framework's file transfer APIs you will best 41 | be served by using the WatchConnectivity framework directly and bypassing MMWormhole. This class 42 | is provided as a basic implementation for simple use cases and isn't intended to be the core of 43 | your file based message transfer system. 44 | */ 45 | @interface MMWormholeSessionFileTransiting : MMWormholeFileTransiting 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Source/MMWormholeSessionFileTransiting.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeSessionFileTransiting.m 3 | // 4 | // Copyright (c) 2015 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "MMWormholeSessionFileTransiting.h" 25 | 26 | #import 27 | 28 | @interface MMWormholeSessionFileTransiting () 29 | @property (nonatomic, strong) WCSession *session; 30 | @end 31 | 32 | @implementation MMWormholeSessionFileTransiting 33 | 34 | - (instancetype)initWithApplicationGroupIdentifier:(nullable NSString *)identifier 35 | optionalDirectory:(nullable NSString *)directory { 36 | if ((self = [super initWithApplicationGroupIdentifier:identifier optionalDirectory:directory])) { 37 | // Setup transiting with the default session 38 | _session = [WCSession defaultSession]; 39 | 40 | // Ensure that the MMWormholeSession's delegate is set to enable message sending 41 | NSAssert(_session.delegate != nil, @"WCSession's delegate is required to be set before you can send messages. Please initialize the MMWormholeSession sharedListeningSession object prior to creating a separate wormhole using the MMWormholeSessionTransiting classes."); 42 | } 43 | 44 | return self; 45 | } 46 | 47 | - (BOOL)writeMessageObject:(id)messageObject forIdentifier:(NSString *)identifier { 48 | if (identifier == nil) { 49 | return NO; 50 | } 51 | 52 | if ([WCSession isSupported] == false) { 53 | return NO; 54 | } 55 | 56 | if (messageObject) { 57 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:messageObject]; 58 | 59 | if (data == nil) { 60 | return NO; 61 | } 62 | 63 | NSString *tempDir = [self messagePassingDirectoryPath]; 64 | 65 | if (tempDir == nil) { 66 | tempDir = NSTemporaryDirectory(); 67 | } 68 | 69 | NSString *tempPath = [tempDir stringByAppendingPathComponent:identifier]; 70 | NSURL *tempURL = [NSURL fileURLWithPath:tempPath]; 71 | 72 | NSError *fileError = nil; 73 | 74 | [data writeToURL:tempURL options:NSDataWritingAtomic error:&fileError]; 75 | 76 | [self.session transferFile:tempURL metadata:@{@"identifier" : identifier}]; 77 | } 78 | 79 | return NO; 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /Source/MMWormholeSessionMessageTransiting.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeSessionMessageTransiting.h 3 | // 4 | // Copyright (c) 2015 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | 25 | #import "MMWormholeFileTransiting.h" 26 | 27 | /** 28 | This class provides support for the WatchConnectivity framework's real time message passing ability. 29 | This is the only version of the MMWormholeSessionTransiting system that will be able to wake up 30 | your iPhone app in the background. As such, this class has a very specific purpose. It also means 31 | that it will most likely be used only by your Apple Watch app, or in very very specific instances 32 | by your iPhone app. Typically, your iPhone app will want to use the 33 | MMWormholeSessionContextTransiting option instead. 34 | 35 | @warning Waking up the iPhone app from a Watch app is an expensive operation. You should only use 36 | this transiting implementation when this action is required for your application. Otherwise you are 37 | better served by using the MMWormholeSessionContextTransiting implementation. 38 | 39 | @warning This transiting implementation does not support reading message contents because real time 40 | messages are delivered once and not persisted. 41 | 42 | @discussion This class should be used in cases where your Apple Watch app needs to ensure your 43 | iPhone app is running to receive a message and take some action on it. One example of this would be 44 | to start background location tracking or audio. 45 | */ 46 | @interface MMWormholeSessionMessageTransiting : MMWormholeFileTransiting 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Source/MMWormholeSessionMessageTransiting.m: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeSessionMessageTransiting.m 3 | // 4 | // Copyright (c) 2015 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import "MMWormholeSessionMessageTransiting.h" 25 | 26 | #import 27 | 28 | @interface MMWormholeSessionMessageTransiting () 29 | @property (nonatomic, strong) WCSession *session; 30 | @end 31 | 32 | @implementation MMWormholeSessionMessageTransiting 33 | 34 | - (instancetype)initWithApplicationGroupIdentifier:(nullable NSString *)identifier 35 | optionalDirectory:(nullable NSString *)directory { 36 | if ((self = [super initWithApplicationGroupIdentifier:identifier optionalDirectory:directory])) { 37 | // Setup transiting with the default session 38 | _session = [WCSession defaultSession]; 39 | 40 | // Ensure that the MMWormholeSession's delegate is set to enable message sending 41 | NSAssert(_session.delegate != nil, @"WCSession's delegate is required to be set before you can send messages. Please initialize the MMWormholeSession sharedListeningSession object prior to creating a separate wormhole using the MMWormholeSessionTransiting classes."); 42 | } 43 | 44 | return self; 45 | } 46 | 47 | 48 | #pragma mark - MMWormholeFileTransiting Subclass Methods 49 | 50 | - (nullable NSString *)messagePassingDirectoryPath { 51 | return nil; 52 | } 53 | 54 | 55 | #pragma mark - MMWormholeTransiting Protocol Methods 56 | 57 | - (BOOL)writeMessageObject:(id)messageObject forIdentifier:(NSString *)identifier { 58 | if (identifier == nil) { 59 | return NO; 60 | } 61 | 62 | if (messageObject) { 63 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:messageObject]; 64 | 65 | if (data == nil) { 66 | return NO; 67 | } 68 | 69 | if ([self.session isReachable]) { 70 | [self.session 71 | sendMessage:@{identifier : data} 72 | replyHandler:nil 73 | errorHandler:^(NSError * __nonnull error) { 74 | 75 | }]; 76 | } 77 | } 78 | 79 | return NO; 80 | } 81 | 82 | - (nullable id)messageObjectForIdentifier:(nullable NSString *)identifier { 83 | return nil; 84 | } 85 | 86 | - (void)deleteContentForIdentifier:(nullable NSString *)identifier { 87 | } 88 | 89 | - (void)deleteContentForAllMessages { 90 | } 91 | 92 | @end -------------------------------------------------------------------------------- /Source/MMWormholeTransiting.h: -------------------------------------------------------------------------------- 1 | // 2 | // MMWormholeTransiting.h 3 | // 4 | // Copyright (c) 2015 Mutual Mobile (http://www.mutualmobile.com/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #import 25 | 26 | NS_ASSUME_NONNULL_BEGIN 27 | 28 | /** 29 | This protocol defines the public interface for classes wishing to support the transiting of data 30 | between two sides of the wormhole. Transiting is defined as passage between two points, and in this 31 | case it involves both the reading and writing of messages as well as the deletion of message 32 | contents. 33 | */ 34 | @protocol MMWormholeTransiting 35 | 36 | /** 37 | This method is responsible for writing a given message object in a persisted format for a given 38 | identifier. The method should return YES if the message was successfully saved. The message object 39 | may be nil, in which case YES should also be returned. Returning YES from this method results in a 40 | notification being fired which will trigger the corresponding listener block for the given 41 | identifier. 42 | 43 | @param messageObject The message object to be passed. 44 | This object may be nil. In this the method should return YES. 45 | @param identifier The identifier for the message 46 | @return YES indicating that a notification should be sent and NO otherwise 47 | */ 48 | - (BOOL)writeMessageObject:(nullable id)messageObject forIdentifier:(NSString *)identifier; 49 | 50 | /** 51 | This method is responsible for reading and returning the contents of a given message. It should 52 | understand the structure of messages saved by the implementation of the above writeMessageObject 53 | method and be able to read those messages and return their contents. 54 | 55 | @param identifier The identifier for the message 56 | */ 57 | - (nullable id)messageObjectForIdentifier:(nullable NSString *)identifier; 58 | 59 | /** 60 | This method should clear the persisted contents of a specific message with a given identifier. 61 | 62 | @param identifier The identifier for the message 63 | */ 64 | - (void)deleteContentForIdentifier:(nullable NSString *)identifier; 65 | 66 | /** 67 | This method should clear the contents of all messages passed to the wormhole. 68 | */ 69 | - (void)deleteContentForAllMessages; 70 | 71 | @end 72 | 73 | @protocol MMWormholeTransitingDelegate 74 | 75 | - (void)notifyListenerForMessageWithIdentifier:(nullable NSString *)identifier message:(nullable id)message; 76 | 77 | @end 78 | 79 | NS_ASSUME_NONNULL_END --------------------------------------------------------------------------------