Permalink
Browse files

Added an NSNotificationCenter extension for weak references

  • Loading branch information...
1 parent 6900c37 commit 875a979c663a816cdf115445f3ada35896c4ee59 @jspahrsummers jspahrsummers committed Sep 26, 2012
@@ -21,6 +21,13 @@
D042FC8815F72BC7004E8054 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D042FC5515F72B23004E8054 /* SenTestingKit.framework */; };
D042FC8B15F72BC7004E8054 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D042FC4415F72B23004E8054 /* Foundation.framework */; };
D042FC8E15F72BC7004E8054 /* libMantle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D042FC7915F72BC7004E8054 /* libMantle.a */; };
+ D064BA2C1613B36C004CA27A /* NSNotificationCenter+MTLWeakReferenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D064BA2A1613B36C004CA27A /* NSNotificationCenter+MTLWeakReferenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D064BA2D1613B36C004CA27A /* NSNotificationCenter+MTLWeakReferenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D064BA2B1613B36C004CA27A /* NSNotificationCenter+MTLWeakReferenceAdditions.m */; };
+ D064BA2E1613B36C004CA27A /* NSNotificationCenter+MTLWeakReferenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D064BA2B1613B36C004CA27A /* NSNotificationCenter+MTLWeakReferenceAdditions.m */; };
+ D064BA301613B921004CA27A /* MTLNotificationCenterAdditionsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D064BA2F1613B921004CA27A /* MTLNotificationCenterAdditionsSpec.m */; };
+ D064BA311613B921004CA27A /* MTLNotificationCenterAdditionsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D064BA2F1613B921004CA27A /* MTLNotificationCenterAdditionsSpec.m */; };
+ D064BA341613BA75004CA27A /* MTLTestNotificationObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = D064BA331613BA75004CA27A /* MTLTestNotificationObserver.m */; };
+ D064BA351613BA75004CA27A /* MTLTestNotificationObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = D064BA331613BA75004CA27A /* MTLTestNotificationObserver.m */; };
D0760E0515FFBD440060F550 /* Mantle.h in Headers */ = {isa = PBXBuildFile; fileRef = D042FC4C15F72B23004E8054 /* Mantle.h */; settings = {ATTRIBUTES = (Public, ); }; };
D0760E7815FFBF330060F550 /* MTLModel.h in Headers */ = {isa = PBXBuildFile; fileRef = D0760E7615FFBF330060F550 /* MTLModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
D0760E7915FFBF330060F550 /* MTLModel.m in Sources */ = {isa = PBXBuildFile; fileRef = D0760E7715FFBF330060F550 /* MTLModel.m */; };
@@ -203,6 +210,11 @@
D042FCB215F72C16004E8054 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = "<group>"; };
D042FCCB15F72EE8004E8054 /* Expecta.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Expecta.xcodeproj; path = MantleTests/expecta/Expecta.xcodeproj; sourceTree = "<group>"; };
D042FCEE15F72EF1004E8054 /* Specta.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Specta.xcodeproj; path = MantleTests/specta/Specta.xcodeproj; sourceTree = "<group>"; };
+ D064BA2A1613B36C004CA27A /* NSNotificationCenter+MTLWeakReferenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+MTLWeakReferenceAdditions.h"; sourceTree = "<group>"; };
+ D064BA2B1613B36C004CA27A /* NSNotificationCenter+MTLWeakReferenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSNotificationCenter+MTLWeakReferenceAdditions.m"; sourceTree = "<group>"; };
+ D064BA2F1613B921004CA27A /* MTLNotificationCenterAdditionsSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTLNotificationCenterAdditionsSpec.m; sourceTree = "<group>"; };
+ D064BA321613BA75004CA27A /* MTLTestNotificationObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTLTestNotificationObserver.h; sourceTree = "<group>"; };
+ D064BA331613BA75004CA27A /* MTLTestNotificationObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTLTestNotificationObserver.m; sourceTree = "<group>"; };
D0760E7615FFBF330060F550 /* MTLModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTLModel.h; sourceTree = "<group>"; };
D0760E7715FFBF330060F550 /* MTLModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTLModel.m; sourceTree = "<group>"; };
D0760EA015FFC8080060F550 /* EXTKeyPathCoding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = EXTKeyPathCoding.h; path = extobjc/EXTKeyPathCoding.h; sourceTree = "<group>"; };
@@ -340,6 +352,8 @@
children = (
D0760EC715FFCA4E0060F550 /* MTLTestModel.h */,
D0760EC815FFCA4E0060F550 /* MTLTestModel.m */,
+ D064BA321613BA75004CA27A /* MTLTestNotificationObserver.h */,
+ D064BA331613BA75004CA27A /* MTLTestNotificationObserver.m */,
D0C92DE115F72F6A00387438 /* MantleTests-Prefix.pch */,
D042FC5D15F72B23004E8054 /* MantleTests-Info.plist */,
D042FC5E15F72B23004E8054 /* InfoPlist.strings */,
@@ -427,6 +441,7 @@
88080C1C160A719D00CCABF2 /* MTLArrayManipulationSpec.m */,
D090E66015F73315005282F9 /* MTLHigherOrderAdditionsSpec.m */,
D0760EC315FFCA250060F550 /* MTLModelSpec.m */,
+ D064BA2F1613B921004CA27A /* MTLNotificationCenterAdditionsSpec.m */,
D08B5AB116002A23001FE685 /* MTLValueTransformerSpec.m */,
);
name = Specs;
@@ -441,6 +456,8 @@
88080C17160A706900CCABF2 /* NSArray+MTLManipulationAdditions.m */,
D090E62815F730D1005282F9 /* NSDictionary+MTLHigherOrderAdditions.h */,
D090E62915F730D1005282F9 /* NSDictionary+MTLHigherOrderAdditions.m */,
+ D064BA2A1613B36C004CA27A /* NSNotificationCenter+MTLWeakReferenceAdditions.h */,
+ D064BA2B1613B36C004CA27A /* NSNotificationCenter+MTLWeakReferenceAdditions.m */,
D090E62A15F730D1005282F9 /* NSOrderedSet+MTLHigherOrderAdditions.h */,
D090E62B15F730D1005282F9 /* NSOrderedSet+MTLHigherOrderAdditions.m */,
D090E62C15F730D1005282F9 /* NSSet+MTLHigherOrderAdditions.h */,
@@ -497,6 +514,7 @@
D0760E7815FFBF330060F550 /* MTLModel.h in Headers */,
D08B5AAE16002694001FE685 /* MTLValueTransformer.h in Headers */,
88080C18160A706900CCABF2 /* NSArray+MTLManipulationAdditions.h in Headers */,
+ D064BA2C1613B36C004CA27A /* NSNotificationCenter+MTLWeakReferenceAdditions.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -746,6 +764,7 @@
D0760E7915FFBF330060F550 /* MTLModel.m in Sources */,
D08B5AAF16002694001FE685 /* MTLValueTransformer.m in Sources */,
88080C19160A706900CCABF2 /* NSArray+MTLManipulationAdditions.m in Sources */,
+ D064BA2D1613B36C004CA27A /* NSNotificationCenter+MTLWeakReferenceAdditions.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -758,6 +777,8 @@
D0760EC915FFCA4E0060F550 /* MTLTestModel.m in Sources */,
D08B5AB216002A23001FE685 /* MTLValueTransformerSpec.m in Sources */,
88080C1D160A719D00CCABF2 /* MTLArrayManipulationSpec.m in Sources */,
+ D064BA301613B921004CA27A /* MTLNotificationCenterAdditionsSpec.m in Sources */,
+ D064BA341613BA75004CA27A /* MTLTestNotificationObserver.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -773,6 +794,7 @@
D0760E7A15FFBF330060F550 /* MTLModel.m in Sources */,
D08B5AB016002694001FE685 /* MTLValueTransformer.m in Sources */,
88080C1A160A706900CCABF2 /* NSArray+MTLManipulationAdditions.m in Sources */,
+ D064BA2E1613B36C004CA27A /* NSNotificationCenter+MTLWeakReferenceAdditions.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -785,6 +807,8 @@
D0760ECA15FFCA4E0060F550 /* MTLTestModel.m in Sources */,
D08B5AB316002A23001FE685 /* MTLValueTransformerSpec.m in Sources */,
88080C1E160A719D00CCABF2 /* MTLArrayManipulationSpec.m in Sources */,
+ D064BA311613B921004CA27A /* MTLNotificationCenterAdditionsSpec.m in Sources */,
+ D064BA351613BA75004CA27A /* MTLTestNotificationObserver.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
@@ -11,5 +11,6 @@
#import "NSArray+MTLHigherOrderAdditions.h"
#import "NSArray+MTLManipulationAdditions.h"
#import "NSDictionary+MTLHigherOrderAdditions.h"
+#import "NSNotificationCenter+MTLWeakReferenceAdditions.h"
#import "NSOrderedSet+MTLHigherOrderAdditions.h"
#import "NSSet+MTLHigherOrderAdditions.h"
@@ -0,0 +1,32 @@
+//
+// NSNotificationCenter+MTLWeakReferenceAdditions.h
+// Mantle
+//
+// Created by Justin Spahr-Summers on 2012-09-26.
+// Copyright (c) 2012 GitHub. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSNotificationCenter (MTLWeakReferenceAdditions)
+
+// Adds `observer` as an observer for the given notification, originating from
+// the given object. If the `observer` is later deallocated, it is automatically
+// unregistered from the notification.
+//
+// observer - The object upon which `selector` will be invoked when the specified
+// notification is posted. This object must support weak references.
+// selector - The selector to invoke upon `object` when the specified
+// notification is posted. This selector must accept a single
+// argument of type `NSNotification *`.
+// name - The name of the notification to observe, or `nil` to not use the
+// notification name as a criterion for dispatch.
+// object - The object to observe for notifications, or `nil` to not use the
+// notification sender as a criterion for dispatch.
+//
+// Returns an opaque observer object which can later be passed to
+// -removeObserver: or -removeObserver:name:object: to stop observation.
+// (`observer` cannot be used for removal.)
+- (id)mtl_addWeakObserver:(id)observer selector:(SEL)selector name:(NSString *)name object:(id)object;
+
+@end
@@ -0,0 +1,79 @@
+//
+// NSNotificationCenter+MTLWeakReferenceAdditions.m
+// Mantle
+//
+// Created by Justin Spahr-Summers on 2012-09-26.
+// Copyright (c) 2012 GitHub. All rights reserved.
+//
+
+#import "NSNotificationCenter+MTLWeakReferenceAdditions.h"
+#import "EXTScope.h"
+#import <objc/runtime.h>
+
+// Used as an associated object on weak observers, so we can automatically
+// remove them when they're deallocated.
+//
+// This is used in addition to -- not instead of -- __weak, because associated
+// objects are only torn down after deallocation _finishes_ (and we need to
+// avoid messaging the object as soon as it starts).
+@interface MTLObserverLifecycleTracker : NSObject
+
+@property (nonatomic, strong, readonly) id blockObserver;
+
+- (id)initWithBlockObserver:(id)observer;
+
+@end
+
+@implementation NSNotificationCenter (MTLWeakReferenceAdditions)
+
+- (id)mtl_addWeakObserver:(id)observerObject selector:(SEL)selector name:(NSString *)name object:(id)object {
+ NSParameterAssert(observerObject != nil);
+ NSParameterAssert(selector != NULL);
+
+ // MTLObserverLifecycleTracker currently only communicates with the default
+ // center.
+ NSAssert([self isEqual:NSNotificationCenter.defaultCenter], @"%s does not support notification centers other than the default", __func__);
+
+ __block id blockObserver;
+
+ @weakify(observerObject);
+
+ blockObserver = [self addObserverForName:name object:object queue:nil usingBlock:^(NSNotification *notification) {
+ @strongify(observerObject);
+ if (observerObject == nil) return;
+
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[observerObject methodSignatureForSelector:selector]];
+ [invocation setTarget:observerObject];
+ [invocation setSelector:selector];
+ [invocation setArgument:&notification atIndex:2];
+ [invocation invoke];
+ }];
+
+ MTLObserverLifecycleTracker *tracker = [[MTLObserverLifecycleTracker alloc] initWithBlockObserver:blockObserver];
+
+ // Use the tracker itself as a unique key, to avoid collisions and since we
+ // never need to remove it.
+ objc_setAssociatedObject(observerObject, (__bridge void *)tracker, tracker, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+
+ return blockObserver;
+}
+
+@end
+
+@implementation MTLObserverLifecycleTracker
+
+- (id)initWithBlockObserver:(id)observer {
+ NSParameterAssert(observer != nil);
+
+ self = [super init];
+ if (self == nil) return nil;
+
+ _blockObserver = observer;
+ return self;
+}
+
+- (void)dealloc {
+ [NSNotificationCenter.defaultCenter removeObserver:self.blockObserver];
+}
+
+@end
@@ -0,0 +1,51 @@
+//
+// MTLNotificationCenterAdditionsSpec.m
+// Mantle
+//
+// Created by Justin Spahr-Summers on 2012-09-26.
+// Copyright (c) 2012 GitHub. All rights reserved.
+//
+
+#import "MTLTestNotificationObserver.h"
+
+SpecBegin(MTLNotificationCenterAdditions)
+
+NSString *notificationName = @"MTLNotificationCenterAdditionsNotification";
+
+it(@"should send notifications to weak observers", ^{
+ MTLTestNotificationObserver *observer = [[MTLTestNotificationObserver alloc] init];
+ expect(observer).notTo.beNil();
+
+ id token = [NSNotificationCenter.defaultCenter mtl_addWeakObserver:observer selector:@selector(notificationPosted:) name:notificationName object:self];
+ expect(token).notTo.beNil();
+
+ expect(observer.receivedNotification).to.beFalsy();
+ [NSNotificationCenter.defaultCenter postNotificationName:notificationName object:self];
+ expect(observer.receivedNotification).to.beTruthy();
+
+ [NSNotificationCenter.defaultCenter removeObserver:token];
+
+ // The observer shouldn't receive this notification. (It'll throw an
+ // assertion if it does.)
+ [NSNotificationCenter.defaultCenter postNotificationName:notificationName object:self];
+});
+
+it(@"should unregister from notifications if the observer is deallocated", ^{
+ __weak id weakObserver = nil;
+
+ @autoreleasepool {
+ MTLTestNotificationObserver *observer __attribute__((objc_precise_lifetime)) = [[MTLTestNotificationObserver alloc] init];
+
+ weakObserver = observer;
+ expect(weakObserver).notTo.beNil();
+
+ [NSNotificationCenter.defaultCenter mtl_addWeakObserver:observer selector:@selector(notificationPosted:) name:notificationName object:self];
+ }
+
+ expect(weakObserver).to.beNil();
+
+ // Should do nothing.
+ [NSNotificationCenter.defaultCenter postNotificationName:notificationName object:self];
+});
+
+SpecEnd
@@ -0,0 +1,18 @@
+//
+// MTLTestNotificationObserver.h
+// Mantle
+//
+// Created by Justin Spahr-Summers on 2012-09-26.
+// Copyright (c) 2012 GitHub. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface MTLTestNotificationObserver : NSObject
+
+@property (nonatomic, assign, readonly) BOOL receivedNotification;
+
+// Should only be invoked once.
+- (void)notificationPosted:(NSNotification *)notification;
+
+@end
@@ -0,0 +1,24 @@
+//
+// MTLTestNotificationObserver.m
+// Mantle
+//
+// Created by Justin Spahr-Summers on 2012-09-26.
+// Copyright (c) 2012 GitHub. All rights reserved.
+//
+
+#import "MTLTestNotificationObserver.h"
+
+@interface MTLTestNotificationObserver ()
+@property (nonatomic, assign, readwrite) BOOL receivedNotification;
+@end
+
+@implementation MTLTestNotificationObserver
+
+- (void)notificationPosted:(NSNotification *)notification {
+ NSParameterAssert(notification != nil);
+
+ NSAssert(!self.receivedNotification, @"Should receive a single notification: %@", notification);
+ self.receivedNotification = YES;
+}
+
+@end

0 comments on commit 875a979

Please sign in to comment.