diff --git a/.gitignore b/.gitignore index 97f7c15..12cedc8 100644 --- a/.gitignore +++ b/.gitignore @@ -155,4 +155,11 @@ xcuserdata #### # UNKNOWN: recommended by others, but I can't discover what these files are # -# ...none. Everything is now explained. \ No newline at end of file +# ...none. Everything is now explained. + + +#### +# Xcode VCS metadata +# + +*.xccheckout diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..ff553dd --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Jason Jarrett + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/PubSub.h b/PubSub.h index 6daee94..6bbb877 100644 --- a/PubSub.h +++ b/PubSub.h @@ -19,11 +19,45 @@ @end -#define PubSub(name, type) \ + + +// Variable expansion with variable arguments thanks to +// http://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments +// TODO look at a generic PubSub(name, ...) macro? + + +#define PubSub0(name) \ +@interface Pub (PubSub_ ## name ## _Category) \ ++ (void) name; \ +@end\ +@interface Sub (PubSub_ ## name ## _Category)\ ++ (void) while:(id)obj name:(void(^)(void))callback;\ +@end + +#define PubSub1(name, arg1Type, arg1Name) \ +@interface Pub (PubSub_ ## name ## _Category) \ ++ (void) name:(arg1Type)arg1Name; \ +@end\ +@interface Sub (PubSub_ ## name ## _Category)\ ++ (void) while:(id)obj name:(void(^)(arg1Type arg1Name))callback;\ +@end + +#define PubSub2(name, arg1Type, arg1Name, arg2Type, arg2Name) \ @interface Pub (PubSub_ ## name ## _Category) \ -+ (void) name:(type*)arg; \ ++ (void) name:(arg1Type)arg1Name arg2Name:(arg2Type)arg2Name; \ @end\ @interface Sub (PubSub_ ## name ## _Category)\ -+ (void) while:(id)obj name:(void(^)(type*))callback;\ ++ (void) while:(id)obj name:(void(^)(arg1Type arg1Name, arg2Type arg2Name))callback;\ @end + +#define PubSub3(name, arg1Type, arg1Name, arg2Type, arg2Name, arg3Type, arg3Name) \ +@interface Pub (PubSub_ ## name ## _Category) \ ++ (void) name:(arg1Type)arg1Name arg2Name:(arg2Type)arg2Name arg3Name:(arg3Type)arg3Name; \ +@end\ +@interface Sub (PubSub_ ## name ## _Category)\ ++ (void) while:(id)obj name:(void(^)(arg1Type arg1Name, arg2Type arg2Name, arg3Type arg3Name))callback;\ +@end + + + diff --git a/PubSub.m b/PubSub.m index b518309..d7bcb16 100644 --- a/PubSub.m +++ b/PubSub.m @@ -11,8 +11,6 @@ // #import "PubSub.h" -#import -#import #import @@ -27,8 +25,6 @@ + (NSMethodSignature *) methodSignatureForSelector:(SEL)selector signature = [self methodSignatureForSelector:@selector(mockSubscribeWhile:callback:)]; } return signature; - - return nil; } + (void) forwardInvocation:(NSInvocation *)anInvocation @@ -38,8 +34,14 @@ + (void) forwardInvocation:(NSInvocation *)anInvocation __unsafe_unretained id keepAlive = nil; [anInvocation getArgument:&keepAlive atIndex:2]; - void(^callback)(id arg) = nil; - [anInvocation getArgument:&callback atIndex:3]; + void(^callbackZero)(void) = nil; + void(^callbackOne)(id arg1) = nil; + void(^callbackTwo)(id arg1, id arg2) = nil; + void(^callbackThree)(id arg1, id arg2, id arg3) = nil; + [anInvocation getArgument:&callbackZero atIndex:3]; + [anInvocation getArgument:&callbackOne atIndex:3]; + [anInvocation getArgument:&callbackTwo atIndex:3]; + [anInvocation getArgument:&callbackThree atIndex:3]; NSString* notificationName = [NSStringFromSelector(aSelector) componentsSeparatedByString:@":"][1]; @@ -52,7 +54,35 @@ + (void) forwardInvocation:(NSInvocation *)anInvocation return; } - callback(notification.object); + if(notification.userInfo.count == 0){ + callbackZero(); + return; + } + + __block id (^getArgAtIndex)(NSString *) = ^id(NSString *index) { + id result = notification.userInfo[index]; + if(result == [NSNull null]){ + return nil; + } + return result; + }; + + + if(notification.userInfo.count == 1){ + callbackOne(getArgAtIndex(@"0")); + return; + } + + if(notification.userInfo.count == 2){ + callbackTwo(getArgAtIndex(@"0"), getArgAtIndex(@"1")); + return; + } + + if(notification.userInfo.count == 3){ + callbackThree(getArgAtIndex(@"0"), getArgAtIndex(@"1"), getArgAtIndex(@"2")); + return; + } + }]; } @@ -62,25 +92,56 @@ + (void) forwardInvocation:(NSInvocation *)anInvocation @implementation Pub -+ (void) mockPublish:(id)arg { } ++ (void) mockPublishZero { } ++ (void) mockPublishOne:(id)arg1 { } ++ (void) mockPublishTwo:(id)arg1 :(id)arg2 { } ++ (void) mockPublishThree:(id)arg1 :(id)arg2 :(id)arg3 { } + (NSMethodSignature *) methodSignatureForSelector:(SEL)selector { + NSMethodSignature* signature = [super methodSignatureForSelector:selector]; if (!signature) { - signature = [self methodSignatureForSelector:@selector(mockPublish:)]; + NSString *selectorName = NSStringFromSelector(selector); + NSArray *selectorArgs = [selectorName componentsSeparatedByString:@":"]; + int argCount = selectorArgs.count - 1; + if(argCount == 0) { + signature = [self methodSignatureForSelector:@selector(mockPublishZero)]; + } else if(argCount == 1) { + signature = [self methodSignatureForSelector:@selector(mockPublishOne:)]; + } else if(argCount == 2) { + signature = [self methodSignatureForSelector:@selector(mockPublishTwo::)]; + } else if(argCount == 3) { + signature = [self methodSignatureForSelector:@selector(mockPublishThree:::)]; + } } return signature; } -+ (void)forwardInvocation:(NSInvocation *)i ++ (void)forwardInvocation:(NSInvocation *)anInvocation { - id arg = nil; - [i getArgument:&arg atIndex:2]; - NSString* notificationName = [NSStringFromSelector(i.selector) componentsSeparatedByString:@":"][0]; - [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:arg]; + NSString *selectorName = NSStringFromSelector(anInvocation.selector); + NSArray *selectorArgs = [selectorName componentsSeparatedByString:@":"]; + + NSString* notificationName = selectorArgs[0]; + + NSMutableDictionary *argsDict = [NSMutableDictionary new]; + + for (NSUInteger i = 0; i < (selectorArgs.count-1); i++) { + id arg = nil; + NSInteger paramIndex = i+2; + [anInvocation getArgument:&arg atIndex:paramIndex]; + + if(arg == nil){ + arg = [NSNull null]; + } + + [argsDict setObject:arg forKey:[[NSNumber numberWithInt:i] stringValue]]; + } + + [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:nil userInfo:argsDict]; } @end diff --git a/PubSub.podspec b/PubSub.podspec new file mode 100644 index 0000000..f87d6a7 --- /dev/null +++ b/PubSub.podspec @@ -0,0 +1,16 @@ +Pod::Spec.new do |s| + + s.name = "PubSub" + s.version = "0.0.1" + s.summary = "A minimal syntax block based wrapper around NSNotificaitonCenter." + s.homepage = "https://github.com/staxmanade/PubSub-iOS" + s.license = { :type => 'MIT', :file => 'LICENSE.md' } + s.author = { "Jason Jarrett" => "jason@elegantcode.com" } + s.authors = { "Josh Wright" => "josh@joshwright.com", "Jason Jarrett" => "jason@elegantcode.com"} + s.platform = :ios + s.ios.deployment_target = "5.0" + s.source = { :git => "https://github.com/staxmanade/PubSub-iOS.git", :tag => "0.0.1" } + s.source_files = 'PubSub.h', 'PubSub.m' + s.requires_arc = true + +end diff --git a/TestProject/TestPubSub/TestPubSub.xcodeproj/project.pbxproj b/TestProject/TestPubSub/TestPubSub.xcodeproj/project.pbxproj index 312109a..3970039 100644 --- a/TestProject/TestPubSub/TestPubSub.xcodeproj/project.pbxproj +++ b/TestProject/TestPubSub/TestPubSub.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 58ABB18418BBC79000A2F782 /* PubSub.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 58ABB18318BBC79000A2F782 /* PubSub.podspec */; }; BC23B537175E6CB800577890 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC23B536175E6CB800577890 /* UIKit.framework */; }; BC23B539175E6CB800577890 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC23B538175E6CB800577890 /* Foundation.framework */; }; BC23B53B175E6CB800577890 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC23B53A175E6CB800577890 /* CoreGraphics.framework */; }; @@ -18,6 +19,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 58ABB18318BBC79000A2F782 /* PubSub.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = PubSub.podspec; path = ../../PubSub.podspec; sourceTree = ""; }; BC23B532175E6CB800577890 /* TestPubSub.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestPubSub.app; sourceTree = BUILT_PRODUCTS_DIR; }; BC23B536175E6CB800577890 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; BC23B538175E6CB800577890 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -51,6 +53,7 @@ BC23B527175E6CB800577890 = { isa = PBXGroup; children = ( + 58ABB18318BBC79000A2F782 /* PubSub.podspec */, BC23B562175EB67200577890 /* readme.md */, BC23B53C175E6CB800577890 /* TestPubSub */, BC23B535175E6CB800577890 /* Frameworks */, @@ -159,6 +162,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 58ABB18418BBC79000A2F782 /* PubSub.podspec in Resources */, BC23B541175E6CB800577890 /* InfoPlist.strings in Resources */, BC23B563175EB67200577890 /* readme.md in Resources */, ); @@ -281,6 +285,7 @@ BC23B552175E6CB800577890 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/TestProject/TestPubSub/TestPubSub/AppDelegate.m b/TestProject/TestPubSub/TestPubSub/AppDelegate.m index 8af12b3..2d62088 100644 --- a/TestProject/TestPubSub/TestPubSub/AppDelegate.m +++ b/TestProject/TestPubSub/TestPubSub/AppDelegate.m @@ -24,35 +24,84 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( keeper = [[NSObject alloc] init]; - [Sub while:keeper nicknameChanged:^(NSString* newNickname) { - NSLog(@"nickname changed to: %@", newNickname); + // + // Zero arg subscription + // + [Sub while:keeper testWithZeroArgs:^{ + NSLog(@"Zero args..."); }]; - [Pub nicknameChanged:@"A"]; + [Pub testWithZeroArgs]; + [Pub testWithZeroArgs]; + [Pub testWithZeroArgs]; + [Pub testWithZeroArgs]; + + // + // One arg subscription + // + [Sub while:keeper testWithOneArg:^(NSString *a) { + NSLog(@"One arg: %@", a); + }]; + + [Pub testWithOneArg:@"A"]; - [self performSelector:@selector(b) withObject:nil afterDelay:.1]; - [self performSelector:@selector(c) withObject:nil afterDelay:.15]; - [self performSelector:@selector(d) withObject:nil afterDelay:.2]; + // + // Two arg subscription + // + [Sub while:keeper testWithTwoArgs:^(NSString *a, NSString *b) { + NSLog(@"Two args: a: %@, b: %@", a, b); + }]; + [Pub testWithTwoArgs:@"a" b:@"B"]; + [Pub testWithTwoArgs:@"A" b:@"b"]; + [Pub testWithTwoArgs:@"aaa" b:@"bbb"]; + + // + // Three arg subscription + // + [Sub while:keeper testWithThreeArgs:^(NSString *a, NSString *b, NSString *c) { + NSLog(@"Three args: a: %@, b: %@, c: %@", a, b, c); + }]; + [Pub testWithThreeArgs:@"a" b:@"b" c:@"c"]; + [Pub testWithThreeArgs:@"a" b:@"b" c:@"c"]; + [Pub testWithThreeArgs:@"a" b:@"b" c:@"c"]; + [Pub testWithThreeArgs:@"a" b:@"b" c:@"c"]; + [Pub testWithThreeArgs:@"a" b:@"b" c:@"c"]; + + + [self testEventWithNilArguments]; + [self testWithAnInt]; + return YES; } -- (void) b -{ - NSLog(@"publishing b"); - [Pub nicknameChanged:@"B"]; -} -- (void) c -{ - NSLog(@"keeper = nil"); - keeper = nil; +- (void) testEventWithNilArguments { + [Sub while:self testWith1NilArguments:^(NSString *a) { + NSParameterAssert(a == nil); + }]; + [Pub testWith1NilArguments:nil]; + + [Sub while:self testWith2NilArguments:^(NSString *a, NSString *b) { + NSParameterAssert(a == nil); + NSParameterAssert(b == nil); + }]; + [Pub testWith2NilArguments:nil b:nil]; + + [Sub while:self testWith3NilArguments:^(NSString *a, NSString *b, NSString *c) { + NSParameterAssert(a == nil); + NSParameterAssert(b == nil); + NSParameterAssert(c == nil); + }]; + [Pub testWith3NilArguments:nil b:nil c:nil]; } -- (void) d -{ - NSLog(@"publishing d"); - [Pub nicknameChanged:@"D"]; +- (void) testWithAnInt { +// TODO: find a way to support this scenario +// [Sub while:self testWithAnInt:^(int a) { +// NSParameterAssert(a == 7); +// }]; +// [Pub testWithAnInt:7]; } @end diff --git a/TestProject/TestPubSub/TestPubSub/Notifications.h b/TestProject/TestPubSub/TestPubSub/Notifications.h index 66ea660..e7ff375 100644 --- a/TestProject/TestPubSub/TestPubSub/Notifications.h +++ b/TestProject/TestPubSub/TestPubSub/Notifications.h @@ -9,4 +9,35 @@ #import #import "PubSub.h" -PubSub(nicknameChanged, NSString) + +PubSub0(testWithZeroArgs) + +PubSub1(testWithOneArg, \ + NSString*, a) + +PubSub2(testWithTwoArgs, \ + NSString*, a, \ + NSString*, b) + +PubSub3(testWithThreeArgs, \ + NSString*, a, \ + NSString*, b, \ + NSString*, c) + + + +PubSub1(testWith1NilArguments, \ + NSString*, a) + +PubSub2(testWith2NilArguments, \ + NSString*, a, \ + NSString*, b) + +PubSub3(testWith3NilArguments, \ + NSString*, a, \ + NSString*, b, \ + NSString*, c) + + +// TODO: find a way to support this scenario +// PubSub1(testWithAnInt, int, a) \ No newline at end of file diff --git a/readme.md b/readme.md index 72a9150..84a7066 100644 --- a/readme.md +++ b/readme.md @@ -1,3 +1,7 @@ +_NOTE: this project is a fork of [bendytree/PubSub-iOS](https://github.com/bendytree/PubSub-iOS) and has changed significantly from it's original source. I'd like to say thanks to *bendytree* for the idea and the kick-start of a project._ + +# WARNING: +I love the concept of this project and the simplicity of declaring, consuming and publishing of notifications it provides. However, I've recently pulled it out of a project I was using due to a number of random crashes - if anyone more Objective-C versed than I could have a look I'd love some feedback. But for now - BEWARE :P @@ -10,10 +14,10 @@ The goal is minimal syntax, strong typing, and automatic unregistering. Here's what it looks like: // Declare your notification - PubSub(nameChanged, NSString) + PubSub1(nameChanged, NSString*, name) // Subscribe - [Sub while:self nameChanged:^(NSString* name){ + [Sub while:self nameChanged:^(NSString *name){ NSLog(@"New Name: %@", name); }]; @@ -26,14 +30,23 @@ Here's what it looks like: This library has no dependencies and requires arc. -### Copy 3 Files +### Install into your project + +#### Option 1 (Copy 3 files) -Copy `PubSub.h`, `PubSub.m`, and `Notifications.h` into your XCode project. The PubSub files +Copy `PubSub.h`, `PubSub.m`, and `Notifications.h` into your Xcode project. The PubSub files do all the work and `Notifications.h` is where you declare your notifications. If your project doesn't have ARC enabled, add the `-fobjc-arc` flag for `PubSub.m`. -### Import Notifications.h +#### Option 2 (Installation with CocoaPods) + +CocoaPods is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries. + + pod "PubSub" + + +### Import Notifications.h (Bonus step) In your `AppName-Prefix.pch` file, add `#import "Notifications.h"`. This makes it available in all your classes. You can skip this step if you want to do it differently. @@ -42,34 +55,71 @@ in all your classes. You can skip this step if you want to do it differently. In `Notifications.h` you will use a preprocessor statement to declare your notifications. Declare one per line. For example: - PubSub(nameChanged, NSString) - -This generates two strongly typed methods that look kinda like this: + PubSub0(testWithZeroArgs) + + PubSub1(testWithOneArg, \ + NSString*, a) + + PubSub2(testWithTwoArgs, \ + NSString*, a, \ + NSString*, b) + + PubSub3(testWithThreeArgs, \ + NSString*, a, \ + NSString*, b, \ + NSString*, c) + +The first argument to `PubZubN` is the name of the notification/event. For each parameter you specify the `type` and the `name` of the argument up to 3 arguments. + +These macro's generate the below strongly typed interface definition methods: @interface Pub - + (void) nameChanged:(NSString*)arg; + + (void) testWithZeroArgs; + + (void) testWithOneArg:(NSString*)a; + + (void) testWithTwoArgs:(NSString*)a b:(NSString*)b; + + (void) testWithThreeArgs:(NSString*)a b:(NSString*)b c:(NSString*)c; @end + @interface Sub - + (void) while:(id)obj nameChanged:(void(^)(NSString*))callback; + + (void) while:(id)obj testWithZeroArgs:(void(^)(void))callback; + + (void) while:(id)obj testWithOneArg:(void(^)(NSString *a))callback; + + (void) while:(id)obj testWithTwoArgs:(void(^)(NSString *a, NSString *b))callback; + + (void) while:(id)obj testWithThreeArgs:(void(^)(NSString *a, NSString *b, NSString *c))callback; @end -The first argument to `PubSub` is the name - it can be anything you want. The second argument -is the type of object you are sending out. - + ### Publishing -Now whenever you want to post a `nameChanged` notification, you just call: +Now whenever you want to post a notification, you can call: - [Pub nameChanged:@"Josh"]; + [Pub testWithZeroArgs]; + + [Pub testWithOneArg:@"a"]; + [Pub testWithTwoArgs:@"a" b:@"b"]; + [Pub testWithThreeArgs:@"a" b:@"b" c:@"c"]; + + ### Subscribing When you want to subscribe to an event, you pass it the block of code that should run: - [Sub while:self nameChanged:^(NSString* name){ - NSLog(@"New Name: %@", name); + [Sub while:self testWithZeroArgs:^{ + NSLog(@"zero args notification published"); + }]; + + [Sub while:self testWithOneArg:^(NSString* a){ + NSLog(@"a:%@", a); + }]; + + [Sub while:self testWithTwoArgs:^(NSString *a, NSString *b){ + NSLog(@"a:%@ b:%@", a, b); + }]; + + [Sub while:self testWithThreeArgs:^(NSString *a, NSString *b, NSString *c){ + NSLog(@"a:%@ b:%@ c:%@", a, b, c); }]; The first argument `while` is the best part. It keeps a weak reference to an object. When @@ -82,14 +132,7 @@ unsubscribed. No cleanup necessary. Compare that to `NSNotificationCenter` where you have to retain an observer then you have to explicitly unregister when you're done. - - - - - - - - - - - + +# WARNING: + +You can't use value types such as `int`, `BOOL`, etc in the PubSub macro... _If someone could help me figure out a way to acomplish this (possibly through some macro tricks) I'd love the feedback!_