├── .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 |
--------------------------------------------------------------------------------