├── .gitignore ├── .gitmodules ├── WFNotificationCenter.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ └── WFNotificationCenter.xcscheme └── project.pbxproj ├── WFNotificationCenter.podspec ├── WFNotificationCenterTests ├── Info.plist └── WFNotificationCenterTests.m ├── LICENSE ├── README.md └── WFNotificationCenter ├── WFDistributedNotificationCenter.h └── WFDistributedNotificationCenter.m /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | *.xccheckout 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "WFNotificationCenter/fishhook"] 2 | path = WFNotificationCenterTests/fishhook 3 | url = git://github.com/facebook/fishhook.git 4 | -------------------------------------------------------------------------------- /WFNotificationCenter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WFNotificationCenter.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'WFNotificationCenter' 3 | s.version = '0.1' 4 | s.license = 'MIT' 5 | s.summary = 'A notification center for app groups.' 6 | s.homepage = 'https://github.com/DeskConnect/WFNotificationCenter' 7 | s.author = { 'Conrad Kramer' => 'conrad@deskconnect.com' } 8 | s.source = { :git => 'https://github.com/DeskConnect/WFNotificationCenter.git', 9 | :tag => s.version } 10 | s.source_files = 'WFNotificationCenter' 11 | s.requires_arc = true 12 | 13 | s.ios.deployment_target = '7.0' 14 | end 15 | -------------------------------------------------------------------------------- /WFNotificationCenterTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.deskconnect.$(PRODUCT_NAME:rfc1034identifier) 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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 DeskConnect, Inc. (http://deskconnect.com/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WFNotificationCenter 2 | 3 | `WFDistributedNotificationCenter` is a notification center for communicating between your app and your extensions. 4 | 5 | ## Features 6 | 7 | - Works between any process in the same application group 8 | - API compatible with `NSNotificationCenter` 9 | - Supports notifications with rich `userInfo` dictionaries 10 | - Handles process suspension gracefully (and delivers notifications upon resume) 11 | 12 | ## Usage 13 | 14 | ```objc 15 | WFDistributedNotificationCenter *center = [[WFDistributedNotificationCenter alloc] initWithSecurityApplicationGroupIdentifier:@"group.test"]; 16 | 17 | [center postNotificationName:@"UpdatedStuff" object:nil userInfo:@{@"ChangedIds": @[@3,@4,@10]}]; 18 | ``` 19 | 20 | ## Installation 21 | 22 | ### Manually 23 | 24 | Add `WFDistributedNotificationCenter.h` and `WFDistributedNotificationCenter.m` into your project. 25 | 26 | ## Architecture 27 | 28 | The traditional model for distributed notifications is client-server, where applications are the clients and a daemon (`distnoted`, `notifyd`, etc) acts as the server. The server receives notifications from its clients and distributes them appropriately 29 | 30 | This model doesn't work on iOS. There isn't a daemon that supports posting rich notifications, and you can't write your own. With `WFDistributedNotificationCenter`, the connections are decentralized. When applications want to listen for a notification, they create a local mach server and update a shared registry with the mach port name. Clients posting notifications read this registry and send notifications to the appropriate process. This communication happens using the public `CFMessagePort` API, and the shared registry is currently stored in the app group container. 31 | 32 | ## Notes 33 | 34 | - It is not meant to be used between two separate apps yet, only between an app and its extensions (it is not designed to be runtime compatible across versions) 35 | - It allows sending any object in the `userInfo` dictionary that adheres to `NSSecureCoding`, provided that the observer specifies the class in `allowedClasses` 36 | - Test coverage is incomplete, but being worked on 37 | 38 | ## License 39 | 40 | WFNotificationCenter is available under the MIT license. See the LICENSE file for more info. 41 | -------------------------------------------------------------------------------- /WFNotificationCenter.xcodeproj/xcshareddata/xcschemes/WFNotificationCenter.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 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /WFNotificationCenter/WFDistributedNotificationCenter.h: -------------------------------------------------------------------------------- 1 | // 2 | // WFDistributedNotificationCenter.h 3 | // WFNotificationCenter 4 | // 5 | // Created by Conrad Kramer on 3/5/15. 6 | // Copyright (c) 2015 DeskConnect, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | `WFDistributedNotificationCenter` is a notification center that works within app groups on iOS. 13 | 14 | It behaves just like `NSDistributedNotificationCenter`, which in turn behaves just like `NSNotificationCenter`, with the following differences: 15 | 16 | - NSNotification `object` properties must be `NSString` objects, if used. 17 | - All objects in a notification's `userInfo` dictionary must adhere to `NSSecureCoding`. 18 | - Notifications are always delivered asynchronously, even within the same process 19 | */ 20 | @interface WFDistributedNotificationCenter : NSObject 21 | 22 | /** 23 | Initializes and returns a notification center object that can communicate with other notification center objects that share the same `groupIdentifier`. 24 | 25 | @param groupIdentifier The application group identifier shared by the code you want to communicate between. 26 | 27 | @return The initialized notification center object, or `nil` if the object couldn't be created. 28 | 29 | @note Your app must have a `com.apple.security.application-groups` entitlement for the specified application group. 30 | */ 31 | - (instancetype)initWithSecurityApplicationGroupIdentifier:(NSString *)groupIdentifier NS_DESIGNATED_INITIALIZER; 32 | 33 | ///------------------------------------------------ 34 | /// @name Adding Observers 35 | ///------------------------------------------------ 36 | 37 | /** 38 | Adds a target-selector pair to the reciever's dispatch table that is called when a notification is received matching the specified name and object. This method registers the observer with the set of property list classes as the allowed classes. 39 | 40 | @param observer The object registering as an observer. The observer is not retained, and must not be `nil`. 41 | @param aSelector The selector of the message sent to `observer` when a notification is received. Must not be `0`. The method specified by `aSelector` must have either zero arguments or one argument (of type `NSNotification`). 42 | @param aName The notification name for which to register the observer. Only notifications with this name are delivered to the observer. When `nil`, the observer receives notifications of any name. 43 | @param anObject The notification object for which to register the observer. Only notifications with the same object are delivered to the observer. When `nil`, the observer receives notifications with any object. 44 | 45 | @see -addObserver:selector:name:object:allowedClasses: 46 | */ 47 | - (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(NSString *)anObject; 48 | 49 | /** 50 | Adds a target-selector pair to the reciever's dispatch table that is called when a notification is received matching the specified name and object. 51 | 52 | @param observer The object registering as an observer. The observer is not retained, and must not be `nil`. 53 | @param aSelector The selector of the message sent to `observer` when a notification is received. Must not be `0`. The method specified by `aSelector` must have either zero arguments or one argument (of type `NSNotification`). 54 | @param aName The notification name for which to register the observer. Only notifications with this name are delivered to the observer. When `nil`, the observer receives notifications of any name. 55 | @param anObject The notification object for which to register the observer. Only notifications with the same object are delivered to the observer. When `nil`, the observer receives notifications with any object. 56 | @param allowedClasses The set of classes allowed for secure coding. The receiver uses this set of classes to decode the `userInfo` dictionary of the received notification. If an empty set or `nil` is specified, the set of property list classes is used. If a notification is received that contains classes not found in this parameter, the `userInfo` dictionary is dropped and a warning is logged. 57 | 58 | @see [Whitelisting a Class for Use Inside Containers](https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html#//apple_ref/doc/uid/10000172i-SW6-SW26) 59 | */ 60 | - (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(NSString *)anObject allowedClasses:(NSSet *)allowedClasses; 61 | 62 | /** 63 | Adds a block to the reciever's dispatch table that is called when a notification is received matching the specified name and object. This method registers the block with the set of property list classes as the allowed classes. 64 | 65 | @param name The notification name for which to register the block. Only notifications with this name are used to add the block to the operation queue. When `nil`, the block will be added to the operation queue for notifications of any name. 66 | @param obj The notification object for which to register the block. Only notifications with the same object are used to add the block to the operation queue. When `nil`, the block will be added to the operation queue for notifications with any object. 67 | @param queue The operation queue to which the block should be added. If you pass `nil`, the block is run on a background queue. 68 | @param block The block to be executed when a notification is received. The block is copied by the receiver and held until the observer registration is removed. The block has no return value and takes one argument: the notification. 69 | 70 | @return An opaque object to act as the observer. The `block` and `queue` parameters will both be retained until the observer is removed using `removeObserver:`. 71 | 72 | @see -addObserverForName:object:allowedClasses:queue:usingBlock: 73 | */ 74 | - (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block; 75 | 76 | /** 77 | Adds a block to the reciever's dispatch table that is called when a notification is received matching the specified name and object. 78 | 79 | @param name The notification name for which to register the block. Only notifications with this name are used to add the block to the operation queue. When `nil`, the block will be added to the operation queue for notifications of any name. 80 | @param obj The notification object for which to register the block. Only notifications with the same object are used to add the block to the operation queue. When `nil`, the block will be added to the operation queue for notifications with any object. 81 | @param allowedClasses The set of classes allowed for secure coding. The receiver uses this set of classes to decode the `userInfo` dictionary of the received notification. If an empty set or `nil` is specified, the set of property list classes is used. If a notification is received that contains classes not found in this parameter, the `userInfo` dictionary is dropped and a warning is logged. 82 | @param queue The operation queue to which the block should be added. If you pass `nil`, the block is run on a background queue. 83 | @param block The block to be executed when a notification is received. The block is copied by the receiver and held until the observer registration is removed. The block has no return value and takes one argument: the notification. 84 | 85 | @return An opaque object to act as the observer. The `block` and `queue` parameters will both be retained until the observer is removed using `removeObserver:`. 86 | 87 | @see [Whitelisting a Class for Use Inside Containers](https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html#//apple_ref/doc/uid/10000172i-SW6-SW26) 88 | */ 89 | - (id)addObserverForName:(NSString *)name object:(id)obj allowedClasses:(NSSet *)allowedClasses queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block; 90 | 91 | ///------------------------------------------------ 92 | /// @name Posting Notifications 93 | ///------------------------------------------------ 94 | 95 | /** 96 | Posts the given notification to all registered observers. 97 | 98 | @param notification The notification to post. This value must not be `nil`. The `userInfo` dictionary must conform to `NSSecureCoding`. 99 | */ 100 | - (void)postNotification:(NSNotification *)notification; 101 | 102 | /** 103 | Creates a notification with a given name and object and posts it to all registered observers. 104 | 105 | @param aName The name of the notification. Must not be `nil`. 106 | @param anObject The object of the notification. Can be `nil`. 107 | */ 108 | - (void)postNotificationName:(NSString *)aName object:(NSString *)anObject; 109 | 110 | /** 111 | Creates a notification with a given name, object and userInfo dictionary and posts it to all registered observers. 112 | 113 | @param aName The name of the notification. Must not be `nil`. 114 | @param anObject The object of the notification. Can be `nil`. 115 | @param aUserInfo The user info of the notification. Must conform to `NSSecureCoding`. Can be `nil`. 116 | */ 117 | - (void)postNotificationName:(NSString *)aName object:(NSString *)anObject userInfo:(NSDictionary *)aUserInfo; 118 | 119 | ///------------------------------------------------ 120 | /// @name Removing Observers 121 | ///------------------------------------------------ 122 | 123 | /** 124 | Removes all the entries specifying the given observer from the receiver’s dispatch table. 125 | 126 | @param observer The observer to remove. Must not be `nil`, or this method will have no effect. 127 | */ 128 | - (void)removeObserver:(id)observer; 129 | 130 | /** 131 | Removes the entries from the reciever's dispatch table that match the specified observer, name, and object. 132 | 133 | @param observer Observer to remove from the dispatch table. Specify an observer to remove only entries for this observer. Must not be `nil`, or this method will have no effect. 134 | @param aName Name of the notification to remove entries for from from dispatch table. Specify a notification name to remove only entries that have this notification name. When `nil`, observers with all notification names are considered for removal. 135 | @param anObject Object of the notification to remove entries for from the dispatch table. Specify a notification object to remove only entries that specify this object. When `nil`, observers with all notification objects are considered for removal. 136 | */ 137 | - (void)removeObserver:(id)observer name:(NSString *)aName object:(id)anObject; 138 | 139 | @end 140 | -------------------------------------------------------------------------------- /WFNotificationCenterTests/WFNotificationCenterTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // WFNotificationCenterTests.m 3 | // WFNotificationCenter 4 | // 5 | // Created by Conrad Kramer on 3/8/15. 6 | // Copyright (c) 2015 DeskConnect, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #include 11 | 12 | #import "WFDistributedNotificationCenter.h" 13 | #import "fishhook.h" 14 | 15 | #define WFAssertEqualNotifications(notification1, notification2, ...) \ 16 | XCTAssertEqualObjects([(NSNotification *)notification1 name], [(NSNotification *)notification2 name], __VA_ARGS__); \ 17 | XCTAssertEqualObjects([(NSNotification *)notification1 object], [(NSNotification *)notification2 object], __VA_ARGS__); \ 18 | XCTAssertEqualObjects([(NSNotification *)notification1 userInfo], [(NSNotification *)notification2 userInfo] __VA_ARGS__,); 19 | 20 | @interface WFNotificationCenterTests : XCTestCase 21 | 22 | @property (nonatomic, strong) WFDistributedNotificationCenter *center; 23 | @property (nonatomic, strong) NSMutableArray *notifications; 24 | @property (nonatomic, readonly) NSNotification *lastNotification; 25 | 26 | @end 27 | 28 | static pid_t custom_pid = -1; 29 | static pid_t (*orig_getpid)(); 30 | 31 | static pid_t custom_getpid() { 32 | if (custom_pid > 0) 33 | return custom_pid; 34 | return orig_getpid(); 35 | } 36 | 37 | static void set_pid(pid_t pid) { 38 | custom_pid = pid; 39 | } 40 | 41 | static void reset_pid() { 42 | custom_pid = -1; 43 | } 44 | 45 | static NSString * const WFNotificationCenterTestAppGroup = @"com.deskconnect.test"; 46 | static NSString * const WFTestNotificationName = @"Test"; 47 | static NSString * const WFSecondTestNotificationName = @"Test2"; 48 | static NSString * const WFTestObject = @"Object"; 49 | static NSString * const WFSecondTestObject = @"Object2"; 50 | 51 | @implementation WFNotificationCenterTests 52 | 53 | + (void)load { 54 | orig_getpid = dlsym(RTLD_DEFAULT, "getpid"); 55 | rebind_symbols((struct rebinding[1]){"getpid", custom_getpid}, 1); 56 | } 57 | 58 | - (void)setUp { 59 | [super setUp]; 60 | [self willChangeValueForKey:NSStringFromSelector(@selector(lastNotification))]; 61 | self.notifications = [NSMutableArray new]; 62 | [self didChangeValueForKey:NSStringFromSelector(@selector(lastNotification))]; 63 | self.center = [[WFDistributedNotificationCenter alloc] initWithSecurityApplicationGroupIdentifier:WFNotificationCenterTestAppGroup]; 64 | } 65 | 66 | - (void)tearDown { 67 | self.center = nil; 68 | self.notifications = nil; 69 | [super tearDown]; 70 | } 71 | 72 | - (void)receive:(NSNotification *)notification { 73 | [self willChangeValueForKey:NSStringFromSelector(@selector(lastNotification))]; 74 | [self.notifications addObject:notification]; 75 | [self didChangeValueForKey:NSStringFromSelector(@selector(lastNotification))]; 76 | } 77 | 78 | - (NSNotification *)lastNotification { 79 | return self.notifications.lastObject; 80 | } 81 | 82 | #pragma mark - Serialization 83 | 84 | - (void)testNonStringObject { 85 | NSNotification *notification = [NSNotification notificationWithName:WFTestNotificationName object:@YES]; 86 | XCTAssertThrowsSpecificNamed([self.center postNotification:notification], NSException, NSInternalInconsistencyException); 87 | } 88 | 89 | - (void)testSecureCoding { 90 | [self.center addObserver:self selector:@selector(receive:) name:nil object:nil]; 91 | NSNotification *notification = [NSNotification notificationWithName:WFTestNotificationName object:@"Foo" userInfo:@{@"Bar": @YES, @"Baz": @[@1,@2,@3]}]; 92 | 93 | [self keyValueObservingExpectationForObject:self keyPath:NSStringFromSelector(@selector(lastNotification)) expectedValue:nil]; 94 | [self.center postNotification:notification]; 95 | [self waitForExpectationsWithTimeout:0.5f handler:nil]; 96 | WFAssertEqualNotifications(self.lastNotification, notification); 97 | } 98 | 99 | #pragma mark - Observers 100 | 101 | - (void)testAddingObserver { 102 | [self.center addObserver:self selector:@selector(receive:) name:nil object:nil]; 103 | 104 | [self keyValueObservingExpectationForObject:self keyPath:NSStringFromSelector(@selector(lastNotification)) expectedValue:nil]; 105 | [self.center postNotificationName:WFTestNotificationName object:nil]; 106 | [self waitForExpectationsWithTimeout:0.5f handler:nil]; 107 | XCTAssertEqualObjects(self.lastNotification.name, WFTestNotificationName); 108 | 109 | [self keyValueObservingExpectationForObject:self keyPath:NSStringFromSelector(@selector(lastNotification)) expectedValue:nil]; 110 | [self.center postNotificationName:WFSecondTestNotificationName object:nil]; 111 | [self waitForExpectationsWithTimeout:0.5f handler:nil]; 112 | XCTAssertEqualObjects(self.lastNotification.name, WFSecondTestNotificationName); 113 | } 114 | 115 | - (void)testAddingNamedObserver { 116 | [self.center addObserver:self selector:@selector(receive:) name:WFTestNotificationName object:nil]; 117 | 118 | [self.center postNotificationName:WFSecondTestNotificationName object:nil]; 119 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]]; 120 | XCTAssertNil(self.lastNotification); 121 | 122 | [self keyValueObservingExpectationForObject:self keyPath:NSStringFromSelector(@selector(lastNotification)) expectedValue:nil]; 123 | [self.center postNotificationName:WFTestNotificationName object:nil]; 124 | [self waitForExpectationsWithTimeout:0.5f handler:nil]; 125 | XCTAssertEqualObjects(self.lastNotification.name, WFTestNotificationName); 126 | } 127 | 128 | - (void)testAddingObserverWithObject { 129 | [self.center addObserver:self selector:@selector(receive:) name:nil object:WFTestObject]; 130 | 131 | [self.center postNotificationName:WFTestNotificationName object:WFSecondTestObject]; 132 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]]; 133 | XCTAssertNil(self.lastNotification); 134 | 135 | [self keyValueObservingExpectationForObject:self keyPath:NSStringFromSelector(@selector(lastNotification)) expectedValue:nil]; 136 | [self.center postNotificationName:WFTestNotificationName object:WFTestObject]; 137 | [self waitForExpectationsWithTimeout:0.5f handler:nil]; 138 | XCTAssertEqualObjects(self.lastNotification.name, WFTestNotificationName); 139 | XCTAssertEqualObjects(self.lastNotification.object, WFTestObject); 140 | 141 | [self keyValueObservingExpectationForObject:self keyPath:NSStringFromSelector(@selector(lastNotification)) expectedValue:nil]; 142 | [self.center postNotificationName:WFSecondTestNotificationName object:WFTestObject]; 143 | [self waitForExpectationsWithTimeout:0.5f handler:nil]; 144 | XCTAssertEqualObjects(self.lastNotification.name, WFSecondTestNotificationName); 145 | XCTAssertEqualObjects(self.lastNotification.object, WFTestObject); 146 | } 147 | 148 | - (void)testAddingNamedObserverWithObject { 149 | [self.center addObserver:self selector:@selector(receive:) name:WFTestNotificationName object:WFTestObject]; 150 | 151 | [self.center postNotificationName:WFTestNotificationName object:WFSecondTestObject]; 152 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]]; 153 | XCTAssertNil(self.lastNotification); 154 | 155 | [self.center postNotificationName:WFSecondTestObject object:WFTestObject]; 156 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]]; 157 | XCTAssertNil(self.lastNotification); 158 | 159 | [self keyValueObservingExpectationForObject:self keyPath:NSStringFromSelector(@selector(lastNotification)) expectedValue:nil]; 160 | [self.center postNotificationName:WFTestNotificationName object:WFTestObject]; 161 | [self waitForExpectationsWithTimeout:0.5f handler:nil]; 162 | XCTAssertEqualObjects(self.lastNotification.name, WFTestNotificationName); 163 | XCTAssertEqualObjects(self.lastNotification.object, WFTestObject); 164 | } 165 | 166 | - (void)testAddingBlockObserver { 167 | __weak __typeof__(self) weakSelf = self; 168 | XCTestExpectation *expectation = [self expectationWithDescription:nil]; 169 | [self.center addObserverForName:WFTestNotificationName object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { 170 | [weakSelf.notifications addObject:note]; 171 | [expectation fulfill]; 172 | }]; 173 | [self.center postNotificationName:WFTestNotificationName object:nil]; 174 | [self waitForExpectationsWithTimeout:0.5f handler:nil]; 175 | XCTAssertEqualObjects(self.lastNotification.name, WFTestNotificationName); 176 | } 177 | 178 | - (void)testRemovingObserver { 179 | [self.center addObserver:self selector:@selector(receive:) name:WFTestNotificationName object:nil]; 180 | [self.center addObserver:self selector:@selector(receive:) name:WFSecondTestNotificationName object:nil]; 181 | [self.center removeObserver:self]; 182 | 183 | [self.center postNotificationName:WFTestNotificationName object:nil]; 184 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]]; 185 | XCTAssertNil(self.lastNotification); 186 | 187 | [self.center postNotificationName:WFSecondTestNotificationName object:nil]; 188 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]]; 189 | XCTAssertNil(self.lastNotification); 190 | } 191 | 192 | - (void)testRemovingNamedObserver { 193 | [self.center addObserver:self selector:@selector(receive:) name:WFTestNotificationName object:nil]; 194 | [self.center addObserver:self selector:@selector(receive:) name:WFSecondTestNotificationName object:nil]; 195 | [self.center removeObserver:self name:WFTestNotificationName object:nil]; 196 | 197 | [self.center postNotificationName:WFTestNotificationName object:nil]; 198 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]]; 199 | XCTAssertNil(self.lastNotification); 200 | 201 | [self keyValueObservingExpectationForObject:self keyPath:NSStringFromSelector(@selector(lastNotification)) expectedValue:nil]; 202 | [self.center postNotificationName:WFSecondTestNotificationName object:nil]; 203 | [self waitForExpectationsWithTimeout:0.5f handler:nil]; 204 | XCTAssertEqualObjects(self.lastNotification.name, WFSecondTestNotificationName); 205 | } 206 | 207 | - (void)testRemovingObserverWithObject { 208 | [self.center addObserver:self selector:@selector(receive:) name:WFTestNotificationName object:WFTestObject]; 209 | [self.center addObserver:self selector:@selector(receive:) name:WFTestNotificationName object:WFSecondTestObject]; 210 | [self.center removeObserver:self name:WFTestNotificationName object:WFTestObject]; 211 | 212 | [self.center postNotificationName:WFTestNotificationName object:WFTestObject]; 213 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]]; 214 | XCTAssertNil(self.lastNotification); 215 | 216 | [self keyValueObservingExpectationForObject:self keyPath:NSStringFromSelector(@selector(lastNotification)) expectedValue:nil]; 217 | [self.center postNotificationName:WFTestNotificationName object:WFSecondTestObject]; 218 | [self waitForExpectationsWithTimeout:0.5f handler:nil]; 219 | XCTAssertEqualObjects(self.lastNotification.object, WFSecondTestObject); 220 | } 221 | 222 | - (void)testRemovingNamedObserverWithObject { 223 | [self.center addObserver:self selector:@selector(receive:) name:WFTestNotificationName object:WFTestObject]; 224 | [self.center addObserver:self selector:@selector(receive:) name:WFSecondTestNotificationName object:WFSecondTestObject]; 225 | [self.center removeObserver:self name:WFTestNotificationName object:WFSecondTestObject]; 226 | [self.center removeObserver:self name:WFSecondTestNotificationName object:WFTestObject]; 227 | 228 | [self keyValueObservingExpectationForObject:self keyPath:NSStringFromSelector(@selector(lastNotification)) expectedValue:nil]; 229 | [self.center postNotificationName:WFTestNotificationName object:WFTestObject]; 230 | [self waitForExpectationsWithTimeout:0.5f handler:nil]; 231 | XCTAssertEqualObjects(self.lastNotification.name, WFTestNotificationName); 232 | XCTAssertEqualObjects(self.lastNotification.object, WFTestObject); 233 | 234 | [self keyValueObservingExpectationForObject:self keyPath:NSStringFromSelector(@selector(lastNotification)) expectedValue:nil]; 235 | [self.center postNotificationName:WFSecondTestNotificationName object:WFSecondTestObject]; 236 | [self waitForExpectationsWithTimeout:0.5f handler:nil]; 237 | XCTAssertEqualObjects(self.lastNotification.name, WFSecondTestNotificationName); 238 | XCTAssertEqualObjects(self.lastNotification.object, WFSecondTestObject); 239 | } 240 | 241 | - (void)testRemovingBlockObserver { 242 | __weak __typeof__(self) weakSelf = self; 243 | __weak id observer = [self.center addObserverForName:WFTestNotificationName object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { 244 | [weakSelf.notifications addObject:note]; 245 | }]; 246 | XCTAssertNotNil(observer); 247 | 248 | [self.center removeObserver:observer]; 249 | [self.center postNotificationName:WFTestNotificationName object:nil]; 250 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]]; 251 | XCTAssertNil(self.lastNotification); 252 | } 253 | 254 | #pragma mark - Multiple Centers 255 | 256 | - (void)testMultipleCentersInSameProcess { 257 | WFDistributedNotificationCenter *otherCenter = [[WFDistributedNotificationCenter alloc] initWithSecurityApplicationGroupIdentifier:WFNotificationCenterTestAppGroup]; 258 | WFDistributedNotificationCenter *yetAnotherCenter = [[WFDistributedNotificationCenter alloc] initWithSecurityApplicationGroupIdentifier:WFNotificationCenterTestAppGroup]; 259 | [self testCommunicationBetweenCenters:@[self.center, otherCenter, yetAnotherCenter]]; 260 | } 261 | 262 | - (void)testMultipleCentersInDifferentProcesses { 263 | set_pid(getpid() / 2); 264 | WFDistributedNotificationCenter *otherCenter = [[WFDistributedNotificationCenter alloc] initWithSecurityApplicationGroupIdentifier:WFNotificationCenterTestAppGroup]; 265 | set_pid(getpid() / 2); 266 | WFDistributedNotificationCenter *yetAnotherCenter = [[WFDistributedNotificationCenter alloc] initWithSecurityApplicationGroupIdentifier:WFNotificationCenterTestAppGroup]; 267 | reset_pid(); 268 | [self testCommunicationBetweenCenters:@[self.center, otherCenter, yetAnotherCenter]]; 269 | } 270 | 271 | - (void)testCommunicationBetweenCenters:(NSArray *)centers { 272 | for (WFDistributedNotificationCenter *center in centers) { 273 | [center addObserver:self selector:@selector(receive:) name:WFTestNotificationName object:nil]; 274 | } 275 | 276 | for (WFDistributedNotificationCenter *center in centers) { 277 | [self.notifications removeAllObjects]; 278 | [center postNotificationName:WFTestNotificationName object:nil]; 279 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0f]]; 280 | 281 | XCTAssertTrue(self.notifications.count == centers.count); 282 | XCTAssertEqualObjects(self.lastNotification.name, WFTestNotificationName); 283 | 284 | for (NSNotification *notification in self.notifications) { 285 | for (NSNotification *otherNotification in self.notifications) { 286 | WFAssertEqualNotifications(notification, otherNotification); 287 | } 288 | } 289 | } 290 | } 291 | 292 | @end 293 | -------------------------------------------------------------------------------- /WFNotificationCenter.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D05BCDD71AAD77EC0079C258 /* libWFNotificationCenter.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D05BCDCB1AAD77EC0079C258 /* libWFNotificationCenter.a */; }; 11 | D05BCDE71AAD78200079C258 /* WFDistributedNotificationCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = D05BCDE61AAD78200079C258 /* WFDistributedNotificationCenter.m */; }; 12 | D05BCDEF1AAD7A300079C258 /* WFNotificationCenterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D05BCDEE1AAD7A300079C258 /* WFNotificationCenterTests.m */; }; 13 | D08CD3511ACE7098005D0626 /* fishhook.c in Sources */ = {isa = PBXBuildFile; fileRef = D08CD34F1ACE7098005D0626 /* fishhook.c */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXContainerItemProxy section */ 17 | D05BCDD81AAD77EC0079C258 /* PBXContainerItemProxy */ = { 18 | isa = PBXContainerItemProxy; 19 | containerPortal = D05BCDC31AAD77EC0079C258 /* Project object */; 20 | proxyType = 1; 21 | remoteGlobalIDString = D05BCDCA1AAD77EC0079C258; 22 | remoteInfo = WFNotificationCenter; 23 | }; 24 | /* End PBXContainerItemProxy section */ 25 | 26 | /* Begin PBXCopyFilesBuildPhase section */ 27 | D05BCDC91AAD77EC0079C258 /* CopyFiles */ = { 28 | isa = PBXCopyFilesBuildPhase; 29 | buildActionMask = 2147483647; 30 | dstPath = "include/$(PRODUCT_NAME)"; 31 | dstSubfolderSpec = 16; 32 | files = ( 33 | ); 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXCopyFilesBuildPhase section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | D05BCDCB1AAD77EC0079C258 /* libWFNotificationCenter.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libWFNotificationCenter.a; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | D05BCDD61AAD77EC0079C258 /* WFNotificationCenterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WFNotificationCenterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | D05BCDDC1AAD77EC0079C258 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | D05BCDE51AAD78200079C258 /* WFDistributedNotificationCenter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WFDistributedNotificationCenter.h; sourceTree = ""; }; 43 | D05BCDE61AAD78200079C258 /* WFDistributedNotificationCenter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WFDistributedNotificationCenter.m; sourceTree = ""; }; 44 | D05BCDEE1AAD7A300079C258 /* WFNotificationCenterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WFNotificationCenterTests.m; sourceTree = ""; }; 45 | D08CD34F1ACE7098005D0626 /* fishhook.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fishhook.c; sourceTree = ""; }; 46 | D08CD3501ACE7098005D0626 /* fishhook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fishhook.h; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | D05BCDC81AAD77EC0079C258 /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | D05BCDD31AAD77EC0079C258 /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | D05BCDD71AAD77EC0079C258 /* libWFNotificationCenter.a in Frameworks */, 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | D05BCDC21AAD77EC0079C258 = { 69 | isa = PBXGroup; 70 | children = ( 71 | D05BCDCD1AAD77EC0079C258 /* WFNotificationCenter */, 72 | D05BCDDA1AAD77EC0079C258 /* WFNotificationCenterTests */, 73 | D05BCDCC1AAD77EC0079C258 /* Products */, 74 | ); 75 | sourceTree = ""; 76 | }; 77 | D05BCDCC1AAD77EC0079C258 /* Products */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | D05BCDCB1AAD77EC0079C258 /* libWFNotificationCenter.a */, 81 | D05BCDD61AAD77EC0079C258 /* WFNotificationCenterTests.xctest */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | D05BCDCD1AAD77EC0079C258 /* WFNotificationCenter */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | D05BCDE51AAD78200079C258 /* WFDistributedNotificationCenter.h */, 90 | D05BCDE61AAD78200079C258 /* WFDistributedNotificationCenter.m */, 91 | ); 92 | path = WFNotificationCenter; 93 | sourceTree = ""; 94 | }; 95 | D05BCDDA1AAD77EC0079C258 /* WFNotificationCenterTests */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | D05BCDEE1AAD7A300079C258 /* WFNotificationCenterTests.m */, 99 | D05BCDEB1AAD78C40079C258 /* fishhook */, 100 | D05BCDDB1AAD77EC0079C258 /* Supporting Files */, 101 | ); 102 | path = WFNotificationCenterTests; 103 | sourceTree = ""; 104 | }; 105 | D05BCDDB1AAD77EC0079C258 /* Supporting Files */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | D05BCDDC1AAD77EC0079C258 /* Info.plist */, 109 | ); 110 | name = "Supporting Files"; 111 | sourceTree = ""; 112 | }; 113 | D05BCDEB1AAD78C40079C258 /* fishhook */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | D08CD34F1ACE7098005D0626 /* fishhook.c */, 117 | D08CD3501ACE7098005D0626 /* fishhook.h */, 118 | ); 119 | path = fishhook; 120 | sourceTree = ""; 121 | }; 122 | /* End PBXGroup section */ 123 | 124 | /* Begin PBXNativeTarget section */ 125 | D05BCDCA1AAD77EC0079C258 /* WFNotificationCenter */ = { 126 | isa = PBXNativeTarget; 127 | buildConfigurationList = D05BCDDF1AAD77EC0079C258 /* Build configuration list for PBXNativeTarget "WFNotificationCenter" */; 128 | buildPhases = ( 129 | D05BCDC71AAD77EC0079C258 /* Sources */, 130 | D05BCDC81AAD77EC0079C258 /* Frameworks */, 131 | D05BCDC91AAD77EC0079C258 /* CopyFiles */, 132 | ); 133 | buildRules = ( 134 | ); 135 | dependencies = ( 136 | ); 137 | name = WFNotificationCenter; 138 | productName = WFNotificationCenter; 139 | productReference = D05BCDCB1AAD77EC0079C258 /* libWFNotificationCenter.a */; 140 | productType = "com.apple.product-type.library.static"; 141 | }; 142 | D05BCDD51AAD77EC0079C258 /* WFNotificationCenterTests */ = { 143 | isa = PBXNativeTarget; 144 | buildConfigurationList = D05BCDE21AAD77EC0079C258 /* Build configuration list for PBXNativeTarget "WFNotificationCenterTests" */; 145 | buildPhases = ( 146 | D05BCDD21AAD77EC0079C258 /* Sources */, 147 | D05BCDD31AAD77EC0079C258 /* Frameworks */, 148 | D05BCDD41AAD77EC0079C258 /* Resources */, 149 | ); 150 | buildRules = ( 151 | ); 152 | dependencies = ( 153 | D05BCDD91AAD77EC0079C258 /* PBXTargetDependency */, 154 | ); 155 | name = WFNotificationCenterTests; 156 | productName = WFNotificationCenterTests; 157 | productReference = D05BCDD61AAD77EC0079C258 /* WFNotificationCenterTests.xctest */; 158 | productType = "com.apple.product-type.bundle.unit-test"; 159 | }; 160 | /* End PBXNativeTarget section */ 161 | 162 | /* Begin PBXProject section */ 163 | D05BCDC31AAD77EC0079C258 /* Project object */ = { 164 | isa = PBXProject; 165 | attributes = { 166 | LastUpgradeCheck = 0630; 167 | ORGANIZATIONNAME = "DeskConnect, Inc."; 168 | TargetAttributes = { 169 | D05BCDCA1AAD77EC0079C258 = { 170 | CreatedOnToolsVersion = 6.3; 171 | }; 172 | D05BCDD51AAD77EC0079C258 = { 173 | CreatedOnToolsVersion = 6.3; 174 | }; 175 | }; 176 | }; 177 | buildConfigurationList = D05BCDC61AAD77EC0079C258 /* Build configuration list for PBXProject "WFNotificationCenter" */; 178 | compatibilityVersion = "Xcode 3.2"; 179 | developmentRegion = English; 180 | hasScannedForEncodings = 0; 181 | knownRegions = ( 182 | en, 183 | ); 184 | mainGroup = D05BCDC21AAD77EC0079C258; 185 | productRefGroup = D05BCDCC1AAD77EC0079C258 /* Products */; 186 | projectDirPath = ""; 187 | projectRoot = ""; 188 | targets = ( 189 | D05BCDCA1AAD77EC0079C258 /* WFNotificationCenter */, 190 | D05BCDD51AAD77EC0079C258 /* WFNotificationCenterTests */, 191 | ); 192 | }; 193 | /* End PBXProject section */ 194 | 195 | /* Begin PBXResourcesBuildPhase section */ 196 | D05BCDD41AAD77EC0079C258 /* Resources */ = { 197 | isa = PBXResourcesBuildPhase; 198 | buildActionMask = 2147483647; 199 | files = ( 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | /* End PBXResourcesBuildPhase section */ 204 | 205 | /* Begin PBXSourcesBuildPhase section */ 206 | D05BCDC71AAD77EC0079C258 /* Sources */ = { 207 | isa = PBXSourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | D05BCDE71AAD78200079C258 /* WFDistributedNotificationCenter.m in Sources */, 211 | ); 212 | runOnlyForDeploymentPostprocessing = 0; 213 | }; 214 | D05BCDD21AAD77EC0079C258 /* Sources */ = { 215 | isa = PBXSourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | D05BCDEF1AAD7A300079C258 /* WFNotificationCenterTests.m in Sources */, 219 | D08CD3511ACE7098005D0626 /* fishhook.c in Sources */, 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | }; 223 | /* End PBXSourcesBuildPhase section */ 224 | 225 | /* Begin PBXTargetDependency section */ 226 | D05BCDD91AAD77EC0079C258 /* PBXTargetDependency */ = { 227 | isa = PBXTargetDependency; 228 | target = D05BCDCA1AAD77EC0079C258 /* WFNotificationCenter */; 229 | targetProxy = D05BCDD81AAD77EC0079C258 /* PBXContainerItemProxy */; 230 | }; 231 | /* End PBXTargetDependency section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | D05BCDDD1AAD77EC0079C258 /* Debug */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ENABLE_MODULES = YES; 239 | CLANG_ENABLE_OBJC_ARC = YES; 240 | CLANG_WARN_BOOL_CONVERSION = YES; 241 | CLANG_WARN_CONSTANT_CONVERSION = YES; 242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 243 | CLANG_WARN_EMPTY_BODY = YES; 244 | CLANG_WARN_ENUM_CONVERSION = YES; 245 | CLANG_WARN_INT_CONVERSION = YES; 246 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 247 | CLANG_WARN_UNREACHABLE_CODE = YES; 248 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 249 | COPY_PHASE_STRIP = NO; 250 | DEBUG_INFORMATION_FORMAT = dwarf; 251 | ENABLE_STRICT_OBJC_MSGSEND = YES; 252 | GCC_DYNAMIC_NO_PIC = NO; 253 | GCC_OPTIMIZATION_LEVEL = 0; 254 | GCC_PREPROCESSOR_DEFINITIONS = ( 255 | "DEBUG=1", 256 | "$(inherited)", 257 | ); 258 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 259 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 260 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 261 | GCC_WARN_UNDECLARED_SELECTOR = YES; 262 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 263 | GCC_WARN_UNUSED_FUNCTION = YES; 264 | GCC_WARN_UNUSED_VARIABLE = YES; 265 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 266 | ONLY_ACTIVE_ARCH = YES; 267 | SDKROOT = iphoneos; 268 | }; 269 | name = Debug; 270 | }; 271 | D05BCDDE1AAD77EC0079C258 /* Release */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ALWAYS_SEARCH_USER_PATHS = NO; 275 | CLANG_ENABLE_MODULES = YES; 276 | CLANG_ENABLE_OBJC_ARC = YES; 277 | CLANG_WARN_BOOL_CONVERSION = YES; 278 | CLANG_WARN_CONSTANT_CONVERSION = YES; 279 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 280 | CLANG_WARN_EMPTY_BODY = YES; 281 | CLANG_WARN_ENUM_CONVERSION = YES; 282 | CLANG_WARN_INT_CONVERSION = YES; 283 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 284 | CLANG_WARN_UNREACHABLE_CODE = YES; 285 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 286 | COPY_PHASE_STRIP = NO; 287 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 288 | ENABLE_STRICT_OBJC_MSGSEND = YES; 289 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 290 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 291 | GCC_WARN_UNDECLARED_SELECTOR = YES; 292 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 293 | GCC_WARN_UNUSED_FUNCTION = YES; 294 | GCC_WARN_UNUSED_VARIABLE = YES; 295 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 296 | SDKROOT = iphoneos; 297 | VALIDATE_PRODUCT = YES; 298 | }; 299 | name = Release; 300 | }; 301 | D05BCDE01AAD77EC0079C258 /* Debug */ = { 302 | isa = XCBuildConfiguration; 303 | buildSettings = { 304 | OTHER_LDFLAGS = "-ObjC"; 305 | PRODUCT_NAME = "$(TARGET_NAME)"; 306 | SKIP_INSTALL = YES; 307 | }; 308 | name = Debug; 309 | }; 310 | D05BCDE11AAD77EC0079C258 /* Release */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | OTHER_LDFLAGS = "-ObjC"; 314 | PRODUCT_NAME = "$(TARGET_NAME)"; 315 | SKIP_INSTALL = YES; 316 | }; 317 | name = Release; 318 | }; 319 | D05BCDE31AAD77EC0079C258 /* Debug */ = { 320 | isa = XCBuildConfiguration; 321 | buildSettings = { 322 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 323 | FRAMEWORK_SEARCH_PATHS = ( 324 | "$(SDKROOT)/Developer/Library/Frameworks", 325 | "$(inherited)", 326 | ); 327 | GCC_PREPROCESSOR_DEFINITIONS = ( 328 | "DEBUG=1", 329 | "$(inherited)", 330 | ); 331 | INFOPLIST_FILE = WFNotificationCenterTests/Info.plist; 332 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 333 | PRODUCT_NAME = "$(TARGET_NAME)"; 334 | }; 335 | name = Debug; 336 | }; 337 | D05BCDE41AAD77EC0079C258 /* Release */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 341 | FRAMEWORK_SEARCH_PATHS = ( 342 | "$(SDKROOT)/Developer/Library/Frameworks", 343 | "$(inherited)", 344 | ); 345 | INFOPLIST_FILE = WFNotificationCenterTests/Info.plist; 346 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 347 | PRODUCT_NAME = "$(TARGET_NAME)"; 348 | }; 349 | name = Release; 350 | }; 351 | /* End XCBuildConfiguration section */ 352 | 353 | /* Begin XCConfigurationList section */ 354 | D05BCDC61AAD77EC0079C258 /* Build configuration list for PBXProject "WFNotificationCenter" */ = { 355 | isa = XCConfigurationList; 356 | buildConfigurations = ( 357 | D05BCDDD1AAD77EC0079C258 /* Debug */, 358 | D05BCDDE1AAD77EC0079C258 /* Release */, 359 | ); 360 | defaultConfigurationIsVisible = 0; 361 | defaultConfigurationName = Release; 362 | }; 363 | D05BCDDF1AAD77EC0079C258 /* Build configuration list for PBXNativeTarget "WFNotificationCenter" */ = { 364 | isa = XCConfigurationList; 365 | buildConfigurations = ( 366 | D05BCDE01AAD77EC0079C258 /* Debug */, 367 | D05BCDE11AAD77EC0079C258 /* Release */, 368 | ); 369 | defaultConfigurationIsVisible = 0; 370 | defaultConfigurationName = Release; 371 | }; 372 | D05BCDE21AAD77EC0079C258 /* Build configuration list for PBXNativeTarget "WFNotificationCenterTests" */ = { 373 | isa = XCConfigurationList; 374 | buildConfigurations = ( 375 | D05BCDE31AAD77EC0079C258 /* Debug */, 376 | D05BCDE41AAD77EC0079C258 /* Release */, 377 | ); 378 | defaultConfigurationIsVisible = 0; 379 | defaultConfigurationName = Release; 380 | }; 381 | /* End XCConfigurationList section */ 382 | }; 383 | rootObject = D05BCDC31AAD77EC0079C258 /* Project object */; 384 | } 385 | -------------------------------------------------------------------------------- /WFNotificationCenter/WFDistributedNotificationCenter.m: -------------------------------------------------------------------------------- 1 | // 2 | // WFDistributedNotificationCenter.m 3 | // WFNotificationCenter 4 | // 5 | // Created by Conrad Kramer on 3/5/15. 6 | // Copyright (c) 2015 DeskConnect, Inc. All rights reserved. 7 | // 8 | 9 | #import "WFDistributedNotificationCenter.h" 10 | 11 | #pragma mark - Serialization 12 | 13 | static NSString * const WFNotificationArchiveNameKey = @"WFNotificationName"; 14 | static NSString * const WFNotificationArchiveObjectKey = @"WFNotificationObject"; 15 | static NSString * const WFNotificationArchiveUserInfoKey = @"WFNotificationUserInfo"; 16 | 17 | static NSData *WFArchivedDataFromNotification(NSNotification *notification) { 18 | NSCAssert(notification.object == nil || [notification.object isKindOfClass:[NSString class]], @"Notification object must be of class NSString"); 19 | NSMutableData *data = [NSMutableData new]; 20 | NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; 21 | [archiver setOutputFormat:NSPropertyListBinaryFormat_v1_0]; 22 | [archiver setRequiresSecureCoding:YES]; 23 | [archiver encodeObject:notification.name forKey:WFNotificationArchiveNameKey]; 24 | [archiver encodeObject:notification.object forKey:WFNotificationArchiveObjectKey]; 25 | [archiver encodeObject:notification.userInfo forKey:WFNotificationArchiveUserInfoKey]; 26 | [archiver finishEncoding]; 27 | return [data copy]; 28 | } 29 | 30 | static NSNotification *WFNotificationFromArchivedData(NSData *data, NSSet *allowedClasses) { 31 | NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; 32 | [unarchiver setRequiresSecureCoding:YES]; 33 | NSString *name = [unarchiver decodeObjectOfClass:[NSString class] forKey:WFNotificationArchiveNameKey]; 34 | NSString *object = [unarchiver decodeObjectOfClass:[NSString class] forKey:WFNotificationArchiveObjectKey]; 35 | NSDictionary *userInfo = nil; 36 | if (allowedClasses) { 37 | @try { 38 | userInfo = [unarchiver decodeObjectOfClasses:[allowedClasses setByAddingObject:[NSDictionary class]] forKey:WFNotificationArchiveUserInfoKey]; 39 | } 40 | @catch (NSException *exception) { 41 | if ([exception.name isEqualToString:NSInvalidUnarchiveOperationException]) { 42 | NSLog(@"%@: Error: invalid set of allowed classes for \"%@\", dropping userInfo dictionary", NSStringFromClass([WFDistributedNotificationCenter class]), name); 43 | } else { 44 | @throw; 45 | } 46 | } 47 | } 48 | return [NSNotification notificationWithName:name object:object userInfo:userInfo]; 49 | } 50 | 51 | #pragma mark - Server Callback 52 | 53 | @interface WFDistributedNotificationCenter () 54 | - (void)receivedData:(NSData *)data withMessageId:(SInt32)messageId fromPort:(CFMessagePortRef)port; 55 | @end 56 | 57 | CFDataRef WFNotificationServerCallback(CFMessagePortRef local, SInt32 msgid, CFDataRef dataRef, void *info) { 58 | NSData *data = (dataRef ? [NSData dataWithBytes:CFDataGetBytePtr(dataRef) length:CFDataGetLength(dataRef)] : nil); 59 | for (WFDistributedNotificationCenter *center in (__bridge NSHashTable *)info) 60 | [center receivedData:data withMessageId:msgid fromPort:local]; 61 | return NULL; 62 | } 63 | 64 | #pragma mark - Block Observer 65 | 66 | @interface WFDNCBlockObserver : NSObject 67 | 68 | @end 69 | 70 | @implementation WFDNCBlockObserver { 71 | NSOperationQueue *_queue; 72 | void (^_block)(NSNotification *); 73 | } 74 | 75 | - (instancetype)initWithBlock:(void (^)(NSNotification *))block queue:(NSOperationQueue *)queue { 76 | NSParameterAssert(block); 77 | self = [super init]; 78 | if (!self) { 79 | return nil; 80 | } 81 | 82 | _block = [block copy]; 83 | _queue = queue; 84 | 85 | return self; 86 | } 87 | 88 | - (void)send:(NSNotification *)notification { 89 | if (_queue) { 90 | [_queue addOperationWithBlock:^{ 91 | _block(notification); 92 | }]; 93 | } else { 94 | _block(notification); 95 | } 96 | } 97 | 98 | @end 99 | 100 | static SInt32 const WFDistributedNotificationPostMessageId = 1; 101 | static NSString * const WFDistributedNotificationCatchAllKey = @"*"; 102 | 103 | @implementation WFDistributedNotificationCenter { 104 | NSURL *_registryURL; 105 | NSFileHandle *_registryHandle; 106 | NSString *_serverName; 107 | 108 | NSMutableDictionary *_observers; 109 | NSMutableSet *_blockObservers; 110 | 111 | CFMessagePortRef _server; 112 | } 113 | 114 | #pragma mark - Threading 115 | 116 | + (dispatch_queue_t)receiveNotificationQueue { 117 | static dispatch_queue_t receiveNotificationQueue = nil; 118 | static dispatch_once_t onceToken; 119 | dispatch_once(&onceToken, ^{ 120 | receiveNotificationQueue = dispatch_queue_create("com.deskconnect.WFDistributedNotificationCenter.receive", DISPATCH_QUEUE_SERIAL); 121 | }); 122 | return receiveNotificationQueue; 123 | } 124 | 125 | + (void)postNotificationThreadEntryPoint:(id)__unused object { 126 | @autoreleasepool { 127 | [[NSThread currentThread] setName:NSStringFromClass(self)]; 128 | 129 | NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 130 | [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; 131 | [runLoop run]; 132 | } 133 | } 134 | 135 | + (NSThread *)postNotificationThread { 136 | static NSThread *postNotificationThread = nil; 137 | static dispatch_once_t oncePredicate; 138 | dispatch_once(&oncePredicate, ^{ 139 | postNotificationThread = [[NSThread alloc] initWithTarget:self selector:@selector(postNotificationThreadEntryPoint:) object:nil]; 140 | [postNotificationThread start]; 141 | }); 142 | 143 | return postNotificationThread; 144 | } 145 | 146 | #pragma mark - Lifecycle 147 | 148 | + (NSHashTable *)activeCentersForServerName:(NSString *)serverName { 149 | static NSMutableDictionary *activeCentersByServerName = nil; 150 | static dispatch_once_t onceToken; 151 | dispatch_once(&onceToken, ^{ 152 | activeCentersByServerName = [NSMutableDictionary new]; 153 | }); 154 | NSHashTable *activeCenters = ([activeCentersByServerName objectForKey:serverName] ?: [NSHashTable weakObjectsHashTable]); 155 | [activeCentersByServerName setObject:activeCenters forKey:serverName]; 156 | return activeCenters; 157 | } 158 | 159 | - (instancetype)init { 160 | return [self initWithSecurityApplicationGroupIdentifier:nil]; 161 | } 162 | 163 | - (instancetype)initWithSecurityApplicationGroupIdentifier:(NSString *)groupIdentifier { 164 | NSParameterAssert(groupIdentifier.length); 165 | self = [super init]; 166 | if (!self) { 167 | return nil; 168 | } 169 | 170 | _registryURL = [[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupIdentifier] URLByAppendingPathComponent:@".WFDNCRegistry"]; 171 | if (!_registryURL) { 172 | NSLog(@"%@: Warning: Could not get container URL for group identifier \"%@\", falling back to temporary directory.", self, groupIdentifier); 173 | _registryURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:@".WFDNCRegistry"]; 174 | } 175 | _serverName = [groupIdentifier stringByAppendingFormat:@".wfdnc.%i", getpid()]; 176 | _observers = [NSMutableDictionary new]; 177 | _blockObservers = [NSMutableSet new]; 178 | 179 | int registryFd; 180 | if ((registryFd = open([_registryURL.path UTF8String], O_RDWR | O_CREAT, 0644)) == -1) { 181 | NSLog(@"%@: Error: Could not open registry file at path \"%@\": %@", self, _registryURL.path, [[NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil] localizedFailureReason]); 182 | return nil; 183 | } 184 | 185 | _registryHandle = [[NSFileHandle alloc] initWithFileDescriptor:registryFd closeOnDealloc:YES]; 186 | 187 | return self; 188 | } 189 | 190 | - (void)dealloc { 191 | if (_server) { 192 | if (![[WFDistributedNotificationCenter activeCentersForServerName:_serverName] anyObject]) 193 | CFMessagePortInvalidate(_server); 194 | CFRelease(_server); 195 | } 196 | } 197 | 198 | #pragma mark - Port Registry 199 | 200 | - (NSDictionary *)portRegistry { 201 | if (flock(_registryHandle.fileDescriptor, LOCK_SH) == -1) { 202 | NSLog(@"%@: Error: Failed to acquire registry lock: %@", self, [[NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil] localizedFailureReason]); 203 | return nil; 204 | } 205 | 206 | [_registryHandle seekToFileOffset:0]; 207 | NSDictionary *portRegistry = [NSPropertyListSerialization propertyListWithData:[_registryHandle readDataToEndOfFile] options:0 format:NULL error:nil]; 208 | flock(_registryHandle.fileDescriptor, LOCK_UN); 209 | return portRegistry; 210 | } 211 | 212 | - (void)removePortsFromRegistry:(NSSet *)portNames forNotificationName:(NSString *)aName object:(NSString *)anObject { 213 | if (flock(_registryHandle.fileDescriptor, LOCK_EX) == -1) { 214 | NSLog(@"%@: Error: Failed to acquire registry lock: %@", self, [[NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil] localizedFailureReason]); 215 | return; 216 | } 217 | 218 | [_registryHandle seekToFileOffset:0]; 219 | NSMutableDictionary *portRegistry = [NSPropertyListSerialization propertyListWithData:[_registryHandle readDataToEndOfFile] options:NSPropertyListMutableContainers format:NULL error:nil]; 220 | for (NSString *observerName in [portRegistry allKeys]) { 221 | if (!aName || [observerName isEqualToString:aName]) { 222 | NSMutableDictionary *portsByObject = [portRegistry objectForKey:observerName]; 223 | for (NSString *observerObject in [portsByObject allKeys]) { 224 | NSMutableSet *ports = [portsByObject objectForKey:observerObject]; 225 | if (!anObject || [observerObject isEqualToString:anObject]) { 226 | for (NSString *portName in portNames) 227 | [ports removeObject:portName]; 228 | } 229 | if (!ports.count) 230 | [portsByObject removeObjectForKey:observerObject]; 231 | } 232 | if (!portsByObject.count) 233 | [portRegistry removeObjectForKey:observerName]; 234 | } 235 | } 236 | 237 | NSData *data = [NSPropertyListSerialization dataWithPropertyList:portRegistry format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil]; 238 | [_registryHandle truncateFileAtOffset:data.length]; 239 | [_registryHandle seekToFileOffset:0]; 240 | [_registryHandle writeData:data]; 241 | flock(_registryHandle.fileDescriptor, LOCK_UN); 242 | } 243 | 244 | - (void)addPortsToRegistry:(NSSet *)portNames forNotificationName:(NSString *)aName object:(NSString *)anObject { 245 | if (flock(_registryHandle.fileDescriptor, LOCK_EX) == -1) { 246 | NSLog(@"%@: Error: Failed to acquire registry lock: %@", self, [[NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil] localizedFailureReason]); 247 | return; 248 | } 249 | 250 | [_registryHandle seekToFileOffset:0]; 251 | NSMutableDictionary *portRegistry = ([NSPropertyListSerialization propertyListWithData:[_registryHandle readDataToEndOfFile] options:NSPropertyListMutableContainers format:NULL error:nil] ?: [NSMutableDictionary new]); 252 | NSMutableDictionary *portsByObject = ([portRegistry objectForKey:(aName ?: WFDistributedNotificationCatchAllKey)] ?: [NSMutableDictionary new]); 253 | NSMutableSet *ports = ([NSMutableSet setWithArray:[portsByObject objectForKey:(anObject ?: WFDistributedNotificationCatchAllKey)]] ?: [NSMutableSet new]); 254 | [ports addObject:_serverName]; 255 | [portsByObject setObject:[ports allObjects] forKey:(anObject ?: WFDistributedNotificationCatchAllKey)]; 256 | [portRegistry setObject:portsByObject forKey:(aName ?: WFDistributedNotificationCatchAllKey)]; 257 | 258 | NSData *data = [NSPropertyListSerialization dataWithPropertyList:portRegistry format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil]; 259 | [_registryHandle truncateFileAtOffset:data.length]; 260 | [_registryHandle seekToFileOffset:0]; 261 | [_registryHandle writeData:data]; 262 | flock(_registryHandle.fileDescriptor, LOCK_UN); 263 | } 264 | 265 | #pragma mark - Registration 266 | 267 | - (void)removeObserver:(id)observer { 268 | [self removeObserver:observer name:nil object:nil]; 269 | } 270 | 271 | - (void)removeObserver:(id)observer name:(NSString *)aName object:(NSString *)anObject { 272 | if (!observer) 273 | return; 274 | 275 | if ([_blockObservers containsObject:observer]) { 276 | aName = nil; 277 | anObject = nil; 278 | [_blockObservers removeObject:observer]; 279 | } 280 | 281 | for (NSString *notificationName in [_observers allKeys]) { 282 | NSMutableDictionary *targetsByObject = [_observers objectForKey:notificationName]; 283 | if (!aName || [notificationName isEqualToString:aName]) { 284 | for (NSString *notificationObject in [targetsByObject allKeys]) { 285 | NSMapTable *selectorsByTarget = [targetsByObject objectForKey:notificationObject]; 286 | if (!anObject || [anObject isEqualToString:notificationObject]) { 287 | [selectorsByTarget removeObjectForKey:observer]; 288 | } 289 | if (!selectorsByTarget.count) { 290 | [targetsByObject removeObjectForKey:notificationObject]; 291 | } 292 | } 293 | } 294 | if (!targetsByObject.count) { 295 | [_observers removeObjectForKey:notificationName]; 296 | } 297 | } 298 | 299 | if (!_observers.count && _server) { 300 | NSHashTable *activeCenters = [WFDistributedNotificationCenter activeCentersForServerName:_serverName]; 301 | if ([activeCenters containsObject:self] && activeCenters.count == 1) { 302 | CFMessagePortInvalidate(_server); 303 | [self removePortsFromRegistry:[NSSet setWithObject:_serverName] forNotificationName:nil object:nil]; 304 | } 305 | CFRelease(_server); 306 | _server = NULL; 307 | [activeCenters removeObject:self]; 308 | } 309 | } 310 | 311 | - (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(NSString *)anObject { 312 | [self addObserver:observer selector:aSelector name:aName object:anObject allowedClasses:nil]; 313 | } 314 | 315 | - (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(NSString *)anObject allowedClasses:(NSSet *)allowedClasses { 316 | NSParameterAssert(observer); 317 | NSParameterAssert(aSelector); 318 | 319 | if (!allowedClasses.count) { 320 | allowedClasses = [NSSet setWithObjects:[NSArray class], [NSDictionary class], [NSString class], [NSData class], [NSDate class], [NSNumber class], nil]; 321 | } 322 | 323 | NSMutableDictionary *targetsByObject = ([_observers objectForKey:(aName ?: WFDistributedNotificationCatchAllKey)] ?: [NSMutableDictionary new]); 324 | NSMapTable *selectorsByTarget = ([targetsByObject objectForKey:(anObject ?: WFDistributedNotificationCatchAllKey)] ?: [NSMapTable weakToStrongObjectsMapTable]); 325 | NSMutableSet *selectors = ([selectorsByTarget objectForKey:observer] ?: [NSMutableSet new]); 326 | [selectors addObject:@[NSStringFromSelector(aSelector), allowedClasses]]; 327 | [selectorsByTarget setObject:selectors forKey:observer]; 328 | [targetsByObject setObject:selectorsByTarget forKey:(anObject ?: WFDistributedNotificationCatchAllKey)]; 329 | [_observers setObject:targetsByObject forKey:(aName ?: WFDistributedNotificationCatchAllKey)]; 330 | 331 | if (!_server) { 332 | NSHashTable *activeCenters = [WFDistributedNotificationCenter activeCentersForServerName:_serverName]; 333 | [activeCenters addObject:self]; 334 | CFMessagePortContext context = {0, (__bridge void *)activeCenters, NULL, NULL, NULL}; 335 | _server = CFMessagePortCreateLocal(NULL, (__bridge CFStringRef)_serverName, &WFNotificationServerCallback, &context, NULL); 336 | NSAssert(_server != NULL, @"%@: Error: The notification server could not be established, is the app group identifier set correctly?", self); 337 | CFMessagePortSetDispatchQueue(_server, [WFDistributedNotificationCenter receiveNotificationQueue]); 338 | } 339 | 340 | [self addPortsToRegistry:[NSSet setWithObject:_serverName] forNotificationName:aName object:anObject]; 341 | } 342 | 343 | - (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block { 344 | return [self addObserverForName:name object:obj allowedClasses:nil queue:queue usingBlock:block]; 345 | } 346 | 347 | - (id)addObserverForName:(NSString *)name object:(id)obj allowedClasses:(NSSet *)allowedClasses queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block { 348 | NSParameterAssert(block); 349 | WFDNCBlockObserver *observer = [[WFDNCBlockObserver alloc] initWithBlock:block queue:queue]; 350 | [_blockObservers addObject:observer]; 351 | [self addObserver:observer selector:@selector(send:) name:name object:obj allowedClasses:allowedClasses]; 352 | return observer; 353 | } 354 | 355 | #pragma mark - Posting 356 | 357 | - (void)postNotificationName:(NSString *)aName object:(NSString *)anObject { 358 | [self postNotification:[NSNotification notificationWithName:aName object:anObject]]; 359 | } 360 | 361 | - (void)postNotificationName:(NSString *)aName object:(NSString *)anObject userInfo:(NSDictionary *)aUserInfo { 362 | [self postNotification:[NSNotification notificationWithName:aName object:anObject userInfo:aUserInfo]]; 363 | } 364 | 365 | - (void)postNotification:(NSNotification *)notification { 366 | NSParameterAssert(notification); 367 | NSAssert(notification.object == nil || [notification.object isKindOfClass:[NSString class]], @"%@: Notification object must be of class NSString", self); 368 | NSData *data = WFArchivedDataFromNotification(notification); 369 | 370 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(distributeNotification:data:)]]; 371 | [invocation setSelector:@selector(distributeNotification:data:)]; 372 | [invocation setArgument:¬ification atIndex:2]; 373 | [invocation setArgument:&data atIndex:3]; 374 | [invocation retainArguments]; 375 | [invocation performSelector:@selector(invokeWithTarget:) onThread:[WFDistributedNotificationCenter postNotificationThread] withObject:self waitUntilDone:NO]; 376 | } 377 | 378 | - (void)distributeNotification:(NSNotification *)notification data:(NSData *)data { 379 | NSMutableSet *portNames = [NSMutableSet new]; 380 | NSDictionary *portRegistry = self.portRegistry; 381 | for (NSString *portNotificationName in portRegistry) { 382 | if ([portNotificationName isEqualToString:notification.name] || [portNotificationName isEqualToString:WFDistributedNotificationCatchAllKey]) { 383 | NSDictionary *portsByObject = [portRegistry objectForKey:portNotificationName]; 384 | for (NSString *portObject in portsByObject) { 385 | if ([portObject isEqualToString:notification.object] || [portObject isEqualToString:WFDistributedNotificationCatchAllKey]) { 386 | [portNames addObjectsFromArray:[portsByObject objectForKey:portObject]]; 387 | } 388 | } 389 | } 390 | } 391 | 392 | if (!portNames.count) 393 | return; 394 | 395 | NSMutableSet *invalidPortNames = [NSMutableSet new]; 396 | 397 | for (NSString *portName in portNames) { 398 | CFMessagePortRef port = CFMessagePortCreateRemote(NULL, (__bridge CFStringRef)portName); 399 | if (!port || !CFMessagePortIsValid(port)) { 400 | [invalidPortNames addObject:portName]; 401 | if (port) 402 | CFRelease(port); 403 | continue; 404 | } 405 | 406 | SInt32 status = CFMessagePortSendRequest(port, WFDistributedNotificationPostMessageId, (__bridge CFDataRef)data, 1000, 0, NULL, NULL); 407 | if (status != kCFMessagePortSuccess) { 408 | NSLog(@"%@: Error: could not post notification to port \"%@\" with error code %i", self, portName, (int)status); 409 | } 410 | 411 | CFMessagePortInvalidate(port); 412 | CFRelease(port); 413 | } 414 | 415 | [self removePortsFromRegistry:invalidPortNames forNotificationName:nil object:nil]; 416 | } 417 | 418 | #pragma mark - Receiving 419 | 420 | - (void)receivedData:(NSData *)data withMessageId:(SInt32)messageId fromPort:(CFMessagePortRef)port { 421 | if (messageId == WFDistributedNotificationPostMessageId) { 422 | [self receivedNotificationData:data]; 423 | } 424 | } 425 | 426 | - (void)receivedNotificationData:(NSData *)data { 427 | #pragma clang diagnostic push 428 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 429 | NSNotification *notification = WFNotificationFromArchivedData(data, nil); 430 | for (NSString *observerNotificationName in _observers) { 431 | if ([observerNotificationName isEqualToString:notification.name] || [observerNotificationName isEqualToString:WFDistributedNotificationCatchAllKey]) { 432 | NSMutableDictionary *observerTargetsByObject = [_observers objectForKey:observerNotificationName]; 433 | for (NSString *observerObject in observerTargetsByObject) { 434 | if ([notification.object isEqualToString:observerObject] || [observerObject isEqualToString:WFDistributedNotificationCatchAllKey]) { 435 | NSMapTable *selectorsByTarget = [observerTargetsByObject objectForKey:observerObject]; 436 | for (id target in selectorsByTarget) { 437 | for (NSArray *selectorPair in [selectorsByTarget objectForKey:target]) { 438 | notification = WFNotificationFromArchivedData(data, [selectorPair objectAtIndex:1]); 439 | [target performSelector:NSSelectorFromString([selectorPair objectAtIndex:0]) withObject:notification]; 440 | } 441 | } 442 | } 443 | } 444 | } 445 | } 446 | #pragma clang diagnostic pop 447 | } 448 | 449 | @end 450 | --------------------------------------------------------------------------------