├── .gitignore ├── .travis.yml ├── NNKit.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── NNKit.xcscheme ├── NNKit ├── API Extension │ ├── NSInvocation+NNCopying.h │ ├── NSInvocation+NNCopying.m │ ├── NSNotificationCenter+NNAdditions.h │ ├── NSNotificationCenter+NNAdditions.m │ ├── README.md │ ├── despatch.h │ ├── despatch.m │ ├── runtime.c │ └── runtime.h ├── Actors │ ├── NNCleanupProxy.h │ ├── NNCleanupProxy.m │ ├── NNMultiDispatchManager.h │ ├── NNMultiDispatchManager.m │ ├── NNPollingObject+Protected.h │ ├── NNPollingObject.h │ ├── NNPollingObject.m │ ├── NNSelfInvalidatingObject.h │ ├── NNSelfInvalidatingObject.m │ └── README.md ├── Collections │ ├── NNWeakSet.h │ ├── NNWeakSet.m │ ├── NSCollections+NNComprehensions.h │ ├── NSCollections+NNComprehensions.m │ ├── README.md │ ├── _NNWeakArrayTombstone.h │ ├── _NNWeakArrayTombstone.m │ ├── _NNWeakSetEnumerator.h │ └── _NNWeakSetEnumerator.m ├── Concurrency │ ├── NNDelegateProxy.h │ ├── NNDelegateProxy.m │ └── README.md ├── Hacks │ ├── NNStrongifiedProperties.h │ ├── NNStrongifiedProperties.m │ ├── README.md │ ├── memoize.h │ ├── memoize.m │ ├── nn_autofree.h │ └── nn_autofree.m ├── NNKit Mac-Prefix.pch ├── NNKit iOS-Prefix.pch ├── NNKit-Info.plist ├── NNKit.h ├── NNKit.m ├── Services │ ├── NNService+Protected.h │ ├── NNService.h │ ├── NNService.m │ ├── NNServiceManager.h │ ├── NNServiceManager.m │ └── README.md ├── Swizzling │ ├── NNISASwizzledObject.h │ ├── NNISASwizzledObject.m │ ├── README.md │ ├── nn_isaSwizzling.h │ ├── nn_isaSwizzling.m │ └── nn_isaSwizzling_Private.h └── macros.h ├── NNKitTests ├── NNCleanupProxyTests.m ├── NNComprehensionTests.m ├── NNDelegateProxyTests.m ├── NNKit Mac Tests-Info.plist ├── NNKit iOS Tests-Info.plist ├── NNMultiDispatchManagerTests.m ├── NNPollingObjectTests.m ├── NNSelfInvalidatingObjectTests.m ├── NNServiceTests.m ├── NNStrongifiedPropertiesTests.m ├── NNSynthesizedObjectStorageTests.m ├── NNTestCase.h ├── NNTestCase.m ├── NNWeakObserverTests.m ├── NNWeakSetTests.m ├── nn_autofreeTests.m └── nn_isaSwizzlingTests.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Project specific ignores: 2 | 3 | *-local.* 4 | *-local/ 5 | 6 | # Xcode-specific ignores: 7 | 8 | ######################### 9 | # .gitignore file for Xcode4 / OS X Source projects 10 | # 11 | # NB: if you are storing "built" products, this WILL NOT WORK, 12 | # and you should use a different .gitignore (or none at all) 13 | # This file is for SOURCE projects, where there are many extra 14 | # files that we want to exclude 15 | # 16 | # For updates, see: http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects 17 | ######################### 18 | 19 | ##### 20 | # OS X temporary files that should never be committed 21 | 22 | .DS_Store 23 | *.swp 24 | *.lock 25 | profile 26 | 27 | 28 | #### 29 | # Xcode temporary files that should never be committed 30 | # 31 | # NB: NIB/XIB files still exist even on Storyboard projects, so we want this... 32 | 33 | *~.nib 34 | 35 | 36 | #### 37 | # Xcode build files - 38 | # 39 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "DerivedData" 40 | 41 | DerivedData/ 42 | 43 | ##### 44 | # Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups) 45 | # 46 | # This is complicated: 47 | # 48 | # SOMETIMES you need to put this file in version control. 49 | # Apple designed it poorly - if you use "custom executables", they are 50 | # saved in this file. 51 | # 99% of projects do NOT use those, so they do NOT want to version control this file. 52 | # ..but if you're in the 1%, comment out the line "*.pbxuser" 53 | 54 | *.pbxuser 55 | *.mode1v3 56 | *.mode2v3 57 | *.perspectivev3 58 | # NB: also, whitelist the default ones, some projects need to use these 59 | !default.pbxuser 60 | !default.mode1v3 61 | !default.mode2v3 62 | !default.perspectivev3 63 | 64 | 65 | #### 66 | # Xcode 4 - semi-personal settings, often included in workspaces 67 | # 68 | # You can safely ignore the xcuserdata files - but do NOT ignore the files next to them 69 | # 70 | 71 | xcuserdata 72 | 73 | 74 | #### 75 | # XCode 4 workspaces - more detailed 76 | # 77 | # Workspaces are important! They are a core feature of Xcode - don't exclude them :) 78 | # 79 | # Workspace layout is quite spammy. For reference: 80 | # 81 | # (root)/ 82 | # (project-name).xcodeproj/ 83 | # project.pbxproj 84 | # project.xcworkspace/ 85 | # contents.xcworkspacedata 86 | # xcuserdata/ 87 | # (your name)/xcuserdatad/ 88 | # xcuserdata/ 89 | # (your name)/xcuserdatad/ 90 | # 91 | # 92 | # 93 | # Xcode 4 workspaces - SHARED 94 | # 95 | # This is UNDOCUMENTED (google: "developer.apple.com xcshareddata" - 0 results 96 | # But if you're going to kill personal workspaces, at least keep the shared ones... 97 | # 98 | # 99 | !xcshareddata 100 | 101 | 102 | #### 103 | # Xcode 4 - Deprecated classes 104 | # 105 | # Allegedly, if you manually "deprecate" your classes, they get moved here. 106 | # 107 | # We're using source-control, so this is a "feature" that we do not want! 108 | 109 | *.moved-aside 110 | 111 | *.xccheckout 112 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | xcode_project: NNKit.xcodeproj 3 | xcode_scheme: NNKit 4 | osx_image: xcode7 -------------------------------------------------------------------------------- /NNKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NNKit.xcodeproj/xcshareddata/xcschemes/NNKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /NNKit/API Extension/NSInvocation+NNCopying.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSInvocation+NNCopying.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 03/10/14. 6 | // Copyright © 2014 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | @interface NSInvocation (NNCopying) 18 | 19 | - (instancetype)nn_copy; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /NNKit/API Extension/NSInvocation+NNCopying.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSInvocation+NNCopying.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 03/10/14. 6 | // Copyright © 2014 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NSInvocation+NNCopying.h" 16 | 17 | @implementation NSInvocation (NNCopying) 18 | 19 | - (instancetype)nn_copy; 20 | { 21 | NSMethodSignature *signature = [self methodSignature]; 22 | NSUInteger const arguments = signature.numberOfArguments; 23 | 24 | NSInvocation *result = [NSInvocation invocationWithMethodSignature:signature]; 25 | 26 | void *heapBuffer = NULL; 27 | size_t heapBufferSize = 0; 28 | 29 | NSUInteger alignp = 0; 30 | for (NSUInteger i = 0; i < arguments; i++) { 31 | const char *type = [signature getArgumentTypeAtIndex:i]; 32 | NSGetSizeAndAlignment(type, NULL, &alignp); 33 | 34 | if (alignp > heapBufferSize) { 35 | heapBuffer = heapBuffer 36 | ? reallocf(heapBuffer, alignp) 37 | : malloc(alignp); 38 | heapBufferSize = alignp; 39 | } 40 | 41 | [self getArgument:heapBuffer atIndex:i]; 42 | [result setArgument:heapBuffer atIndex:i]; 43 | } 44 | 45 | if (heapBuffer) { 46 | free(heapBuffer); 47 | } 48 | 49 | result.target = self.target; 50 | 51 | if (self.argumentsRetained) { 52 | [result retainArguments]; 53 | } 54 | 55 | return result; 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /NNKit/API Extension/NSNotificationCenter+NNAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSNotificationCenter+NNAdditions.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/14/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | 18 | /*! 19 | * @category NNAdditions 20 | * 21 | * @discussion 22 | * Provides weak observer support to NSNotificationCenter. 23 | */ 24 | @interface NSNotificationCenter (NNAdditions) 25 | 26 | /*! 27 | * @method addWeakObserver:selector:name:object: 28 | * 29 | * @abstract 30 | * Adds an entry to the receiver's dispatch table with an observer, a notification 31 | * selector and optional criteria: notification name and sender. 32 | * 33 | * @discussion 34 | * The observer is referenced weakly and does not need to be explicitly removed when 35 | * the object is deallocated. 36 | * 37 | * @param observer 38 | * Object registering as an observer. This value must not be nil. 39 | * 40 | * @param aSelector 41 | * Selector that specifies the message the receiver sends observer to notify 42 | * it of the notification posting. The method specified by aSelector must have 43 | * one and only one argument (an instance of NSNotification). 44 | * 45 | * @param aName 46 | * The name of the notification for which to register the observer; that is, only 47 | * notifications with this name are delivered to the observer. If you pass 48 | * nil, the notification center doesn’t use a notification’s name to 49 | * decide whether to deliver it to the observer. 50 | * 51 | * @param anObject 52 | * The object whose notifications the observer wants to receive; that is, only 53 | * notifications sent by this sender are delivered to the observer. If you pass 54 | * nil, the notification center doesn’t use a notification’s sender 55 | * to decide whether to deliver it to the observer. 56 | */ 57 | - (void)addWeakObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject; 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /NNKit/API Extension/NSNotificationCenter+NNAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSNotificationCenter+NNAdditions.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/14/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NSNotificationCenter+NNAdditions.h" 16 | 17 | #import "NNCleanupProxy.h" 18 | 19 | 20 | @implementation NSNotificationCenter (NNAdditions) 21 | 22 | - (void)addWeakObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject; 23 | { 24 | NSString *key = [NSString stringWithFormat:@"%p-%@-%@-%p", anObject, NSStringFromSelector(aSelector), aName, self]; 25 | NNCleanupProxy *proxy = [NNCleanupProxy cleanupProxyForTarget:observer withKey:[key hash]]; 26 | 27 | __weak NSNotificationCenter *weakCenter = self; 28 | __unsafe_unretained NNCleanupProxy *unsafeProxy = proxy; 29 | 30 | [proxy cacheMethodSignatureForSelector:aSelector]; 31 | proxy.cleanupBlock = ^{ 32 | NSNotificationCenter *center = weakCenter; 33 | 34 | [center removeObserver:unsafeProxy name:aName object:anObject]; 35 | }; 36 | [self addObserver:proxy selector:aSelector name:aName object:anObject]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /NNKit/API Extension/README.md: -------------------------------------------------------------------------------- 1 | NNKit API Extensions 2 | ==================== 3 | 4 | For its own internal use, as well as your enjoyment, NNKit contains a set of extensions to existing system APIs. 5 | 6 | despatch 7 | -------- 8 | 9 | [Despatch](http://numist.net/define/?despatch) contains helper functions to make dealing with GCD easier. At the moment this is one function: 10 | 11 | ### `despatch_sync_main_reentrant` #### 12 | 13 | `despatch_sync_main_reentrant` is a function for making synchronous dispatch onto the main queue simpler. The block argument is invoked directly if the sender is already executing on the main thread, and dispatched synchronously onto the main queue otherwise. 14 | 15 | ### `despatch_group_yield` ### 16 | 17 | The yield concept is borrowed from Python and other languages as a way for a path of execution to pause and allow other work scheduled for that thread to proceed. In this case it's most useful in unit tests to allow asynchronous work that takes place on the main thread to proceed. 18 | 19 | A very basic example from `NNDelegateProxyTests.m`: 20 | 21 | - (void)testGlobalAsync; 22 | { 23 | dispatch_group_enter(group); 24 | [[[MYClass alloc] initWithDelegate:self] globalAsync]; 25 | 26 | NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:0.1]; 27 | while (!despatch_group_yield(group) && [[NSDate date] compare:timeout] == NSOrderedAscending); 28 | XCTAssertFalse(dispatch_group_wait(group, DISPATCH_TIME_NOW), @"Delegate message was never received (timed out)!"); 29 | } 30 | 31 | runtime 32 | ------- 33 | 34 | Runtime provides functions that should exist in the Objective-C runtime, but don't. 35 | 36 | ### `nn_selector_belongsToProtocol` ### 37 | 38 | `nn_selector_belongsToProtocol` returns whether or not a selector belongs to a protocol, with additional arguments that inform its search pattern and return information about the selector found in the protocol. Providing default values for `instance` and `required` begin the search with those attributes, and their values on return indicate the attributes of the first match found. 39 | 40 | NSNotificationCenter 41 | -------------------- 42 | 43 | The `NNAdditions` category to `NSNotificationCenter` adds a `addWeakObserver:selector:name:object:` method which references the observer weakly so no observer removal is required—it is automatically cleaned up when the observer is deallocated. 44 | 45 | NSInvocation 46 | ------------ 47 | 48 | The `NNCopying` category to `NSInvocation` adds an `nn_copy` method which returns a copy of the receiver. 49 | -------------------------------------------------------------------------------- /NNKit/API Extension/despatch.h: -------------------------------------------------------------------------------- 1 | // 2 | // despatch.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #ifndef NNKit_despatch_h 16 | #define NNKit_despatch_h 17 | 18 | /*! 19 | * @function despatch_sync_main_reentrant 20 | * 21 | * @abstract 22 | * Runs a block on the main queue, reentrantly if already on the main queue. 23 | * 24 | * @discussion 25 | * When on the main queue, executes the block synchronously before returning. 26 | * Otherwise, submits a block to the main queue and does not return until the block 27 | * has finished. 28 | * 29 | * @param block 30 | * The block to be invoked on the main queue. 31 | * The result of passing NULL in this parameter is undefined. 32 | * Which is to say it will probably crash. 33 | */ 34 | void despatch_sync_main_reentrant(dispatch_block_t block); 35 | 36 | /*! 37 | * @function despatch_group_yield 38 | * 39 | * @abstract 40 | * Yields control of the current runloop. Return value indicates if the dispatch 41 | * group is clear. 42 | * 43 | * @discussion 44 | * When waiting for asynchronous jobs in a dispatch group that may block on the 45 | * current thread, this function yields the runloop and then returns the group 46 | * state. 47 | * 48 | * @param group 49 | * The group for which the caller is waiting. 50 | * 51 | * @result 52 | * YES if the group has no members, NO otherwise. 53 | */ 54 | BOOL despatch_group_yield(dispatch_group_t group); 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /NNKit/API Extension/despatch.m: -------------------------------------------------------------------------------- 1 | // 2 | // despatch.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #include "despatch.h" 16 | 17 | 18 | void despatch_sync_main_reentrant(dispatch_block_t block) 19 | { 20 | if ([[NSThread currentThread] isMainThread]) { 21 | block(); 22 | } else { 23 | dispatch_sync(dispatch_get_main_queue(), block); 24 | } 25 | } 26 | 27 | BOOL despatch_group_yield(dispatch_group_t group) 28 | { 29 | // Let the runloop consume another event. 30 | NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; 31 | assert(currentRunLoop); // I sure have gotten paranoid in my old age. 32 | (void)[currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]]; 33 | 34 | // dispatch_group_wait docs say it returns zero or nonzero. Luckily, it needs to be inverted anyway, so a valid BOOL value gets enforced. 35 | return !dispatch_group_wait(group, DISPATCH_TIME_NOW); 36 | } 37 | -------------------------------------------------------------------------------- /NNKit/API Extension/runtime.c: -------------------------------------------------------------------------------- 1 | // 2 | // runtime.c 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #include "runtime.h" 16 | 17 | #include 18 | #include 19 | 20 | 21 | BOOL nn_selector_belongsToProtocol(SEL selector, Protocol *protocol, BOOL *requiredPtr, BOOL *instancePtr) 22 | { 23 | BOOL required = requiredPtr ? !!*requiredPtr : NO; 24 | BOOL instance = instancePtr ? !!*instancePtr : NO; 25 | 26 | for (int i = 0; i < (1 << 2); ++i) { 27 | BOOL checkRequired = required ^ (i & 1); 28 | BOOL checkInstance = instance ^ ((i & (1 << 1)) >> 1); 29 | 30 | struct objc_method_description hasMethod = protocol_getMethodDescription(protocol, selector, checkRequired, checkInstance); 31 | if (hasMethod.name || hasMethod.types) { 32 | if (requiredPtr) { 33 | *requiredPtr = checkRequired; 34 | } 35 | if (instancePtr) { 36 | *instancePtr = checkInstance; 37 | } 38 | return YES; 39 | } 40 | } 41 | 42 | return NO; 43 | } 44 | -------------------------------------------------------------------------------- /NNKit/API Extension/runtime.h: -------------------------------------------------------------------------------- 1 | // 2 | // runtime.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #ifndef NNKit_rantime_h 16 | #define NNKit_rantime_h 17 | 18 | #include 19 | 20 | #import 21 | 22 | // Memory-backed property generation for categories 23 | #define NNSynthesizeObjectStorage(type, name, getter, setter) \ 24 | - (type)getter { return objc_getAssociatedObject(self, NNSelfSelector(getter)); } \ 25 | - (void)setter(type)name { \ 26 | int association = [NNMemoize(^{ \ 27 | objc_property_t property = class_getProperty([self class], #name); \ 28 | int association = OBJC_ASSOCIATION_ASSIGN; \ 29 | char *value = NULL; \ 30 | if ((value = property_copyAttributeValue(property, "W"))) { \ 31 | free(value); \ 32 | @throw [NSException exceptionWithName:@"oops" reason:@"Not implemented" userInfo:nil]; \ 33 | } else if ((value = property_copyAttributeValue(property, "C"))) { \ 34 | free(value); \ 35 | association = OBJC_ASSOCIATION_COPY; \ 36 | } else if ((value = property_copyAttributeValue(property, "&"))) { \ 37 | free(value); \ 38 | association = OBJC_ASSOCIATION_RETAIN; \ 39 | } \ 40 | return @(association); \ 41 | }) intValue]; \ 42 | objc_setAssociatedObject(self, NNSelfSelector(getter), name, association); \ 43 | } 44 | 45 | /*! 46 | * @function nn_selector_belongsToProtocol 47 | * 48 | * @abstract 49 | * Returns whether a selector belongs to a specific protocol. 50 | * 51 | * @discussion 52 | * Search hinting can be performed by using the required and instance parameters, which are set on 53 | * successful match to the instance/class, required/optional settings of the match. 54 | * 55 | * @param selector 56 | * The selector to be found. 57 | * 58 | * @param protocol 59 | * The protocol to be searched. 60 | * 61 | * @param required 62 | * Whether the match should be required or not. Can be NULL. 63 | * If not-NULL and the selector is found in the protocol, 64 | * the parameter is set to the requirement setting of the match. 65 | * 66 | * @param instance 67 | * Whether the match should be instance or class-level. Can be NULL. 68 | * If not-NULL and the selector is found in the protocol, the 69 | * parameter is set to the instance/class setting of the match. 70 | * 71 | * @result 72 | * YES if the protocol contains any selector matching selector. 73 | * NO otherwise. 74 | */ 75 | BOOL nn_selector_belongsToProtocol(SEL selector, Protocol *protocol, BOOL *required, BOOL *instance); 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /NNKit/Actors/NNCleanupProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNCleanupProxy.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/18/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | 18 | /*! 19 | * @class NNCleanupProxy 20 | * 21 | * @discussion 22 | * A cleanup proxy is used when a weak reference to an object is desired, but not possible. An example 23 | * of this is registering for notifications—the notification center should maintain a weak reference 24 | * to the object instead of requiring bracketed add/remove observer calls. 25 | * 26 | * The one caveat to using a weak proxy in this situation is that method signatures must be pre-cached, 27 | * since the runtime will explode if a nil method signature is returned during the message forwarding 28 | * process. The NNCleanupProxy object will helpfully assert if a method signature does not already 29 | * exist in cache at the time it is needed. 30 | * 31 | * For example use, see 32 | * NSNotificationCenter+NNAdditions addWeakObserver:selector:name:object:. 33 | */ 34 | @interface NNCleanupProxy : NSProxy 35 | 36 | /*! 37 | * @method cleanupProxyForTarget: 38 | * 39 | * @abstract 40 | * Creates a proxy object holding a weak reference to, forwarding messages to, and with an object 41 | * lifetime dependant on target. 42 | */ 43 | + (NNCleanupProxy *)cleanupProxyForTarget:(id)target withKey:(uintptr_t)key; 44 | 45 | /*! 46 | * @method cleanupProxyForTarget:conformingToProtocol: 47 | * 48 | * @abstract 49 | * Creates a proxy object holding a weak reference to, forwarding messages to, and with an object 50 | * lifetime dependant on target, conforming to protocol protocol. 51 | */ 52 | + (NNCleanupProxy *)cleanupProxyForTarget:(id)target conformingToProtocol:(Protocol *)protocol withKey:(uintptr_t)key; 53 | 54 | + (void)cleanupAfterTarget:(id)target withBlock:(void (^)())block withKey:(uintptr_t)key; 55 | 56 | + (void)cancelCleanupForTarget:(id)target withKey:(uintptr_t)key; 57 | 58 | /*! 59 | * @property cleanupBlock 60 | * 61 | * @abstract 62 | * The block run when target is deallocated (in the absence of other strong references to the proxy). 63 | * 64 | * @discussion 65 | * This block is used to clean up any registrations that have been made for the proxy that require 66 | * bracketed calls to remove. Messages sent to the proxy intended for its target will react 67 | * with the same semantics as messaging nil. 68 | */ 69 | @property (nonatomic, readwrite, copy) void (^cleanupBlock)(); 70 | 71 | /*! 72 | * @method cacheMethodSignatureForSelector: 73 | * 74 | * @abstract 75 | * Caches the method signature of a selector that the proxy is expected to forward to the target. 76 | * 77 | * @discussion 78 | * If an unexpected method is sent to the proxy with the expectation that it will be forwarded, 79 | * the proxy will throw an exception as if the selector is not recognized. Likewise if a method 80 | * signature cannot be cached, the proxy will throw an exception. 81 | * 82 | * @param aSelector 83 | * The selector for which the method signature should be fetched from the proxy's target. 84 | */ 85 | - (void)cacheMethodSignatureForSelector:(SEL)aSelector; 86 | 87 | //- (void)cacheMethodSignaturesForProtocol:(Protocol)aProtocol; 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /NNKit/Actors/NNCleanupProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNCleanupProxy.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/18/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NNCleanupProxy.h" 16 | 17 | #import 18 | 19 | #import "nn_autofree.h" 20 | 21 | 22 | @interface NNCleanupProxy () 23 | 24 | @property (nonatomic, readonly, weak) NSObject *target; 25 | @property (nonatomic, readonly, assign) NSUInteger hash; 26 | @property (nonatomic, readonly, strong) NSMutableDictionary *signatureCache; 27 | 28 | @end 29 | 30 | 31 | // XXX: rdar://15478132 means no explicit local strongification here due to retain leak :( 32 | #pragma clang diagnostic push 33 | #pragma clang diagnostic ignored "-Wreceiver-is-weak" 34 | 35 | @implementation NNCleanupProxy 36 | 37 | + (NNCleanupProxy *)cleanupProxyForTarget:(id)target withKey:(uintptr_t)key; 38 | { 39 | return [self cleanupProxyForTarget:target conformingToProtocol:@protocol(NSObject) withKey:key]; 40 | } 41 | 42 | + (NNCleanupProxy *)cleanupProxyForTarget:(id)target conformingToProtocol:(Protocol *)protocol withKey:(uintptr_t)key; 43 | { 44 | NSParameterAssert([target conformsToProtocol:protocol]); 45 | 46 | NNCleanupProxy *result = [NNCleanupProxy alloc]; 47 | result->_target = target; 48 | result->_signatureCache = [NSMutableDictionary new]; 49 | objc_setAssociatedObject(target, (void *)key, result, OBJC_ASSOCIATION_RETAIN); 50 | 51 | [result cacheMethodSignaturesForProcotol:protocol]; 52 | 53 | return result; 54 | } 55 | 56 | + (void)cleanupAfterTarget:(id)target withBlock:(void (^)())block withKey:(uintptr_t)key; 57 | { 58 | NNCleanupProxy *result = [NNCleanupProxy cleanupProxyForTarget:target withKey:(uintptr_t)key]; 59 | result.cleanupBlock = block; 60 | } 61 | 62 | + (void)cancelCleanupForTarget:(id)target withKey:(uintptr_t)key; 63 | { 64 | objc_setAssociatedObject(target, (void *)key, nil, OBJC_ASSOCIATION_RETAIN); 65 | } 66 | 67 | - (void)dealloc; 68 | { 69 | // If the proxy is in dealloc and the target is still live, then no cleanup is needed—the proxy has been removed or replaced. 70 | if (self->_target) { 71 | return; 72 | } 73 | 74 | if (self->_cleanupBlock) { 75 | self->_cleanupBlock(); 76 | } 77 | } 78 | 79 | #pragma mark NSObject protocol 80 | 81 | @synthesize hash = _hash; 82 | - (NSUInteger)hash; 83 | { 84 | @synchronized(self) { 85 | if (!self->_hash) { 86 | self->_hash = self.target.hash; 87 | } 88 | } 89 | 90 | return self->_hash; 91 | } 92 | 93 | - (BOOL)isEqual:(id)object; 94 | { 95 | return [object isEqual:self.target]; 96 | } 97 | 98 | #pragma mark Message forwarding 99 | 100 | - (id)forwardingTargetForSelector:(SEL)aSelector; 101 | { 102 | if ([self.signatureCache objectForKey:NSStringFromSelector(aSelector)]) { 103 | return self.target; 104 | } 105 | 106 | return self; 107 | } 108 | 109 | #pragma mark NNCleanupProxy 110 | 111 | - (void)cacheMethodSignatureForSelector:(SEL)aSelector; 112 | { 113 | NSMethodSignature *signature = [self.target methodSignatureForSelector:aSelector]; 114 | 115 | if (signature) { 116 | [self.signatureCache setObject:signature forKey:NSStringFromSelector(aSelector)]; 117 | } else { 118 | @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Unable to get method signature for selector %@ from target of instance %p", NSStringFromSelector(aSelector), self] userInfo:nil]; 119 | } 120 | } 121 | 122 | // This could be faster/lighter if method signature was late-binding, at the cost of higher complexity. 123 | - (void)cacheMethodSignaturesForProcotol:(Protocol *)protocol; 124 | { 125 | unsigned int totalCount; 126 | for (uint8_t i = 0; i < 1 << 1; ++i) { 127 | struct objc_method_description *methodDescriptions = nn_autofree(protocol_copyMethodDescriptionList(protocol, i & 1, YES, &totalCount)); 128 | 129 | for (unsigned j = 0; j < totalCount; j++) { 130 | struct objc_method_description *methodDescription = methodDescriptions + j; 131 | [self.signatureCache setObject:[NSMethodSignature signatureWithObjCTypes:methodDescription->types] forKey:NSStringFromSelector(methodDescription->name)]; 132 | } 133 | } 134 | 135 | // Recurse to include other protocols to which this protocol adopts 136 | Protocol * __unsafe_unretained *adoptions = protocol_copyProtocolList(protocol, &totalCount); 137 | for (unsigned j = 0; j < totalCount; j++) { 138 | [self cacheMethodSignaturesForProcotol:adoptions[j]]; 139 | } 140 | } 141 | 142 | @end 143 | 144 | #pragma clang diagnostic pop 145 | -------------------------------------------------------------------------------- /NNKit/Actors/NNMultiDispatchManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNMultiDispatchManager.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/19/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | 18 | @interface NNMultiDispatchManager : NSObject 19 | 20 | @property (nonatomic, assign, readwrite, getter = isEnabled, setter = setIsEnabled:) BOOL enabled; 21 | @property (nonatomic, readonly, assign) Protocol *protocol; 22 | 23 | - (instancetype)initWithProtocol:(Protocol *)protocol; 24 | 25 | - (void)addObserver:(id)observer; 26 | - (BOOL)hasObserver:(id)observer; 27 | - (void)removeObserver:(id)observer; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /NNKit/Actors/NNMultiDispatchManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNMultiDispatchManager.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/19/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NNMultiDispatchManager.h" 16 | 17 | #import "NNWeakSet.h" 18 | #import "despatch.h" 19 | #import "nn_autofree.h" 20 | #import "NSInvocation+NNCopying.h" 21 | #import "runtime.h" 22 | 23 | 24 | @interface NNMultiDispatchManager () 25 | 26 | @property (nonatomic, readonly, strong) NSMutableDictionary *signatureCache; 27 | @property (nonatomic, readonly, strong) NNWeakSet *observers; 28 | 29 | @end 30 | 31 | 32 | @implementation NNMultiDispatchManager 33 | 34 | - (instancetype)initWithProtocol:(Protocol *)protocol; 35 | { 36 | if (!(self = [super init])) { return nil; } 37 | 38 | self->_enabled = YES; 39 | self->_protocol = protocol; 40 | self->_signatureCache = [NSMutableDictionary new]; 41 | [self _cacheMethodSignaturesForProcotol:protocol]; 42 | self->_observers = [NNWeakSet new]; 43 | 44 | return self; 45 | } 46 | 47 | @synthesize enabled = _enabled; 48 | 49 | - (BOOL)isEnabled; 50 | { 51 | @synchronized(self) { 52 | return self->_enabled; 53 | } 54 | } 55 | 56 | - (void)setIsEnabled:(BOOL)enabled; 57 | { 58 | @synchronized(self) { 59 | self->_enabled = enabled; 60 | } 61 | } 62 | 63 | - (void)addObserver:(id)observer; 64 | { 65 | NSParameterAssert([observer conformsToProtocol:self.protocol]); 66 | 67 | @synchronized(self) { 68 | [self.observers addObject:observer]; 69 | } 70 | } 71 | 72 | - (BOOL)hasObserver:(id)observer; 73 | { 74 | NSAssert([NSThread isMainThread], @"Boundary call was not made on main thread"); 75 | 76 | @synchronized(self) { 77 | return [self.observers containsObject:observer]; 78 | } 79 | } 80 | 81 | - (void)removeObserver:(id)observer; 82 | { 83 | NSAssert([NSThread isMainThread], @"Boundary call was not made on main thread"); 84 | 85 | @synchronized(self) { 86 | [self.observers removeObject:observer]; 87 | } 88 | } 89 | 90 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector; 91 | { 92 | @synchronized(self) { 93 | return [self.signatureCache objectForKey:NSStringFromSelector(aSelector)]; 94 | } 95 | } 96 | 97 | - (void)forwardInvocation:(NSInvocation *)anInvocation; 98 | { 99 | @synchronized(self) { 100 | if (self.enabled) { 101 | if (anInvocation.methodSignature.isOneway) { 102 | // If we're going async, copy the invocation to avoid multiple threads calling -invoke or otherwise acting in a thread-unsafe manner. 103 | anInvocation = [anInvocation nn_copy]; 104 | [anInvocation retainArguments]; 105 | } 106 | 107 | NSAssert(strstr(anInvocation.methodSignature.methodReturnType, "v"), @"Method return type must be void."); 108 | dispatch_block_t dispatch = ^{ 109 | [self private_forwardInvocation:anInvocation]; 110 | }; 111 | if (anInvocation.methodSignature.isOneway) { 112 | dispatch_async(dispatch_get_main_queue(), dispatch); 113 | } else { 114 | despatch_sync_main_reentrant(dispatch); 115 | } 116 | } 117 | 118 | anInvocation.target = nil; 119 | [anInvocation invoke]; 120 | } 121 | } 122 | 123 | #pragma mark Private 124 | 125 | - (void)_cacheMethodSignaturesForProcotol:(Protocol *)protocol; 126 | { 127 | @synchronized(self) { 128 | unsigned int totalCount; 129 | for (uint8_t i = 0; i < 1 << 1; ++i) { 130 | struct objc_method_description *methodDescriptions = nn_autofree(protocol_copyMethodDescriptionList(protocol, i & 1, YES, &totalCount)); 131 | 132 | for (unsigned j = 0; j < totalCount; j++) { 133 | struct objc_method_description *methodDescription = methodDescriptions + j; 134 | [self.signatureCache setObject:[NSMethodSignature signatureWithObjCTypes:methodDescription->types] forKey:NSStringFromSelector(methodDescription->name)]; 135 | } 136 | } 137 | 138 | // Recurse to include other protocols to which this protocol adopts 139 | Protocol * __unsafe_unretained *adoptions = (Protocol * __unsafe_unretained *)nn_autofree(protocol_copyProtocolList(protocol, &totalCount)); 140 | for (unsigned j = 0; j < totalCount; j++) { 141 | [self _cacheMethodSignaturesForProcotol:adoptions[j]]; 142 | } 143 | } 144 | } 145 | 146 | - (void)private_forwardInvocation:(NSInvocation *)anInvocation; 147 | { 148 | @synchronized(self) { 149 | BOOL required = YES; 150 | BOOL instance = YES; 151 | BOOL sanity = nn_selector_belongsToProtocol(anInvocation.selector, self.protocol, &required, &instance); 152 | 153 | #ifndef NS_BLOCK_ASSERTIONS 154 | NSAssert(sanity && instance, @"Selector %@ is not actually part of protocol %@?!", NSStringFromSelector(anInvocation.selector), NSStringFromProtocol(self.protocol)); 155 | #else 156 | (void)sanity; 157 | #endif 158 | 159 | for (id obj in self.observers) { 160 | if ([obj respondsToSelector:anInvocation.selector] || required) { 161 | [anInvocation invokeWithTarget:obj]; 162 | } 163 | } 164 | } 165 | } 166 | 167 | @end 168 | -------------------------------------------------------------------------------- /NNKit/Actors/NNPollingObject+Protected.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNPollingObject+Protected.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 07/12/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | 18 | @interface NNPollingObject (Protected) 19 | 20 | - (void)postNotification:(NSDictionary *)userInfo; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /NNKit/Actors/NNPollingObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNPollingObject.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 07/10/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | 18 | @interface NNPollingObject : NSObject 19 | 20 | + (NSString *)notificationName; 21 | 22 | // I think this is the first time where I've wanted the default (atomic, assign, readwrite) flags for a property! 23 | // Too bad I have all warnings turned on: 24 | @property (atomic, assign, readwrite) NSTimeInterval interval; 25 | 26 | - (instancetype)initWithQueue:(dispatch_queue_t)queue; 27 | - (void)main; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /NNKit/Actors/NNPollingObject.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNPollingObject.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 07/10/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NNPollingObject.h" 16 | 17 | #import "despatch.h" 18 | 19 | 20 | @interface NNPollingObject () 21 | 22 | @property (nonatomic, strong, readonly) dispatch_queue_t queue; 23 | 24 | @end 25 | 26 | 27 | @implementation NNPollingObject 28 | 29 | + (NSString *)notificationName; 30 | { 31 | return [NSString stringWithFormat:@"%@%@", NSStringFromClass(self), @"PollCompleteNotification"]; 32 | } 33 | 34 | - (instancetype)initWithQueue:(dispatch_queue_t)queue; 35 | { 36 | self = [super init]; 37 | if (!self) return nil; 38 | 39 | _queue = queue; 40 | 41 | dispatch_async(_queue, ^{ 42 | [self workerLoop]; 43 | }); 44 | 45 | return self; 46 | } 47 | 48 | - (instancetype)init; 49 | { 50 | return [self initWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; 51 | } 52 | 53 | - (void)workerLoop; 54 | { 55 | [self main]; 56 | 57 | NSTimeInterval interval = self.interval; 58 | if (interval <= 0.0) { 59 | return; 60 | } 61 | 62 | __weak id weakSelf = self; 63 | double delayInSeconds = interval; 64 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 65 | dispatch_after(popTime, self.queue, ^(void){ 66 | id self = weakSelf; 67 | [self workerLoop]; 68 | }); 69 | } 70 | 71 | - (void)postNotification:(NSDictionary *)userInfo; 72 | { 73 | despatch_sync_main_reentrant(^{ 74 | [[NSNotificationCenter defaultCenter] postNotificationName:[[self class] notificationName] object:self userInfo:userInfo]; 75 | }); 76 | } 77 | 78 | - (void)main; 79 | { 80 | @throw [NSException exceptionWithName:@"NNPollingObjectException" reason:@"Method must be overridden by subclass!" userInfo:@{ @"_cmd" : NSStringFromSelector(_cmd), @"class" : NSStringFromClass([self class]) }]; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /NNKit/Actors/NNSelfInvalidatingObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNSelfInvalidatingObject.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | 18 | /*! 19 | * @class NNSelfInvalidatingObject 20 | * 21 | * @discussion 22 | * For use when an object has to do cleanup work asynchronously, or outside of 23 | * dealloc. Simply implement the invalidate method and 24 | * call [super invalidate] when the actor has finished cleaning up. 25 | */ 26 | @interface NNSelfInvalidatingObject : NSObject 27 | 28 | /*! 29 | * @method invalidate 30 | * 31 | * @discussion 32 | * Called only once, either explicitly (by an interested object) or when the 33 | * object has been fully-released. 34 | * 35 | * When invalidation is complete, [super invalidate] must be called to complete 36 | * deallocation of the object. 37 | */ 38 | - (void)invalidate; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /NNKit/Actors/NNSelfInvalidatingObject.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNSelfInvalidatingObject.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | // Requires -fno-objc-arc 15 | // 16 | 17 | #import "NNSelfInvalidatingObject.h" 18 | 19 | #import 20 | 21 | 22 | @interface NNSelfInvalidatingObject () { 23 | _Bool _valid; 24 | } 25 | 26 | @property (nonatomic, assign) long refCount; 27 | 28 | @end 29 | 30 | 31 | @implementation NNSelfInvalidatingObject 32 | 33 | #pragma mark NSObject 34 | 35 | - (instancetype)init; 36 | { 37 | if (!(self = [super init])) { return nil; } 38 | 39 | self->_refCount = 0; 40 | self->_valid = true; 41 | 42 | /* 43 | * -invalidate supports two conditions: 44 | * * invalidate may be called before the refCount hits zero, in which case the object should survive until its natural death. 45 | * * invalidate may be called at refCount zero, in which case the object should survive until the end of the current runloop. 46 | * To satisfy both of these constraints, retain/release messages are forwarded to super and one extra retain is made (here) balanced by an autorelease in -invalidate to keep the object alive while it is still valid/invalidating. 47 | * Calling dealloc directly is an error and is not supported. 48 | */ 49 | [self retain]; 50 | 51 | return self; 52 | } 53 | 54 | - (instancetype)retain; 55 | { 56 | @synchronized(self) { 57 | ++self.refCount; 58 | } 59 | [super retain]; 60 | return self; 61 | } 62 | 63 | - (oneway void)release; 64 | { 65 | @synchronized(self) { 66 | --self.refCount; 67 | } 68 | [super release]; 69 | } 70 | 71 | - (oneway void)dealloc; 72 | { 73 | if (self->_valid) { 74 | [super dealloc]; 75 | @throw [NSException exceptionWithName:@"NNObjectLifetimeException" reason:@"Calling dealloc directly on a self-invalidating object is not supported (object destroyed without invalidation)." userInfo:nil]; 76 | } 77 | 78 | [super dealloc]; 79 | } 80 | 81 | #pragma mark NNSelfInvalidatingObject 82 | 83 | - (void)invalidate; 84 | { 85 | @synchronized(self) { 86 | if (self->_valid) { 87 | self->_valid = false; 88 | [self autorelease]; 89 | } 90 | } 91 | } 92 | 93 | #pragma mark Internal 94 | 95 | - (void)setRefCount:(long)refCount; 96 | { 97 | self->_refCount = refCount; 98 | 99 | if (!refCount && self->_valid) { 100 | dispatch_async(dispatch_get_main_queue(), ^{ 101 | [self invalidate]; 102 | }); 103 | } 104 | } 105 | 106 | @end 107 | -------------------------------------------------------------------------------- /NNKit/Actors/README.md: -------------------------------------------------------------------------------- 1 | NNKit Actor Model 2 | ================= 3 | 4 | The classes in this component solve some common problems encountered when using the actor pattern in Objective-C. 5 | 6 | NNCleanupProxy 7 | -------------- 8 | 9 | A cleanup proxy is used to perform some kind of work after an object is deallocated. The proxy itself can be used as a message forwarder, so long as the methods are pre-declared using `-cacheMethodSignatureForSelector:`. The implementation of [`NSNotificationCenter+NNAdditions`](https://github.com/numist/NNKit/blob/master/NNKit/API%20Extension/NSNotificationCenter%2BNNAdditions.m) uses a cleanup proxy to provide its functionality. 10 | 11 | NNMultiDispatchManager 12 | ---------------------- 13 | 14 | The multi-dispatch manager is a new mechanism to enable structured to-many message dispatch. Instead of using global notifications, which are very error-prone with magic keys into parochial `userInfo` dictionaries, multi-dispatch acts more like a delegate where observers conform to a common protocol, and messages belonging to that protocol that are sent to the multi-dispatch manager are forwarded to all of the observers. All protocol methods must return `void`, and methods decorated with `oneway` are dispatched asynchronously. All dispatch messages are sent on the main thread, and messages sent to the multi-dispatch manager can be sent on any thread. 15 | 16 | #### Example: #### 17 | 18 | ``` objective-c 19 | // 20 | // Interface 21 | // 22 | 23 | @protocol MyProtocol 24 | - (oneway void)foo:(id)bar; 25 | @end 26 | 27 | 28 | @interface MyDispatcher : NSObject 29 | 30 | - (void)addObserver:(id)observer; 31 | - (void)removeObserver:(id)observer; 32 | 33 | @end 34 | 35 | @interface MyObserver : NSObject 36 | 37 | @end 38 | 39 | 40 | // 41 | // Implementation 42 | // 43 | 44 | @interface MyDispatcher () 45 | 46 | @property (nonatomic, strong, readonly) NNMultiDispatchManager *dispatchManager; 47 | 48 | @end 49 | 50 | @implementation MyDispatcher 51 | 52 | + (instancetype)sharedDispatcher; 53 | { 54 | static MyDispatcher *singleton; 55 | static dispatch_once_t onceToken; 56 | dispatch_once(&onceToken, ^{ 57 | singleton = [MyDispatcher new]; 58 | }); 59 | return singleton; 60 | } 61 | 62 | - (id)init; 63 | { 64 | if (!(self = [super init])) { return nil; } 65 | 66 | _dispatchManager = [[NNMultiDispatchManager alloc] initWithProtocol:@protocol(MyProtocol)]; 67 | 68 | return self; 69 | } 70 | 71 | - (void)addObserver:(id)observer; 72 | { 73 | [self.dispatchManager addObserver:observer]; 74 | } 75 | 76 | - (void)removeObserver:(id)observer; 77 | { 78 | [self.dispatchManager removeObserver:observer]; 79 | } 80 | 81 | - (void)dispatchEvent; 82 | { 83 | [(id)self.dispatchManager foo:[NSObject new]]; 84 | } 85 | 86 | @end 87 | 88 | 89 | @implementation MyObserver 90 | 91 | - (id)init; 92 | { 93 | if (!(self = [super init])) { return nil; } 94 | 95 | // There's no need to removeObserver in dealloc, since NNMultiDispatchManager references observers weakly. 96 | [[MyDispatcher sharedDispatcher] addObserver:self]; 97 | 98 | return self; 99 | } 100 | 101 | - (oneway void)foo:(id)bar; 102 | { 103 | // … 104 | } 105 | 106 | @end 107 | ``` 108 | 109 | NNPollingObject 110 | --------------- 111 | 112 | Sometimes there is no way to have information pushed to you, and it has to be checked occasionally by a polling object. This base class provides basic interval and queue priority support with a polling worker thread that terminates when the object is released. 113 | 114 | Subclasses need only override `-main` to use, and it's recommended that the built in `-postNotification:` method be used to emit events to interested parties. The `interval` property can be set to any time interval, with values less than or equal to zero causing the worker thread to terminate when it has finished its next scheduled iteration. 115 | 116 | NNSelfInvalidatingObject 117 | ------------------------ 118 | 119 | Some objects may encapsulate resources that require work to clean up, such as an open file handle. In some cases, these operations can take time or may otherwise require that the actor be alive for an extended period of time after its owner has released it. Subclassing `NNSelfInvalidatingObject` allows this condition to be handled easily. Simply override `-invalidate`, and it is called asynchronously on the main queue once the internal refCount of the object has reached zero. When cleanup is complete, calling `[super invalidate]` puts the object in the nearest autorelease pool and it is finally destroyed on the next iteration of the runloop. Calling `[self invalidate]` early prevents it from being called when the object refCount reaches zero (the object is destroyed immediately). 120 | 121 | This base class was inspired by [Andy Matuschak](https://github.com/andymatuschak). 122 | -------------------------------------------------------------------------------- /NNKit/Collections/NNWeakSet.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNWeakSet.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 06/04/16. 6 | // Copyright © 2016 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | /*! 18 | * @class NNMutableWeakSet 19 | * 20 | * @abstract 21 | * Provides a set-type collection that references its members weakly. 22 | * 23 | * @discussion 24 | * NNWeakSet implements the same API as NSMutableSet, but holds weak 25 | * references to its members. 26 | * 27 | * No compaction is required, members are automatically removed when they are 28 | * deallocated. 29 | * 30 | * This collection type may be slower than NSHashTable, but it also has 31 | * fewer bugs. 32 | */ 33 | @interface NNWeakSet : NSMutableSet 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /NNKit/Collections/NNWeakSet.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNWeakSet.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/15/13. 6 | // Copyright © 2016 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NNWeakSet.h" 16 | 17 | #import "_NNWeakArrayTombstone.h" 18 | #import "_NNWeakSetEnumerator.h" 19 | #import "NNCleanupProxy.h" 20 | 21 | 22 | @interface NNWeakSet () 23 | 24 | @property (nonatomic, readonly, strong) NSMutableSet *backingStore; 25 | 26 | - (void)_removeObjectAllowingNil:(id)object; 27 | 28 | @end 29 | 30 | 31 | /** 32 | 33 | collection -> tombstone // Unavoidable. The whole point of this exercise. 34 | tombstone -> object [style = "dotted"]; // Obvious. 35 | cleanup -> tombstone [style = "dotted"]; // For removing the tombstone from the collection. 36 | cleanup -> collection [style = "dotted"]; // For removing the tombstone from the collection. 37 | object -> cleanup; // Object association, the only strong reference to the proxy. 38 | object -> tombstone [style = "dotted]; // Object association so the collection can look at the object and find the tombstone. 39 | 40 | */ 41 | 42 | 43 | @implementation NNWeakSet 44 | 45 | - (instancetype)initWithCapacity:(NSUInteger)numItems; 46 | { 47 | if (!(self = [super init])) { return nil; } 48 | 49 | self->_backingStore = [[NSMutableSet alloc] initWithCapacity:numItems]; 50 | 51 | return self; 52 | } 53 | 54 | - (id)init; 55 | { 56 | if (!(self = [super init])) { return nil; } 57 | 58 | self->_backingStore = [NSMutableSet new]; 59 | 60 | return self; 61 | } 62 | 63 | #pragma mark NSSet 64 | 65 | - (NSUInteger)count; 66 | { 67 | @synchronized(self.backingStore) { 68 | return self.backingStore.count; 69 | } 70 | } 71 | 72 | - (id)member:(id)object; 73 | { 74 | @synchronized(self.backingStore) { 75 | return ((_NNWeakArrayTombstone *)[self.backingStore member:object]).target; 76 | } 77 | } 78 | 79 | - (NSEnumerator *)objectEnumerator; 80 | { 81 | @synchronized(self.backingStore) { 82 | return [[_NNWeakSetEnumerator alloc] initWithWeakSet:self]; 83 | } 84 | } 85 | 86 | #pragma mark NSMutableSet 87 | 88 | - (void)addObject:(id)object; 89 | { 90 | _NNWeakArrayTombstone *tombstone = [_NNWeakArrayTombstone tombstoneWithTarget:object]; 91 | NNCleanupProxy *proxy = [NNCleanupProxy cleanupProxyForTarget:object withKey:(uintptr_t)self]; 92 | 93 | __weak NNWeakSet *weakCollection = self; 94 | __weak _NNWeakArrayTombstone *weakTombstone = tombstone; 95 | proxy.cleanupBlock = ^{ 96 | NNWeakSet *collection = weakCollection; 97 | [collection _removeObjectAllowingNil:weakTombstone]; 98 | }; 99 | 100 | @synchronized(self.backingStore) { 101 | [self.backingStore addObject:tombstone]; 102 | } 103 | } 104 | 105 | - (void)removeObject:(id)object; 106 | { 107 | [NNCleanupProxy cancelCleanupForTarget:object withKey:(uintptr_t)self]; 108 | @synchronized(self.backingStore) { 109 | [self.backingStore removeObject:object]; 110 | } 111 | } 112 | 113 | #pragma mark Private 114 | 115 | - (void)_removeObjectAllowingNil:(id)object; 116 | { 117 | if (!object) { return; } 118 | 119 | @synchronized(self.backingStore) { 120 | [self.backingStore removeObject:object]; 121 | } 122 | } 123 | 124 | @end -------------------------------------------------------------------------------- /NNKit/Collections/NSCollections+NNComprehensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+NNComprehensions.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 02/25/14. 6 | // Copyright © 2014 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | typedef BOOL (^nn_filter_block_t)(id item); 18 | typedef id (^nn_map_block_t)(id item); 19 | typedef id (^nn_reduce_block_t)(id accumulator, id item); 20 | 21 | @protocol NNCollection 22 | - (instancetype)nn_filter:(nn_filter_block_t)block; 23 | - (instancetype)nn_map:(nn_map_block_t)block; 24 | - (id)nn_reduce:(nn_reduce_block_t)block; 25 | @end 26 | 27 | @interface NSArray (NNComprehensions) 28 | @end 29 | 30 | @interface NSSet (NNComprehensions) 31 | @end 32 | 33 | @interface NSOrderedSet (NNComprehensions) 34 | @end 35 | -------------------------------------------------------------------------------- /NNKit/Collections/NSCollections+NNComprehensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+NNComprehensions.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 02/25/14. 6 | // Copyright © 2014 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NSCollections+NNComprehensions.h" 16 | 17 | #define FILTER_DECLARATION() - (instancetype)nn_filter:(nn_filter_block_t)block 18 | #define FILTER_DEFINITION(__type__) \ 19 | id result = [[[__type__ class] new] mutableCopy]; \ 20 | for (id object in self) { \ 21 | if (block(object)) { \ 22 | [result addObject:object]; \ 23 | } \ 24 | } \ 25 | return result 26 | 27 | #define MAP_DECLARATION() - (instancetype)nn_map:(nn_map_block_t)block 28 | #define MAP_DEFINITION(__type__) \ 29 | id result = [[[__type__ class] new] mutableCopy]; \ 30 | for (id object in self) { \ 31 | [result addObject:block(object)]; \ 32 | } \ 33 | return result 34 | 35 | 36 | #define REDUCE_DECLARATION() - (id)nn_reduce:(nn_reduce_block_t)block 37 | #define REDUCE_DEFINITION(__type__) \ 38 | id accumulator = nil; \ 39 | for (id object in self) { \ 40 | accumulator = block(accumulator, object); \ 41 | } \ 42 | return accumulator 43 | 44 | 45 | @implementation NSArray (NNComprehensions) 46 | 47 | FILTER_DECLARATION(); 48 | { 49 | FILTER_DEFINITION(NSArray); 50 | } 51 | 52 | MAP_DECLARATION(); 53 | { 54 | MAP_DEFINITION(NSArray); 55 | } 56 | 57 | REDUCE_DECLARATION(); 58 | { 59 | REDUCE_DEFINITION(NSArray); 60 | } 61 | 62 | @end 63 | 64 | @implementation NSSet (NNComprehensions) 65 | 66 | FILTER_DECLARATION(); 67 | { 68 | FILTER_DEFINITION(NSSet); 69 | } 70 | 71 | MAP_DECLARATION(); 72 | { 73 | MAP_DEFINITION(NSSet); 74 | } 75 | 76 | REDUCE_DECLARATION(); 77 | { 78 | REDUCE_DEFINITION(NSSet); 79 | } 80 | 81 | @end 82 | 83 | @implementation NSOrderedSet (NNComprehensions) 84 | 85 | FILTER_DECLARATION(); 86 | { 87 | FILTER_DEFINITION(NSOrderedSet); 88 | } 89 | 90 | MAP_DECLARATION(); 91 | { 92 | MAP_DEFINITION(NSOrderedSet); 93 | } 94 | 95 | REDUCE_DECLARATION(); 96 | { 97 | REDUCE_DEFINITION(NSOrderedSet); 98 | } 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /NNKit/Collections/README.md: -------------------------------------------------------------------------------- 1 | # NNKit Collections # 2 | 3 | The code in this component gives collections superpowers. 4 | 5 | ## Category: NNComprehensions ## 6 | 7 | The `NNComprehensions` category on `NSArray`, `NSSet`, and `NSOrderedSet` implements the following comprehensions: 8 | 9 | ### nn_filter: ### 10 | Returns a new collection of the same (immutable) type that contains a subset of the items in the original array, as chosen by the method's argument, a block that takes an `id` and returns a `BOOL`. 11 | 12 | #### Example: #### 13 | ``` objective-c 14 | // Returns @[@3, @6, @9] 15 | [@[@1, @2, @3, @4, @5, @6, @7, @8, @9, @10] nn_filter:^(id item){ return (BOOL)!([item integerValue] % 3); }]; 16 | ``` 17 | 18 | ### nn_map: ### 19 | Returns a new collection of the same (immutable) type that contains new values based on the result of the block parameter, which takes an id and returns an id. 20 | 21 | Returning `nil` from the block is not supported. A separate filter step should be used first to remove unwanted items from the collection. 22 | 23 | #### Example: #### 24 | ``` objective-c 25 | // Returns @[@2, @4, @6, @8, @10, @12, @14, @16, @18, @20] 26 | [@[@1, @2, @3, @4, @5, @6, @7, @8, @9, @10] nn_map:^(id item){ return @([item integerValue] * 2); }]; 27 | ``` 28 | 29 | ### nn_reduce: ### 30 | Returns a reduction of the collection as defined by the block parameter, which takes an accumulator value (an `id` which starts as nil) and an item and returns the new value of the accumulator. 31 | 32 | #### Example: #### 33 | ``` objective-c 34 | // Returns @55 35 | [@[@1, @2, @3, @4, @5, @6, @7, @8, @9, @10] nn_reduce:^(id acc, id item){ return @([acc integerValue] + [item integerValue]); }]; 36 | ``` 37 | -------------------------------------------------------------------------------- /NNKit/Collections/_NNWeakArrayTombstone.h: -------------------------------------------------------------------------------- 1 | // 2 | // _NNWeakArrayTombstone.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/19/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | 18 | @interface _NNWeakArrayTombstone : NSObject 19 | 20 | + (_NNWeakArrayTombstone *)tombstoneWithTarget:(id)target; 21 | 22 | @property (nonatomic, readonly, weak) id target; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /NNKit/Collections/_NNWeakArrayTombstone.m: -------------------------------------------------------------------------------- 1 | // 2 | // _NNWeakArrayTombstone.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/19/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "_NNWeakArrayTombstone.h" 16 | 17 | 18 | @interface _NNWeakArrayTombstone () 19 | 20 | @property (nonatomic, readonly, assign) NSUInteger hash; 21 | 22 | @end 23 | 24 | 25 | @implementation _NNWeakArrayTombstone 26 | 27 | + (_NNWeakArrayTombstone *)tombstoneWithTarget:(id)target; 28 | { 29 | _NNWeakArrayTombstone *tombstone = [_NNWeakArrayTombstone new]; 30 | tombstone->_target = target; 31 | return tombstone; 32 | } 33 | 34 | @synthesize hash = _hash; 35 | - (NSUInteger)hash; 36 | { 37 | if (!self->_hash) { 38 | @synchronized(self) { 39 | if (!self->_hash) { 40 | id target = self.target; 41 | if (target) { 42 | self->_hash = [target hash]; 43 | } else { 44 | self->_hash = (uintptr_t)self; 45 | } 46 | } 47 | } 48 | } 49 | 50 | return self->_hash; 51 | } 52 | 53 | - (BOOL)isEqual:(id)object; 54 | { 55 | id target = self.target; 56 | return [target isEqual:object] ? YES : (uintptr_t)object == (uintptr_t)self; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /NNKit/Collections/_NNWeakSetEnumerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // _NNWeakSetEnumerator.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/19/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | 18 | @class NNWeakSet; 19 | 20 | 21 | @interface _NNWeakSetEnumerator : NSEnumerator 22 | 23 | - (instancetype)initWithWeakSet:(NNWeakSet *)set; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /NNKit/Collections/_NNWeakSetEnumerator.m: -------------------------------------------------------------------------------- 1 | // 2 | // _NNWeakSetEnumerator.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/19/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "_NNWeakSetEnumerator.h" 16 | 17 | #import "_NNWeakArrayTombstone.h" 18 | #import "NNWeakSet.h" 19 | 20 | 21 | @interface NNWeakSet (Private) 22 | 23 | @property (nonatomic, readonly, strong) NSSet *backingStore; 24 | 25 | @end 26 | 27 | 28 | @interface _NNWeakSetEnumerator () 29 | 30 | @property (nonatomic, readonly, strong) NSEnumerator *tombstoneEnumerator; 31 | 32 | @end 33 | 34 | 35 | @implementation _NNWeakSetEnumerator 36 | 37 | - (instancetype)initWithWeakSet:(NNWeakSet *)set; 38 | { 39 | if (!(self = [super init])) { return nil; } 40 | 41 | self->_tombstoneEnumerator = [set.backingStore.copy objectEnumerator]; 42 | 43 | return self; 44 | } 45 | 46 | - (NSArray *)allObjects; 47 | { 48 | NSMutableArray<_NNWeakArrayTombstone *> *result = [NSMutableArray new]; 49 | 50 | for (_NNWeakArrayTombstone *tombstone in self.tombstoneEnumerator.allObjects) { 51 | id obj = tombstone.target; 52 | if (obj) { 53 | [result addObject:obj]; 54 | } 55 | } 56 | 57 | return result; 58 | } 59 | 60 | - (id)nextObject; 61 | { 62 | id obj; 63 | _NNWeakArrayTombstone *tombstone; 64 | 65 | do { 66 | tombstone = self.tombstoneEnumerator.nextObject; 67 | obj = tombstone.target; 68 | } while (!obj && tombstone); 69 | 70 | return obj; 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /NNKit/Concurrency/NNDelegateProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNDelegateProxy.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | 18 | /*! 19 | * @class NNDelegateProxy 20 | * 21 | * @discussion 22 | * A proxy object ensuring that messages to the delegate are dispatched on the 23 | * main thread. 24 | * 25 | * Messages declared as oneway void are dispatched asynchronously, messages that 26 | * are optional are dispatched against nil if the delegate does not implement them. 27 | * Messages that are optional and non-void are a bad idea and you shouldn't use them. 28 | */ 29 | @interface NNDelegateProxy : NSProxy 30 | 31 | /*! 32 | * @method proxyWithDelegate:protocol: 33 | * 34 | * @discussion 35 | * Creates a new proxy for delegate conforming to 36 | * protocol. 37 | * 38 | * @param delegate 39 | * The object to receive delegate messages. 40 | * 41 | * @param protocol 42 | * The protocol to which delegate conforms. Can be NULL, 43 | * but shouldn't be. 44 | * 45 | * @result 46 | * Proxy to stand in for delegate for messages conforming to 47 | * protocol. 48 | */ 49 | + (id)proxyWithDelegate:(id)delegate protocol:(Protocol *)protocol; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /NNKit/Concurrency/NNDelegateProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNDelegateProxy.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NNDelegateProxy.h" 16 | 17 | #import "despatch.h" 18 | #import "runtime.h" 19 | 20 | 21 | @interface NNDelegateProxy () 22 | 23 | @property (readonly, weak) id delegate; 24 | @property (readonly, assign) Protocol *protocol; 25 | 26 | @end 27 | 28 | 29 | @implementation NNDelegateProxy 30 | 31 | + (id)proxyWithDelegate:(id)delegate protocol:(Protocol *)protocol; 32 | { 33 | if (protocol && delegate) { 34 | NSAssert([delegate conformsToProtocol:protocol], @"Object %@ does not conform to protocol %@", delegate, NSStringFromProtocol(protocol)); 35 | } 36 | 37 | NNDelegateProxy *proxy = [self alloc]; 38 | proxy->_delegate = delegate; 39 | proxy->_protocol = protocol; 40 | return proxy; 41 | } 42 | 43 | // Helper function to provide an autoreleasing reference to the delegate property 44 | - (id)strongDelegate; 45 | { 46 | id delegate = self.delegate; 47 | return delegate; 48 | } 49 | 50 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel; 51 | { 52 | return [self.strongDelegate methodSignatureForSelector:sel]; 53 | } 54 | 55 | - (void)forwardInvocation:(NSInvocation *)invocation; 56 | { 57 | # ifndef NS_BLOCK_ASSERTIONS 58 | { 59 | BOOL instanceMethod = YES; 60 | NSAssert(nn_selector_belongsToProtocol(invocation.selector, self.protocol, NULL, &instanceMethod) && instanceMethod, @"Instance method %@ not found in protocol %@", NSStringFromSelector(invocation.selector), NSStringFromProtocol(self.protocol)); 61 | } 62 | # endif 63 | 64 | id delegate = self.delegate; 65 | BOOL requiredMethod = NO; 66 | nn_selector_belongsToProtocol(invocation.selector, self.protocol, &requiredMethod, NULL); 67 | if (!requiredMethod && ![delegate respondsToSelector:invocation.selector]) { 68 | return; 69 | } 70 | 71 | if (invocation.methodSignature.isOneway) { 72 | dispatch_async(dispatch_get_main_queue(), ^{ 73 | [invocation invokeWithTarget:delegate]; 74 | }); 75 | } else { 76 | despatch_sync_main_reentrant(^{ 77 | [invocation invokeWithTarget:delegate]; 78 | }); 79 | } 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /NNKit/Concurrency/README.md: -------------------------------------------------------------------------------- 1 | NNKit Concurrency 2 | ================= 3 | 4 | NNKit provides tools to help make concurrency less of a headache, following the model of islands of serialization in a sea of concurrency. Since not all code can be expected to follow this model, NNKit also assumes that the only safe manner of passing messages between modules is on the main thread, and recievers are expected to get off the main thread as needed. 5 | 6 | NNDelegateProxy 7 | --------------- 8 | 9 | The `NNDelegateProxy` class provides a mechanism to ensure that all messages sent to a delegate are dispatched on the main thread. 10 | 11 | ### Example ### 12 | 13 | // You should already have a protocol for your use of the delegate pattern: 14 | @protocol MYClassDelegate 15 | - (void)objectCalledDelegateMethod:(id)obj; 16 | @end 17 | 18 | 19 | @interface MYClass : NSObject 20 | 21 | // And should already have a weak property for your delegate in your class declaration: 22 | @property (nonatomic, weak) id delegate; 23 | 24 | // Add a strong reference to a new property, the delegate proxy: 25 | @property (strong) id delegateProxy; 26 | 27 | @end 28 | 29 | @implementation MYClass 30 | 31 | - (instancetype)init; 32 | { 33 | if (!(self = [super init])) { return nil; } 34 | 35 | // Initialize the delegate proxy, feel free to set the delegate later if you don't have one handy. 36 | _delegateProxy = [NNDelegateProxy proxyWithDelegate:nil protocol:@protocol(MYClassDelegate)]; 37 | 38 | // … 39 | 40 | return self; 41 | } 42 | 43 | // If you have a writable delegate property, you'll need a custom delegate setter to ensure that the proxy gets updated: 44 | - (void)setDelegate:(id)delegate; 45 | { 46 | self->_delegate = delegate; 47 | 48 | ((NNDelegateProxy *)self.delegateProxy).delegate = delegate; 49 | // NOTE: A cast is necessary here because the delegateProxy property is typed id to retain as much static checking as possible elsewhere in your code, which fails here because the compiler doesn't realise that it's still an NNDelegateProxy under the hood. 50 | } 51 | 52 | - (void)method; 53 | { 54 | // Who cares how we got to where we are, or where that even is; when it's time to dispatch a delegate message just send it to the proxy: 55 | [self.delegateProxy objectCalledDelegateMethod:self]; 56 | } 57 | 58 | @end 59 | 60 | This proxy class was inspired by [Andy Matuschak](https://github.com/andymatuschak). 61 | -------------------------------------------------------------------------------- /NNKit/Hacks/NNStrongifiedProperties.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNStrongifiedProperties.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | /*! 18 | * @class NNStrongifiedProperties 19 | * 20 | * @discussion 21 | * Used with isa-swizzling to provide strong getters for weak properties in the 22 | * form of methods named strongProperty, where 23 | * Property is the unambiguous capitalized name of a weak 24 | * property on the object. 25 | */ 26 | @interface NNStrongifiedProperties : NSObject 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /NNKit/Hacks/NNStrongifiedProperties.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNStrongifiedProperties.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NNStrongifiedProperties.h" 16 | 17 | #import 18 | #import 19 | 20 | #import "nn_autofree.h" 21 | 22 | 23 | static SEL weakGetterForPropertyName(Class myClass, NSString *propertyName) { 24 | objc_property_t property = NULL; 25 | do { 26 | property = class_getProperty(myClass, [propertyName UTF8String]); 27 | if (property) { 28 | break; 29 | } 30 | } while ((myClass = class_getSuperclass(myClass))); 31 | 32 | if (!property) { 33 | return NO; 34 | } 35 | 36 | objc_property_attribute_t *attributes = nn_autofree(property_copyAttributeList(property, NULL)); 37 | BOOL propertyIsWeak = NO; 38 | SEL getter = NSSelectorFromString(propertyName); 39 | for (int i = 0; attributes[i].name != NULL; ++i) { 40 | if (!strcmp(attributes[i].name, "W")) { 41 | propertyIsWeak = YES; 42 | } 43 | if (!strncmp(attributes[i].name, "G", 1)) { 44 | getter = NSSelectorFromString([NSString stringWithFormat:@"%s", attributes[i].name + 1]); 45 | } 46 | if (!strcmp(attributes[i].name, "T") && strcmp(attributes[i].value, "@")) { 47 | return NO; 48 | } 49 | } 50 | attributes = NULL; 51 | 52 | if (!propertyIsWeak) { 53 | return NULL; 54 | } 55 | 56 | return getter; 57 | } 58 | 59 | 60 | static _Bool selectorIsStrongGetter(Class myClass, SEL sel, SEL *weakGetter) { 61 | NSString *selectorName = NSStringFromSelector(sel); 62 | NSRange prefixRange = [selectorName rangeOfString:@"strong"]; 63 | 64 | BOOL selectorIsStrongGetter = prefixRange.location == 0; 65 | 66 | if (!selectorIsStrongGetter) { 67 | if (weakGetter) { 68 | *weakGetter = NULL; 69 | } 70 | return NO; 71 | } 72 | 73 | // Also check uppercase in case it's an acronym? 74 | 75 | NSString *upperName = [selectorName substringFromIndex:prefixRange.length]; 76 | NSString *lowerName = [NSString stringWithFormat:@"%@%@", 77 | [[selectorName substringWithRange:(NSRange){prefixRange.length, 1}] lowercaseString], 78 | [selectorName substringFromIndex:prefixRange.length + 1]]; 79 | 80 | SEL lowerGetter = weakGetterForPropertyName(myClass, lowerName); 81 | SEL upperGetter = weakGetterForPropertyName(myClass, upperName); 82 | 83 | if (lowerGetter && upperGetter) { 84 | // Selector is ambiguous, do not support synthesizing a strongified getter for this property. 85 | return NO; 86 | } 87 | 88 | if (!lowerGetter && !upperGetter) { 89 | return NO; 90 | } 91 | 92 | *weakGetter = lowerGetter ?: upperGetter; 93 | 94 | return YES; 95 | } 96 | 97 | 98 | static id strongGetterIMP(id self, SEL _cmd) { 99 | SEL weakSelector = NULL; 100 | 101 | # ifndef NS_BLOCK_ASSERTIONS 102 | { 103 | BOOL sane = selectorIsStrongGetter([self class], _cmd, &weakSelector); 104 | NSAssert(sane, @"Selector %@ does not represent a valid strongifying getter method", NSStringFromSelector(_cmd)); 105 | } 106 | # endif 107 | 108 | if (!weakSelector) { return nil; } 109 | 110 | id strongRef = objc_msgSend(self, weakSelector); 111 | 112 | return strongRef; 113 | } 114 | 115 | 116 | @implementation NNStrongifiedProperties 117 | 118 | + (BOOL)resolveInstanceMethod:(SEL)sel; 119 | { 120 | SEL weakSelector = NULL; 121 | if (selectorIsStrongGetter(self, sel, &weakSelector)) { 122 | Method weakGetter = class_getInstanceMethod(self, weakSelector); 123 | const char *getterEncoding = method_getTypeEncoding(weakGetter); 124 | class_addMethod(self, sel, (IMP)strongGetterIMP, getterEncoding); 125 | return YES; 126 | } 127 | 128 | return NO; 129 | } 130 | 131 | @end 132 | -------------------------------------------------------------------------------- /NNKit/Hacks/README.md: -------------------------------------------------------------------------------- 1 | NNKit Hacks 2 | =========== 3 | 4 | This portion of NNKit contains things that, while well-tested and reliable, shouldn't pass a sane person's test for acceptable complexity per unit utility. 5 | 6 | Automatically Freed C Buffers 7 | ----------------------------- 8 | 9 | Have a complicated function with a lot of logic and early returns? Avoid the possibility of forgetting to free your buffers with `nn_autofree`! 10 | 11 | @autoreleasepool { 12 | int *foo = nn_autofree(malloc(size)); 13 | } 14 | 15 | Don't forget, you're still in charge of NULLing your pointers, so it's easiest if you create your own autorelease pool, which will also create an extra scope for the buffer's pointer which it can't escape. 16 | 17 | Strongified Property Access 18 | --------------------------- 19 | 20 | Weak properties can be accessed with autoreleasing getters by either inheriting from or isa-swizzling `NNStrongifiedProperties`. 21 | 22 | @interface WeakDemo : NNStrongifiedProperties 23 | @property (weak) id foo; 24 | @end 25 | 26 | @interface WeakDemo (StrongAccessors) 27 | - (id)strongFoo; 28 | @end 29 | 30 | @implementation WeakDemo 31 | @end 32 | 33 | int main() { 34 | // Whenever you need an autoreleased reference to foo: 35 | [[WeakDemo new] strongFoo] 36 | } 37 | 38 | If you already inherit from a more useful class, this behaviour can be "learned" by an existing object by using isa swizzling: 39 | 40 | @interface WeakDemo : NSObject 41 | @property (weak) id foo; 42 | @end 43 | 44 | @interface WeakDemo (StrongAccessors) 45 | - (id)strongFoo; 46 | @end 47 | 48 | @implementation WeakDemo 49 | @end 50 | 51 | int main() { 52 | id obj = [WeakDemo new]; 53 | nn_object_swizzleIsa(obj, [NNStrongifiedProperties class]); 54 | 55 | // Whenever you need an autoreleased reference to foo: 56 | [obj strongFoo] 57 | } 58 | 59 | This hack was concieved after enabling the `-Wreceiver-is-weak` warning in clang and learning about the race condition it polices. 60 | -------------------------------------------------------------------------------- /NNKit/Hacks/memoize.h: -------------------------------------------------------------------------------- 1 | // 2 | // memoize.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 06/23/15. 6 | // Copyright © 2015 Scott Perry. All rights reserved. 7 | // 8 | 9 | #define NNMemoize(block) _NNMemoize(self, _cmd, block) 10 | 11 | id _NNMemoize(id self, SEL _cmd, id (^block)()); 12 | -------------------------------------------------------------------------------- /NNKit/Hacks/memoize.m: -------------------------------------------------------------------------------- 1 | // 2 | // memoize.c 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 06/23/15. 6 | // Copyright © 2015 Scott Perry. All rights reserved. 7 | // 8 | 9 | #import "memoize.h" 10 | 11 | #import 12 | 13 | 14 | id _NNMemoize(id self, SEL _cmd, id (^block)()) { 15 | id result; 16 | void *key = (void *)((uintptr_t)(__bridge void *)self ^ (uintptr_t)(void *)_cmd ^ (uintptr_t)&_NNMemoize); 17 | 18 | @synchronized(self) { 19 | result = objc_getAssociatedObject(self, key); 20 | if (!result) { 21 | result = block(); 22 | objc_setAssociatedObject(self, key, result, OBJC_ASSOCIATION_COPY_NONATOMIC); 23 | } 24 | } 25 | 26 | return result; 27 | } 28 | -------------------------------------------------------------------------------- /NNKit/Hacks/nn_autofree.h: -------------------------------------------------------------------------------- 1 | // 2 | // nn_autofree.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/09/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | /*! 18 | * @function nn_autofree 19 | * 20 | * @abstract 21 | * Adds the buffer to the current autorelease pool. 22 | * 23 | * @discussion 24 | * This function ties the buffer's lifetime to that of an object added to the 25 | * current autorelease pool, causing it to be freed when the pool is drained. 26 | * 27 | * @param ptr 28 | * The buffer to add to the current autorelease pool. 29 | */ 30 | void *nn_autofree(void *ptr); 31 | -------------------------------------------------------------------------------- /NNKit/Hacks/nn_autofree.m: -------------------------------------------------------------------------------- 1 | // 2 | // nn_autofree.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/09/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | // Requires -fno-objc-arc 15 | // 16 | 17 | #import "nn_autofree.h" 18 | 19 | void *nn_autofree(void *ptr) 20 | { 21 | if (ptr) { 22 | [NSData dataWithBytesNoCopy:ptr length:1 freeWhenDone:YES]; 23 | } 24 | return ptr; 25 | } 26 | -------------------------------------------------------------------------------- /NNKit/NNKit Mac-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /NNKit/NNKit iOS-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /NNKit/NNKit-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | net.numist.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSHumanReadableCopyright 26 | Copyright © 2013 Scott Perry. All rights reserved. 27 | NSPrincipalClass 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /NNKit/NNKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNKit.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | #import 18 | #import 19 | #import 20 | #import 21 | #import 22 | 23 | #import 24 | #import 25 | #import 26 | #import 27 | #import 28 | #import 29 | #import 30 | #import 31 | #import 32 | #import 33 | 34 | #import 35 | -------------------------------------------------------------------------------- /NNKit/NNKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNKit.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NNKit.h" 16 | -------------------------------------------------------------------------------- /NNKit/Services/NNService+Protected.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNService+Protected.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 10/17/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | 18 | @class NNMultiDispatchManager; 19 | 20 | 21 | @interface NNService (Protected) 22 | 23 | @property (atomic, readonly, strong) NNMultiDispatchManager *subscriberDispatcher; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /NNKit/Services/NNService.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNService.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 10/17/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | 18 | /*! 19 | * @enum NNServiceType 20 | * 21 | * @discussion 22 | * Represents the type of a service. Persistent services are started once all of 23 | * their dependencies have been started, on-demand services are started when an 24 | * object subscribes to the service. 25 | */ 26 | typedef NS_ENUM(uint8_t, NNServiceType) { 27 | NNServiceTypePersistent, 28 | NNServiceTypeOnDemand, 29 | }; 30 | 31 | 32 | /*! 33 | * @class NNService 34 | * 35 | * @discussion 36 | * Discuss. 37 | */ 38 | @interface NNService : NSObject 39 | 40 | /*! 41 | * @method sharedService 42 | * 43 | * @discussion 44 | * Service singleton accessor. 45 | * 46 | * @result 47 | * Singleton object for the service. 48 | */ 49 | + (instancetype)sharedService; 50 | 51 | /*! 52 | * @method serviceType 53 | * 54 | * @discussion 55 | * The type of the service. Must be overridden. Valid services must not return NNServiceTypeNone. 56 | */ 57 | + (NNServiceType)serviceType; 58 | 59 | /*! 60 | * @method dependencies 61 | * 62 | * @discussion 63 | * Services are not started until their dependencies have all been started first. 64 | * This means multiple services can be made on-demand by having a root service 65 | * that is on-demand and multiple dependant services that are persistent. 66 | * 67 | * @result 68 | * Returns a set of Classes that this service depends on to run. 69 | * Default implementation returns nil; 70 | */ 71 | + (NSSet *)dependencies; 72 | 73 | /*! 74 | * @method subscriberProtocol 75 | * 76 | * @discussion 77 | * Protocol for subscribers to conform to. Default implementation returns @protocol(NSObject). 78 | */ 79 | + (Protocol *)subscriberProtocol; 80 | 81 | /*! 82 | * @method startService 83 | * 84 | * @discussion 85 | * Called when the service is started. 86 | */ 87 | - (void)startService __attribute__((objc_requires_super)); 88 | 89 | /*! 90 | * @method stopService 91 | * 92 | * @discussion 93 | * Called when the service is stopped. 94 | */ 95 | - (void)stopService __attribute__((objc_requires_super)); 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /NNKit/Services/NNService.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNService.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 10/17/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NNService+Protected.h" 16 | 17 | #import "NNServiceManager.h" 18 | #import "NNMultiDispatchManager.h" 19 | 20 | 21 | @interface NNService () 22 | 23 | @property (atomic, readonly, strong) NNMultiDispatchManager *subscriberDispatcher; 24 | 25 | @end 26 | 27 | 28 | @implementation NNService 29 | 30 | + (instancetype)sharedService; 31 | { 32 | static NSMutableDictionary *instances; 33 | 34 | static dispatch_once_t onceToken; 35 | dispatch_once(&onceToken, ^{ 36 | instances = [NSMutableDictionary new]; 37 | }); 38 | 39 | NNService *result; 40 | 41 | @synchronized(instances) { 42 | result = [instances objectForKey:self]; 43 | if (!result) { 44 | result = [self new]; 45 | [instances setObject:result forKey:self]; 46 | } 47 | } 48 | 49 | return result; 50 | } 51 | 52 | + (NNServiceType)serviceType; 53 | { 54 | return NNServiceTypePersistent; 55 | } 56 | 57 | + (NSSet *)dependencies; 58 | { 59 | return [NSSet set]; 60 | } 61 | 62 | + (Protocol *)subscriberProtocol; 63 | { 64 | return @protocol(NSObject); 65 | } 66 | 67 | - (id)init; 68 | { 69 | if (!(self = [super init])) { return nil; } 70 | 71 | self->_subscriberDispatcher = [[NNMultiDispatchManager alloc] initWithProtocol:self.class.subscriberProtocol]; 72 | 73 | return self; 74 | } 75 | 76 | - (void)startService; 77 | { 78 | NSAssert([[NSThread currentThread] isMainThread], @"Service must be started on the main thread"); 79 | } 80 | 81 | - (void)stopService; 82 | { 83 | NSAssert([[NSThread currentThread] isMainThread], @"Service must be stopped on the main thread"); 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /NNKit/Services/NNServiceManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNServiceManager.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 10/17/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | #import 18 | 19 | 20 | /*! 21 | * @class NNServiceManager 22 | * 23 | * @discussion 24 | * Manages the running state of registered services based on their dependencies and subscriptions. 25 | */ 26 | @interface NNServiceManager : NSObject 27 | 28 | /*! 29 | * @method sharedManager 30 | * 31 | * @discussion 32 | * Singleton service manager accessor. 33 | * 34 | * @result 35 | * Global shared service manager object. 36 | */ 37 | + (NNServiceManager *)sharedManager; 38 | 39 | /*! 40 | * @method registerAllPossibleServices 41 | * 42 | * @abstract 43 | * Registers all subclasses of NNService known to the runtime with this service manager. 44 | */ 45 | - (void)registerAllPossibleServices; 46 | 47 | /*! 48 | * @method registerService: 49 | * 50 | * @discussion 51 | * Registers service with the service manager, and starts it if its 52 | * dependencies have all been met. 53 | * 54 | * @param service 55 | * The Class of a service to be registered with this service manager. 56 | */ 57 | - (void)registerService:(Class)service; 58 | 59 | /*! 60 | * @method instanceForService 61 | * 62 | * @discussion 63 | * Accessor method to get the instance of a service class, started or not, 64 | * managed by this service manager. 65 | * 66 | * @result 67 | * An instance of the requested service. nil if the service has 68 | * not been registered with this service manager. 69 | */ 70 | - (NNService *)instanceForService:(Class)service; 71 | 72 | /*! 73 | * @method addObserver:forService: 74 | * 75 | * @discussion 76 | * Adds an observer to the service's notification group. Observers must conform 77 | * to the protocol specified by the service's subscriberProtocol. 78 | * 79 | * Observers are automatically removed if they are deallocated while observing 80 | * the service. 81 | * 82 | * @param observer 83 | * The object that is interested in the service's events. The observer must 84 | * conform to the service's subscriberProtocol. 85 | * 86 | * @param service 87 | * The service that the caller wishes to observe. 88 | */ 89 | - (void)addObserver:(id)observer forService:(Class)service; 90 | 91 | /*! 92 | * @method removeSubscriber:forService: 93 | * 94 | * @discussion 95 | * Removes the observer from the service's notification group. 96 | * 97 | * @param observer 98 | * The object that is no longer interested in the service. 99 | * 100 | * @param service 101 | * The service from which the caller is unsubscribing. 102 | */ 103 | - (void)removeObserver:(id)observer forService:(Class)service; 104 | 105 | /*! 106 | * @method addSubscriber:forService:: 107 | * 108 | * @discussion 109 | * Increments the service's subscriber count. Services that are run on demand 110 | * will be started by calls to this method if there were not other subscribers. 111 | * 112 | * Subscribers are automatically removed if they are deallocated while 113 | * subscribed to the service. 114 | * 115 | * @param subscriber 116 | * The object that is interested in the service's events. As with observers, the 117 | * subscriber must conform to the service's subscriberProtocol. 118 | * 119 | * @param service 120 | * The service to which the caller is subscribing. 121 | */ 122 | - (void)addSubscriber:(id)subscriber forService:(Class)service; 123 | 124 | /*! 125 | * @method removeSubscriber:forService: 126 | * 127 | * @discussion 128 | * Decrements the service's subscriber count. Services that are run on demand 129 | * will be stopped by calls to this method when the subscriber count reaches zero. 130 | * 131 | * @param subscriber 132 | * The object that is no longer interested in the service. 133 | * 134 | * @param service 135 | * The service from which the caller is unsubscribing. 136 | */ 137 | - (void)removeSubscriber:(id)subscriber forService:(Class)service; 138 | 139 | @end 140 | -------------------------------------------------------------------------------- /NNKit/Services/README.md: -------------------------------------------------------------------------------- 1 | NNKit Services 2 | ============== 3 | 4 | NNKit provides a framework for services running in your application, including dependency management, subscriber and observer dispatch, automatic starting and stopping of services based on subscriptions, and an easy mechanism to register all the services in your application in your app delegate, keeping your code small and readable. 5 | 6 | What makes a service 7 | -------------------- 8 | 9 | Services are classes that inherit from `NNService` and respond to `serviceType` with a type other than `NNServiceTypeNone`. The other two types are: 10 | 11 | * `NNServiceTypePersistent`: the service will run as long as its dependencies (if any) are running. 12 | * `NNServiceTypeOnDemand`: the service will run as long as its dependencies (if any) are running and at least one object has subscribed to the service using the service manager's `addSubscriber:forService:` method. 13 | 14 | Dependency management 15 | --------------------- 16 | 17 | Service dependencies are defined by the `dependencies` method, which returns an NSSet of class objects. Dependencies that are not already known to the service manager will be added automatically if possible. 18 | 19 | Subscriber (and observer) message dispatch 20 | ------------------------------------------ 21 | 22 | Services whose instances dispatch messages to their subscribers must respond to `subscriberProtocol` with the appropriate protocol. Subscribers must conform to this protocol and will be checked at runtime. Sending a message to subscribers can be accomplished with code resembling the following: 23 | 24 | ``` objective-c 25 | - (void)sendMessage; 26 | { 27 | [(id)self.subscriberDispatcher serviceWillDoThing:self]; 28 | } 29 | ``` 30 | 31 | Convenience hacks 32 | ----------------- 33 | 34 | Automatically adding all services known to the runtime is accomplished by calling `registerAllPossibleServices` on a service manager, preferably the `sharedManager`. 35 | 36 | Would you like to know more? 37 | ---------------------------- 38 | 39 | For a deeper dive into services, check out the [unit tests](https://github.com/numist/NNKit/blob/master/NNKitTests/NNServiceTests.m). 40 | -------------------------------------------------------------------------------- /NNKit/Swizzling/NNISASwizzledObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNISASwizzledObject.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 02/07/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | 18 | @protocol NNISASwizzledObject @end 19 | 20 | 21 | @interface NNISASwizzledObject : NSObject 22 | 23 | + (void)prepareObjectForSwizzling:(NSObject *)anObject; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /NNKit/Swizzling/NNISASwizzledObject.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNISASwizzledObject.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 02/07/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NNISASwizzledObject.h" 16 | 17 | #import 18 | 19 | #import "nn_isaSwizzling_Private.h" 20 | 21 | 22 | static void *_NNSwizzleSuperclassKey; 23 | 24 | 25 | __attribute__((constructor)) 26 | static void nn_isaSwizzling_init() { 27 | arc4random_buf(&_NNSwizzleSuperclassKey, sizeof(_NNSwizzleSuperclassKey)); 28 | } 29 | 30 | 31 | @implementation NNISASwizzledObject 32 | 33 | #pragma mark Swizzler runtime support. 34 | 35 | + (void)prepareObjectForSwizzling:(NSObject *)anObject; 36 | { 37 | // Cache the original value of -class so the swizzled object can lie about itself later. 38 | objc_setAssociatedObject(anObject, _NNSwizzleSuperclassKey, [anObject class], OBJC_ASSOCIATION_ASSIGN); 39 | } 40 | 41 | #pragma mark Private swizzled methods 42 | 43 | - (Class)_swizzler_actualClass 44 | { 45 | return object_getClass(self); 46 | } 47 | 48 | #pragma mark Swizzled object overrides 49 | 50 | - (Class)class 51 | { 52 | Class superclass = objc_getAssociatedObject(self, _NNSwizzleSuperclassKey); 53 | 54 | if (!superclass) { 55 | NSLog(@"ERROR: couldn't find stashed superclass for swizzled object, falling back to parent class—if you're using KVO, this might break everything!"); 56 | return class_getSuperclass(object_getClass(self)); 57 | } 58 | 59 | return superclass; 60 | } 61 | 62 | - (BOOL)conformsToProtocol:(Protocol *)aProtocol 63 | { 64 | return [[self _swizzler_actualClass] conformsToProtocol:aProtocol]; 65 | } 66 | 67 | - (BOOL)respondsToSelector:(SEL)aSelector 68 | { 69 | return [[self _swizzler_actualClass] instancesRespondToSelector:aSelector]; 70 | } 71 | 72 | - (BOOL)isKindOfClass:(Class)aClass; 73 | { 74 | if (nn_alreadySwizzledObjectWithSwizzlingClass(self, aClass)) { 75 | return YES; 76 | } 77 | 78 | return [super isKindOfClass:aClass]; 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /NNKit/Swizzling/README.md: -------------------------------------------------------------------------------- 1 | NNKit Swizzling 2 | =============== 3 | 4 | NNKit provides robust, general-purpose swizzling utilities. 5 | 6 | If you think this is a good idea, you should probably stop and make sure that your needs are not a symptom of more severe architectural problems. 7 | 8 | Isa Swizzling 9 | ------------- 10 | 11 | Robust isa swizzling is provided using the `nn_object_swizzleIsa` function. The following conditions must be met: 12 | 13 | * The object is an instance of the swizzling class's superclass, or a subclass of the swizzling class's superclass. 14 | * The swizzling class does not add any ivars or non-dynamic properties. 15 | 16 | An object has been swizzled by a class if it responds YES to `isKindOfClass:` with the swizzling class as an argument. Protocols can also be used, and queried using `conformsToProtocol:`, as usual. 17 | 18 | To avoid any confusion, your swizzling class should not implement an allocator or initializer. They will never be called for swizzled objects. 19 | 20 | ### Usage ### 21 | 22 | First, you'll need to define your swizzling class. For example: 23 | 24 | @interface MYClass : NSObject 25 | @property (nonatomic, readonly) NSUInteger random; 26 | - (void)duck; 27 | @end 28 | 29 | @implementation MYClass 30 | - (NSUInteger)random { return 7; } 31 | - (void)duck { NSLog(@"quack!"); } 32 | - (void)dog { NSLog(@"woof!"); } 33 | @end 34 | 35 | To swizzle your object and use its newfound functionality, just call `nn_object_swizzleIsa`: 36 | 37 | #import 38 | 39 | @implementation MYCode 40 | - (void)main { 41 | NSObject *bar = [[NSObject alloc] init]; 42 | nn_object_swizzleIsa(bar, [MYClass class]); 43 | if ([bar isKindOfClass:[MYClass class]]) { 44 | [(MYClass *)bar duck]; 45 | } 46 | if ([bar conformsToProtocol:@protocol(NSDog)]) { 47 | [(id)bar dog]; 48 | } 49 | } 50 | @end 51 | 52 | See the tests for more explicit examples of what is supposed to work and what is supposed to be an error. 53 | 54 | Credits 55 | ======= 56 | 57 | If not for [Rob Rix](https://github.com/robrix/), the Swizzling component of NNKit would not exist. Which probably would have been a good thing. 58 | -------------------------------------------------------------------------------- /NNKit/Swizzling/nn_isaSwizzling.h: -------------------------------------------------------------------------------- 1 | // 2 | // nn_isaSwizzling.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 02/07/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | 18 | /*! 19 | * @function nn_object_swizzleIsa 20 | * 21 | * @abstract 22 | * Swizzles the class of obj to a dynamic subclass including the 23 | * qualities of swizzlingClass. 24 | * 25 | * @discussion 26 | * This function implements generic isa swizzling assuming the following conditions are met: 27 | * • A protocol with the same name as swizzlingClass exists and is 28 | * implemented by swizzlingClass. 29 | * • obj is an instance of the swizzlingClass's 30 | * superclass, or a subclass of swizzlingClass's superclass. 31 | * • swizzlingClass does not add any ivars or non-dynamic properties. 32 | * 33 | * An object has been swizzled by a class if it conforms to that class's 34 | * complementing protocol, allowing you to cast the object (after checking!) to 35 | * a type that explicitly implements the protocol. 36 | * 37 | * For more details about use, see the tests in nn_isaSwizzlingTests.m. 38 | * 39 | * @param obj 40 | * The object to be swizzled. 41 | * 42 | * @param swizzlingClass 43 | * The class to apply to obj. 44 | */ 45 | BOOL nn_object_swizzleIsa(id obj, Class swizzlingClass) __attribute__((nonnull(1, 2))); 46 | -------------------------------------------------------------------------------- /NNKit/Swizzling/nn_isaSwizzling.m: -------------------------------------------------------------------------------- 1 | // 2 | // nn_isaSwizzling.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 02/07/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "nn_isaSwizzling_Private.h" 16 | 17 | #import 18 | 19 | #import "NNISASwizzledObject.h" 20 | #import "nn_autofree.h" 21 | 22 | 23 | static NSString *_prefixForSwizzlingClass(Class aClass) 24 | { 25 | return [NSString stringWithFormat:@"SwizzledWith%s_", class_getName(aClass)]; 26 | } 27 | 28 | static NSString * _classNameForObjectWithSwizzlingClass(id anObject, Class aClass) 29 | { 30 | return [NSString stringWithFormat:@"%@%s", _prefixForSwizzlingClass(aClass), object_getClassName(anObject)]; 31 | } 32 | 33 | #pragma mark Class copying functions 34 | 35 | static void _class_addMethods(Class targetClass, Method *methods) { 36 | Method method; 37 | for (NSUInteger i = 0; methods && (method = methods[i]); i++) { 38 | // targetClass is a brand new shiny class, so this should never fail because it already implements a method (even though its superclass(es) might). 39 | if(!class_addMethod(targetClass, method_getName(method), method_getImplementation(method), method_getTypeEncoding(method))) { 40 | // numist/NNKit#17 41 | NSLog(@"Warning: Replacing method %@ previously defined by class %@?", NSStringFromSelector(method_getName(method)), NSStringFromClass(targetClass)); 42 | class_replaceMethod(targetClass, method_getName(method), method_getImplementation(method), method_getTypeEncoding(method)); 43 | } 44 | } 45 | } 46 | 47 | static void _class_addClassMethodsFromClass(Class targetClass, Class source) 48 | { 49 | _class_addMethods(object_getClass(targetClass), nn_autofree(class_copyMethodList(object_getClass(source), NULL))); 50 | } 51 | 52 | static void _class_addInstanceMethodsFromClass(Class targetClass, Class source) 53 | { 54 | _class_addMethods(targetClass, nn_autofree(class_copyMethodList(source, NULL))); 55 | } 56 | 57 | static void _class_addProtocolsFromClass(Class targetClass, Class aClass) 58 | { 59 | Protocol * __unsafe_unretained *protocols = (Protocol * __unsafe_unretained *)nn_autofree(class_copyProtocolList(aClass, NULL)); 60 | Protocol __unsafe_unretained *protocol; 61 | 62 | for (NSUInteger i = 0; protocols && (protocol = protocols[i]); i++) { 63 | // targetClass is a brand new shiny class, so this should never fail because it already conforms to a protocol (even though its superclass(es) might). 64 | if (!class_addProtocol(targetClass, protocol)) { 65 | NSLog(@"Warning: class %@ already conforms to protocol %@?", NSStringFromClass(targetClass), NSStringFromProtocol(protocol)); 66 | } 67 | } 68 | } 69 | 70 | static void _class_addPropertiesFromClass(Class targetClass, Class aClass) 71 | { 72 | objc_property_t *properties = nn_autofree(class_copyPropertyList(aClass, NULL)); 73 | objc_property_t property; 74 | 75 | for (NSUInteger i = 0; properties && (property = properties[i]); i++) { 76 | unsigned attributeCount; 77 | objc_property_attribute_t *attributes = nn_autofree(property_copyAttributeList(property, &attributeCount)); 78 | 79 | // targetClass is a brand new shiny class, so this should never fail because it already has certain properties (even though its superclass(es) might). 80 | if(!class_addProperty(targetClass, property_getName(property), attributes, attributeCount)) { 81 | // numist/NNKit#17 82 | NSLog(@"Warning: Replacing property %s previously defined by class %@?", property_getName(property), NSStringFromClass(targetClass)); 83 | class_replaceProperty(targetClass, property_getName(property), attributes, attributeCount); 84 | } 85 | } 86 | } 87 | 88 | #pragma mark Swizzling safety checks 89 | 90 | static void _class_checkForNonDynamicProperties(Class aClass) 91 | { 92 | objc_property_t *properties = nn_autofree(class_copyPropertyList(aClass, NULL)); 93 | 94 | for (unsigned i = 0; properties && properties[i]; i++) { 95 | objc_property_attribute_t *attributes = nn_autofree(property_copyAttributeList(properties[i], NULL)); 96 | 97 | for (unsigned j = 0; attributes && attributes[j].name; j++) { 98 | if (!strcmp(attributes[j].name, "D")) { // The property is dynamic (@dynamic). 99 | NSLog(@"Warning: Swizzling class %s contains non-dynamic property %s", class_getName(aClass), property_getName(properties[i])); 100 | } 101 | } 102 | } 103 | } 104 | 105 | static BOOL _class_containsIvars(Class aClass) 106 | { 107 | unsigned ivars; 108 | free(class_copyIvarList(aClass, &ivars)); 109 | return ivars != 0; 110 | } 111 | 112 | #pragma mark Isa swizzling 113 | 114 | static Class _targetClassForObjectWithSwizzlingClass(id anObject, Class aClass) 115 | { 116 | Class targetClass = objc_getClass(_classNameForObjectWithSwizzlingClass(anObject, aClass).UTF8String); 117 | 118 | if (!targetClass) { 119 | BOOL success = YES; 120 | const char *swizzlingClassName = class_getName(aClass); 121 | 122 | Class sharedAncestor = class_getSuperclass(aClass); 123 | if (![anObject isKindOfClass:sharedAncestor]) { 124 | NSLog(@"Target object %@ must be a subclass of %@ to be swizzled with class %s.", anObject, sharedAncestor, swizzlingClassName); 125 | success = NO; 126 | } 127 | 128 | _class_checkForNonDynamicProperties(aClass); 129 | 130 | if (_class_containsIvars(aClass)) { 131 | NSLog(@"Swizzling class %s cannot contain ivars not inherited from its superclass", swizzlingClassName); 132 | success = NO; 133 | } 134 | 135 | if (!success) { 136 | return Nil; 137 | } 138 | 139 | targetClass = objc_allocateClassPair(object_getClass(anObject), _classNameForObjectWithSwizzlingClass(anObject, aClass).UTF8String, 0); 140 | _class_addClassMethodsFromClass(targetClass, aClass); 141 | _class_addInstanceMethodsFromClass(targetClass, aClass); 142 | _class_addProtocolsFromClass(targetClass, aClass); 143 | _class_addPropertiesFromClass(targetClass, aClass); 144 | 145 | objc_registerClassPair(targetClass); 146 | } 147 | 148 | return targetClass; 149 | } 150 | 151 | static BOOL _object_swizzleIsa(id anObject, Class aClass) 152 | { 153 | assert(!nn_alreadySwizzledObjectWithSwizzlingClass(anObject, aClass)); 154 | 155 | Class targetClass = _targetClassForObjectWithSwizzlingClass(anObject, aClass); 156 | 157 | if (!targetClass) { 158 | return NO; 159 | } 160 | 161 | object_setClass(anObject, targetClass); 162 | 163 | return YES; 164 | } 165 | 166 | #pragma mark Privately-exported functions 167 | 168 | BOOL nn_alreadySwizzledObjectWithSwizzlingClass(id anObject, Class aClass) 169 | { 170 | NSString *classPrefix = _prefixForSwizzlingClass(aClass); 171 | 172 | for (Class candidate = object_getClass(anObject); candidate; candidate = class_getSuperclass(candidate)) { 173 | if ([[NSString stringWithUTF8String:class_getName(candidate)] hasPrefix:classPrefix]) { 174 | return YES; 175 | } 176 | } 177 | 178 | return NO; 179 | } 180 | 181 | #pragma mark Publicly-exported funtions 182 | 183 | BOOL nn_object_swizzleIsa(id anObject, Class aClass) { 184 | BOOL success = YES; 185 | 186 | @autoreleasepool { 187 | // Bootstrap the object with the necessary lies, like overriding -class to report the original class. 188 | if (!nn_alreadySwizzledObjectWithSwizzlingClass(anObject, [NNISASwizzledObject class])) { 189 | [NNISASwizzledObject prepareObjectForSwizzling:anObject]; 190 | 191 | success = _object_swizzleIsa(anObject, [NNISASwizzledObject class]); 192 | } 193 | 194 | if (success && !nn_alreadySwizzledObjectWithSwizzlingClass(anObject, aClass)) { 195 | success = _object_swizzleIsa(anObject, aClass); 196 | } 197 | } 198 | 199 | return success; 200 | } 201 | -------------------------------------------------------------------------------- /NNKit/Swizzling/nn_isaSwizzling_Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // nn_isaSwizzling_Private.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 02/08/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | BOOL nn_alreadySwizzledObjectWithSwizzlingClass(id anObject, Class aClass) __attribute__((nonnull(1, 2))); 18 | 19 | -------------------------------------------------------------------------------- /NNKit/macros.h: -------------------------------------------------------------------------------- 1 | // 2 | // macros.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 02/25/14. 6 | // Copyright © 2014 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #pragma once 16 | 17 | // Compile-time key path additions 18 | #define NNKeyPath(object, keyPath) ({ if (NO) { (void)((object).keyPath); } @#keyPath; }) 19 | #define NNSelfKeyPath(keyPath) NNKeyPath(self, keyPath) 20 | #define NNTypedKeyPath(ObjectClass, keyPath) NNKeyPath(((ObjectClass *)nil), keyPath) 21 | #define NNProtocolKeyPath(Protocol, keyPath) NNKeyPath(((id )nil), keyPath) 22 | 23 | // Compile-time selector additions 24 | #define NNSelector(object, selectorName) ({ if (NO) { (void)[(object) selectorName]; } @selector(selectorName); }) 25 | #define NNSelfSelector(selectorName) NNSelector(self, selectorName) 26 | #define NNTypedSelector(ObjectClass, selectorName) NNSelector(((ObjectClass *)nil), selectorName) 27 | #define NNProtocolSelector(Protocol, selectorName) NNSelector(((id )nil), selectorName) 28 | 29 | #define NNSelector1(object, selectorName) ({ if (NO) { (void)[(object) selectorName nil]; } @selector(selectorName); }) 30 | #define NNSelfSelector1(selectorName) NNSelector1(self, selectorName) 31 | #define NNTypedSelector1(ObjectClass, selectorName) NNSelector1(((ObjectClass *)nil), selectorName) 32 | #define NNProtocolSelector1(Protocol, selectorName) NNSelector1(((id )nil), selectorName) 33 | -------------------------------------------------------------------------------- /NNKitTests/NNCleanupProxyTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNCleanupProxyTests.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/19/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | #import 18 | #import 19 | 20 | 21 | @protocol NNCleanupProxyTestProtocol 22 | - (BOOL)someKindOfSelectorWithObject:(id)foo; 23 | @end 24 | 25 | 26 | @protocol NNCleanupProxyTestProtocol2 27 | - (BOOL)someKindOfSelectorWithObject2:(id)foo; 28 | @end 29 | 30 | 31 | @interface NNCleanupProxyTestClass : NSObject 32 | @end 33 | @implementation NNCleanupProxyTestClass 34 | - (NSUInteger)hash; 35 | { 36 | return (uintptr_t)self; 37 | } 38 | 39 | - (BOOL)isEqual:(NSObject *)object; 40 | { 41 | return self.hash == object.hash; 42 | } 43 | 44 | - (BOOL)someKindOfSelectorWithObject:(id)foo; 45 | { 46 | return YES; 47 | } 48 | - (BOOL)someKindOfSelectorWithObject2:(id)foo; 49 | { 50 | return YES; 51 | } 52 | - (BOOL)anotherKindofSelectorWithObject:(id)foo; 53 | { 54 | return YES; 55 | } 56 | @end 57 | 58 | 59 | @interface NNCleanupProxyTests : XCTestCase 60 | 61 | @end 62 | 63 | 64 | @implementation NNCleanupProxyTests 65 | 66 | - (void)testCleanupBlock; 67 | { 68 | __block BOOL cleanupComplete = NO; 69 | @autoreleasepool { 70 | __attribute__((objc_precise_lifetime)) id foo = [NSObject new]; 71 | NNCleanupProxy *proxy = [NNCleanupProxy cleanupProxyForTarget:foo withKey:(uintptr_t)foo]; 72 | proxy.cleanupBlock = ^{ cleanupComplete = YES; }; 73 | } 74 | XCTAssertTrue(cleanupComplete, @""); 75 | } 76 | 77 | - (void)testProtocolMethodDispatch; 78 | { 79 | @autoreleasepool { 80 | __attribute__((objc_precise_lifetime)) id foo = [NNCleanupProxyTestClass new]; 81 | id proxy = (id)[NNCleanupProxy cleanupProxyForTarget:foo conformingToProtocol:@protocol(NNCleanupProxyTestProtocol2) withKey:(uintptr_t)foo]; 82 | XCTAssertTrue([proxy someKindOfSelectorWithObject:self], @""); 83 | XCTAssertTrue([proxy someKindOfSelectorWithObject2:self], @""); 84 | } 85 | } 86 | 87 | - (void)testUndeclaredMethodDispatch; 88 | { 89 | @autoreleasepool { 90 | __attribute__((objc_precise_lifetime)) id foo = [NNCleanupProxyTestClass new]; 91 | NNCleanupProxyTestClass *proxy = (id)[NNCleanupProxy cleanupProxyForTarget:foo withKey:(uintptr_t)foo]; 92 | XCTAssertThrows([proxy someKindOfSelectorWithObject:self], @""); 93 | XCTAssertThrows([proxy someKindOfSelectorWithObject2:self], @""); 94 | XCTAssertThrows([proxy anotherKindofSelectorWithObject:self], @""); 95 | } 96 | } 97 | 98 | - (void)testExplicitlyDeclaredMethodDispatch; 99 | { 100 | @autoreleasepool { 101 | __attribute__((objc_precise_lifetime)) id foo = [NNCleanupProxyTestClass new]; 102 | NNCleanupProxy *proxy = [NNCleanupProxy cleanupProxyForTarget:foo withKey:(uintptr_t)foo]; 103 | [proxy cacheMethodSignatureForSelector:@selector(anotherKindofSelectorWithObject:)]; 104 | 105 | NNCleanupProxyTestClass *castProxy = (id)proxy; 106 | XCTAssertTrue([castProxy anotherKindofSelectorWithObject:self], @""); 107 | } 108 | } 109 | 110 | - (void)testNilMessagingAfterTargetDealloc; 111 | { 112 | NNCleanupProxyTestClass *proxy = nil; 113 | 114 | @autoreleasepool { 115 | __attribute__((objc_precise_lifetime)) id foo = [NNCleanupProxyTestClass new]; 116 | proxy = (id)[NNCleanupProxy cleanupProxyForTarget:foo conformingToProtocol:@protocol(NNCleanupProxyTestProtocol2) withKey:(uintptr_t)foo]; 117 | } 118 | 119 | XCTAssertThrows([proxy someKindOfSelectorWithObject:self], @""); 120 | XCTAssertThrows([proxy someKindOfSelectorWithObject2:self], @""); 121 | XCTAssertThrows([proxy anotherKindofSelectorWithObject:self], @""); 122 | } 123 | 124 | - (void)testCachingInvalidMethod; 125 | { 126 | @autoreleasepool { 127 | __attribute__((objc_precise_lifetime)) id foo = [NNCleanupProxyTestClass new]; 128 | NNCleanupProxy *proxy = [NNCleanupProxy cleanupProxyForTarget:foo withKey:(uintptr_t)foo]; 129 | XCTAssertThrows([proxy cacheMethodSignatureForSelector:@selector(testCachingInvalidMethod)]); 130 | } 131 | } 132 | 133 | - (void)testProxyMatchesObject; 134 | { 135 | @autoreleasepool { 136 | __attribute__((objc_precise_lifetime)) id foo = [NNCleanupProxyTestClass new]; 137 | NNCleanupProxy *proxy = [NNCleanupProxy cleanupProxyForTarget:foo withKey:(uintptr_t)foo]; 138 | 139 | XCTAssertTrue([foo isEqual:proxy]); 140 | NSSet *set = [NSSet setWithObject:proxy]; 141 | XCTAssertTrue([set containsObject:foo]); 142 | 143 | XCTAssertTrue([proxy isEqual:foo]); 144 | set = [NSSet setWithObject:foo]; 145 | XCTAssertTrue([set containsObject:proxy]); 146 | } 147 | } 148 | 149 | @end 150 | -------------------------------------------------------------------------------- /NNKitTests/NNComprehensionTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNComprehensionTests.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 03/11/14. 6 | // Copyright © 2014 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | #import 18 | 19 | 20 | @interface NNComprehensionTests : XCTestCase 21 | 22 | @end 23 | 24 | @implementation NNComprehensionTests 25 | 26 | - (void)testFilter; 27 | { 28 | NSArray *inputArray = @[@1, @2, @3, @4, @5, @6, @7, @8, @9, @10]; 29 | NSSet *inputSet = [NSSet setWithArray:inputArray]; 30 | NSOrderedSet *inputOrderedSet = [NSOrderedSet orderedSetWithArray:inputArray]; 31 | 32 | NSArray *outputArray = @[@3, @6, @9]; 33 | NSSet *outputSet = [NSSet setWithArray:outputArray]; 34 | NSOrderedSet *outputOrderedSet = [NSOrderedSet orderedSetWithArray:outputArray]; 35 | 36 | nn_filter_block_t filterBlock = ^(id item){ return (BOOL)!([item integerValue] % 3); }; 37 | 38 | XCTAssertEqualObjects(outputArray, [inputArray nn_filter:filterBlock], @"Filtered array did not match expectations"); 39 | XCTAssertEqualObjects(outputSet, [inputSet nn_filter:filterBlock], @"Filtered set did not match expectations"); 40 | XCTAssertEqualObjects(outputOrderedSet, [inputOrderedSet nn_filter:filterBlock], @"Filtered array did not match expectations"); 41 | } 42 | 43 | - (void)testMap; 44 | { 45 | NSArray *inputArray = @[@1, @2, @3, @4, @5, @6, @7, @8, @9, @10]; 46 | NSSet *inputSet = [NSSet setWithArray:inputArray]; 47 | NSOrderedSet *inputOrderedSet = [NSOrderedSet orderedSetWithArray:inputArray]; 48 | 49 | NSArray *outputArray = @[@2, @4, @6, @8, @10, @12, @14, @16, @18, @20]; 50 | NSSet *outputSet = [NSSet setWithArray:outputArray]; 51 | NSOrderedSet *outputOrderedSet = [NSOrderedSet orderedSetWithArray:outputArray]; 52 | 53 | nn_map_block_t mapBlock = ^(id item){ return @([item integerValue] * 2); }; 54 | 55 | XCTAssertEqualObjects(outputArray, [inputArray nn_map:mapBlock], @"Filtered array did not match expectations"); 56 | XCTAssertEqualObjects(outputSet, [inputSet nn_map:mapBlock], @"Filtered set did not match expectations"); 57 | XCTAssertEqualObjects(outputOrderedSet, [inputOrderedSet nn_map:mapBlock], @"Filtered array did not match expectations"); 58 | } 59 | 60 | - (void)testReduce; 61 | { 62 | NSArray *inputArray = @[@1, @2, @3, @4, @5, @6, @7, @8, @9, @10]; 63 | NSSet *inputSet = [NSSet setWithArray:inputArray]; 64 | NSOrderedSet *inputOrderedSet = [NSOrderedSet orderedSetWithArray:inputArray]; 65 | 66 | NSNumber *output = @55; 67 | 68 | XCTAssertEqualObjects(output, [inputArray nn_reduce:^(id acc, id item){ return @([acc integerValue] + [item integerValue]); }], @""); 69 | XCTAssertEqualObjects(output, [inputSet nn_reduce:^(id acc, id item){ return @([acc integerValue] + [item integerValue]); }], @""); 70 | XCTAssertEqualObjects(output, [inputOrderedSet nn_reduce:^(id acc, id item){ return @([acc integerValue] + [item integerValue]); }], @""); 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /NNKitTests/NNDelegateProxyTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNDelegateProxyTests.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | #import 18 | 19 | 20 | @protocol MYClassDelegate 21 | 22 | - (void)objectCalledDelegateMethod:(id)obj; 23 | - (oneway void)asyncMethod; 24 | 25 | @optional 26 | 27 | - (void)unimplementedOptionalMethod; 28 | - (oneway void)unimplementedOnewayOptionalMethod; 29 | 30 | @end 31 | 32 | 33 | @interface NNDelegateProxyTests : XCTestCase 34 | 35 | @end 36 | 37 | 38 | @interface MYClass : NSObject 39 | 40 | @property (strong) id delegateProxy; 41 | 42 | @end 43 | 44 | @implementation MYClass 45 | 46 | - (instancetype)initWithDelegate:(id)delegate; 47 | { 48 | if (!(self = [super init])) { return nil; } 49 | 50 | _delegateProxy = [NNDelegateProxy proxyWithDelegate:delegate protocol:@protocol(MYClassDelegate)]; 51 | 52 | return self; 53 | } 54 | 55 | - (void)globalAsync; 56 | { 57 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 58 | [self.delegateProxy objectCalledDelegateMethod:self]; 59 | }); 60 | } 61 | 62 | - (void)globalSync; 63 | { 64 | dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 65 | [self.delegateProxy objectCalledDelegateMethod:self]; 66 | }); 67 | } 68 | 69 | - (void)mainAsync; 70 | { 71 | dispatch_async(dispatch_get_main_queue(), ^{ 72 | [self.delegateProxy objectCalledDelegateMethod:self]; 73 | }); 74 | } 75 | 76 | - (void)invalid; 77 | { 78 | [(id)self.delegateProxy willChangeValueForKey:@""]; 79 | } 80 | 81 | - (void)async; 82 | { 83 | [self.delegateProxy asyncMethod]; 84 | } 85 | 86 | - (void)optional; 87 | { 88 | [self.delegateProxy unimplementedOptionalMethod]; 89 | } 90 | 91 | - (void)onewayOptional; 92 | { 93 | [self.delegateProxy unimplementedOnewayOptionalMethod]; 94 | } 95 | 96 | @end 97 | 98 | 99 | static dispatch_group_t group; 100 | 101 | 102 | @implementation NNDelegateProxyTests 103 | 104 | - (void)setUp 105 | { 106 | [super setUp]; 107 | 108 | group = dispatch_group_create(); 109 | } 110 | 111 | - (void)tearDown 112 | { 113 | // Put teardown code here. This method is called after the invocation of each test method in the class. 114 | [super tearDown]; 115 | } 116 | 117 | - (void)testGlobalAsync; 118 | { 119 | dispatch_group_enter(group); 120 | [[[MYClass alloc] initWithDelegate:self] globalAsync]; 121 | 122 | NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:0.1]; 123 | while (!despatch_group_yield(group) && [[NSDate date] compare:timeout] == NSOrderedAscending); 124 | XCTAssertFalse(dispatch_group_wait(group, DISPATCH_TIME_NOW), @"Delegate message was never received (timed out)!"); 125 | } 126 | 127 | - (void)testGlobalSync; 128 | { 129 | dispatch_group_enter(group); 130 | [[[MYClass alloc] initWithDelegate:self] globalSync]; 131 | XCTAssertFalse(dispatch_group_wait(group, DISPATCH_TIME_NOW), @"Delegate message was not received synchronously!"); 132 | } 133 | 134 | - (void)testMainAsync; 135 | { 136 | dispatch_group_enter(group); 137 | [[[MYClass alloc] initWithDelegate:self] mainAsync]; 138 | 139 | NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:0.1]; 140 | while (!despatch_group_yield(group) && [[NSDate date] compare:timeout] == NSOrderedAscending); 141 | XCTAssertFalse(dispatch_group_wait(group, DISPATCH_TIME_NOW), @"Delegate message was never received (timed out)!"); 142 | } 143 | 144 | - (void)testInvalidDelegateMethod; 145 | { 146 | XCTAssertThrows([[[MYClass alloc] initWithDelegate:self] invalid], @"Invalid delegate method was allowed to pass through!"); 147 | } 148 | 149 | - (void)testOnewayAsync; 150 | { 151 | dispatch_group_enter(group); 152 | [[[MYClass alloc] initWithDelegate:self] async]; 153 | 154 | XCTAssertTrue(dispatch_group_wait(group, DISPATCH_TIME_NOW), @"Delegate message was not supposed to be sent synchronously."); 155 | 156 | NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:0.1]; 157 | while (!despatch_group_yield(group) && [[NSDate date] compare:timeout] == NSOrderedAscending); 158 | XCTAssertFalse(dispatch_group_wait(group, DISPATCH_TIME_NOW), @"Delegate message was never received (timed out)!"); 159 | } 160 | 161 | - (void)testOptional; 162 | { 163 | XCTAssertNoThrow([[[MYClass alloc] initWithDelegate:self] optional], @"Sending optional messages to delegate proxies should never blow up."); 164 | XCTAssertNoThrow([[[MYClass alloc] initWithDelegate:self] onewayOptional], @"Sending optional messages to delegate proxies should never blow up."); 165 | } 166 | 167 | 168 | #pragma mark MYClassDelegate 169 | 170 | - (oneway void)asyncMethod; 171 | { 172 | [self objectCalledDelegateMethod:nil]; 173 | } 174 | 175 | - (void)objectCalledDelegateMethod:(id)obj; 176 | { 177 | XCTAssertTrue([[NSThread currentThread] isMainThread], @"Delegate message was not dispatched on the main queue!"); 178 | dispatch_group_leave(group); 179 | } 180 | 181 | @end 182 | -------------------------------------------------------------------------------- /NNKitTests/NNKit Mac Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | net.numist.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /NNKitTests/NNKit iOS Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | net.numist.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /NNKitTests/NNMultiDispatchManagerTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNMultiDispatchManagerTests.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/19/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | #import 17 | #import 18 | 19 | 20 | unsigned callCount = 0; 21 | dispatch_group_t group; 22 | 23 | 24 | @protocol NNMultiDispatchManagerTestProtocol 25 | - (void)foo:(id)sender; 26 | - (oneway void)bar:(id)sender; 27 | @optional 28 | - (void)baz:(id)sender; 29 | - (id)qux:(id)sender; 30 | @end 31 | 32 | 33 | @interface NNMultiDispatchManagerTestObject : NSObject 34 | @end 35 | @implementation NNMultiDispatchManagerTestObject 36 | - (void)foo:(id)sender; 37 | { 38 | callCount++; 39 | } 40 | - (oneway void)bar:(id)sender; 41 | { 42 | callCount++; 43 | dispatch_group_leave(group); 44 | } 45 | @end 46 | 47 | 48 | @interface NNMultiDispatchManagerTestObject2 : NSObject 49 | @end 50 | @implementation NNMultiDispatchManagerTestObject2 51 | - (void)foo:(id)sender; 52 | { 53 | callCount++; 54 | } 55 | - (oneway void)bar:(id)sender; 56 | { 57 | callCount++; 58 | dispatch_group_leave(group); 59 | } 60 | - (void)baz:(id)sender; 61 | { 62 | callCount++; 63 | } 64 | @end 65 | 66 | 67 | @interface NNMultiDispatchManagerTests : XCTestCase 68 | 69 | @end 70 | 71 | 72 | @implementation NNMultiDispatchManagerTests 73 | 74 | - (void)setUp 75 | { 76 | [super setUp]; 77 | 78 | callCount = 0; 79 | group = dispatch_group_create(); 80 | } 81 | 82 | - (void)testSync 83 | { 84 | NNMultiDispatchManager *manager = [[NNMultiDispatchManager alloc] initWithProtocol:@protocol(NNMultiDispatchManagerTestProtocol)]; 85 | __attribute__((objc_precise_lifetime)) id foo1 = [NNMultiDispatchManagerTestObject new]; 86 | __attribute__((objc_precise_lifetime)) id foo2 = [NNMultiDispatchManagerTestObject new]; 87 | __attribute__((objc_precise_lifetime)) id foo3 = [NNMultiDispatchManagerTestObject2 new]; 88 | __attribute__((objc_precise_lifetime)) id foo4 = [NNMultiDispatchManagerTestObject2 new]; 89 | [manager addObserver:foo1]; 90 | [manager addObserver:foo2]; 91 | [manager addObserver:foo3]; 92 | [manager addObserver:foo4]; 93 | 94 | [(id)manager foo:self]; 95 | XCTAssertEqual(callCount, (unsigned)4, @""); 96 | } 97 | 98 | - (void)testAsync; 99 | { 100 | NNMultiDispatchManager *manager = [[NNMultiDispatchManager alloc] initWithProtocol:@protocol(NNMultiDispatchManagerTestProtocol)]; 101 | __attribute__((objc_precise_lifetime)) id foo1 = [NNMultiDispatchManagerTestObject new]; 102 | __attribute__((objc_precise_lifetime)) id foo2 = [NNMultiDispatchManagerTestObject new]; 103 | __attribute__((objc_precise_lifetime)) id foo3 = [NNMultiDispatchManagerTestObject2 new]; 104 | __attribute__((objc_precise_lifetime)) id foo4 = [NNMultiDispatchManagerTestObject2 new]; 105 | [manager addObserver:foo1]; 106 | [manager addObserver:foo2]; 107 | [manager addObserver:foo3]; 108 | [manager addObserver:foo4]; 109 | dispatch_group_enter(group); 110 | dispatch_group_enter(group); 111 | dispatch_group_enter(group); 112 | dispatch_group_enter(group); 113 | 114 | XCTAssertEqual(callCount, (unsigned)0, @""); 115 | [(id)manager bar:self]; 116 | 117 | while(!despatch_group_yield(group)); 118 | XCTAssertEqual(callCount, (unsigned)4, @""); 119 | } 120 | 121 | - (void)testOptionalSelector; 122 | { 123 | NNMultiDispatchManager *manager = [[NNMultiDispatchManager alloc] initWithProtocol:@protocol(NNMultiDispatchManagerTestProtocol)]; 124 | __attribute__((objc_precise_lifetime)) id foo1 = [NNMultiDispatchManagerTestObject new]; 125 | __attribute__((objc_precise_lifetime)) id foo2 = [NNMultiDispatchManagerTestObject new]; 126 | __attribute__((objc_precise_lifetime)) id foo3 = [NNMultiDispatchManagerTestObject2 new]; 127 | __attribute__((objc_precise_lifetime)) id foo4 = [NNMultiDispatchManagerTestObject2 new]; 128 | [manager addObserver:foo1]; 129 | [manager addObserver:foo2]; 130 | [manager addObserver:foo3]; 131 | [manager addObserver:foo4]; 132 | 133 | XCTAssertNoThrow([(id)manager baz:self], @""); 134 | XCTAssertEqual(callCount, (unsigned)2, @""); 135 | } 136 | 137 | - (void)testBadSelector; 138 | { 139 | NNMultiDispatchManager *manager = [[NNMultiDispatchManager alloc] initWithProtocol:@protocol(NNMultiDispatchManagerTestProtocol)]; 140 | __attribute__((objc_precise_lifetime)) id foo1 = [NNMultiDispatchManagerTestObject new]; 141 | __attribute__((objc_precise_lifetime)) id foo2 = [NNMultiDispatchManagerTestObject new]; 142 | __attribute__((objc_precise_lifetime)) id foo3 = [NNMultiDispatchManagerTestObject2 new]; 143 | __attribute__((objc_precise_lifetime)) id foo4 = [NNMultiDispatchManagerTestObject2 new]; 144 | [manager addObserver:foo1]; 145 | [manager addObserver:foo2]; 146 | [manager addObserver:foo3]; 147 | [manager addObserver:foo4]; 148 | 149 | XCTAssertThrows([(id)manager invokeWithTarget:self], @""); 150 | XCTAssertEqual(callCount, (unsigned)0, @""); 151 | } 152 | 153 | - (void)testWeakDispatch 154 | { 155 | NNMultiDispatchManager *manager = [[NNMultiDispatchManager alloc] initWithProtocol:@protocol(NNMultiDispatchManagerTestProtocol)]; 156 | __attribute__((objc_precise_lifetime)) id foo1 = [NNMultiDispatchManagerTestObject new]; 157 | __attribute__((objc_precise_lifetime)) id foo4 = [NNMultiDispatchManagerTestObject2 new]; 158 | @autoreleasepool { 159 | id foo2 = [NNMultiDispatchManagerTestObject new]; 160 | id foo3 = [NNMultiDispatchManagerTestObject2 new]; 161 | [manager addObserver:foo1]; 162 | [manager addObserver:foo2]; 163 | [manager addObserver:foo3]; 164 | [manager addObserver:foo4]; 165 | } 166 | 167 | [(id)manager foo:self]; 168 | XCTAssertEqual(callCount, (unsigned)2, @""); 169 | } 170 | 171 | - (void)testIllegalReturnType 172 | { 173 | NNMultiDispatchManager *manager = [[NNMultiDispatchManager alloc] initWithProtocol:@protocol(NNMultiDispatchManagerTestProtocol)]; 174 | __attribute__((objc_precise_lifetime)) id foo1 = [NNMultiDispatchManagerTestObject new]; 175 | __attribute__((objc_precise_lifetime)) id foo2 = [NNMultiDispatchManagerTestObject new]; 176 | __attribute__((objc_precise_lifetime)) id foo3 = [NNMultiDispatchManagerTestObject2 new]; 177 | __attribute__((objc_precise_lifetime)) id foo4 = [NNMultiDispatchManagerTestObject2 new]; 178 | [manager addObserver:foo1]; 179 | [manager addObserver:foo2]; 180 | [manager addObserver:foo3]; 181 | [manager addObserver:foo4]; 182 | 183 | XCTAssertThrows((void)[(id)manager qux:self], @""); 184 | XCTAssertEqual(callCount, (unsigned)0, @""); 185 | } 186 | 187 | @end 188 | -------------------------------------------------------------------------------- /NNKitTests/NNPollingObjectTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNPollingObjectTests.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/06/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | #import 18 | #import 19 | 20 | 21 | @interface NNPollingObjectTests : XCTestCase 22 | 23 | @end 24 | 25 | 26 | dispatch_group_t pollingObjectGroup; 27 | 28 | 29 | @interface NNTestObject : NNPollingObject 30 | @end 31 | @implementation NNTestObject 32 | 33 | - (instancetype)init; 34 | { 35 | if (!(self = [super init])) { return nil; } 36 | self.interval = 0.0001; 37 | dispatch_group_enter(pollingObjectGroup); 38 | return self; 39 | } 40 | 41 | - (void)main; 42 | { 43 | [self postNotification:nil]; 44 | } 45 | 46 | - (void)dealloc; 47 | { 48 | dispatch_group_leave(pollingObjectGroup); 49 | } 50 | 51 | @end 52 | 53 | 54 | static int iterations; 55 | 56 | 57 | @implementation NNPollingObjectTests 58 | 59 | + (void)initialize; 60 | { 61 | static dispatch_once_t onceToken; 62 | dispatch_once(&onceToken, ^{ 63 | pollingObjectGroup = dispatch_group_create(); 64 | }); 65 | } 66 | 67 | - (void)setUp 68 | { 69 | [super setUp]; 70 | 71 | iterations = 0; 72 | 73 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(objectNotification:) name:[NNTestObject notificationName] object:nil]; 74 | } 75 | 76 | - (void)tearDown 77 | { 78 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 79 | 80 | [super tearDown]; 81 | } 82 | 83 | - (void)testBasicPolling 84 | { 85 | #pragma clang diagnostic push 86 | #pragma clang diagnostic ignored "-Wunused-variable" 87 | __attribute__((objc_precise_lifetime)) NNTestObject *obj = [NNTestObject new]; 88 | #pragma clang diagnostic pop 89 | 90 | NSDate *until = [NSDate dateWithTimeIntervalSinceNow:0.05]; 91 | while ([[NSDate date] compare:until] == NSOrderedAscending && !iterations) { 92 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:until]; 93 | } 94 | XCTAssert(iterations > 0, @"Polling object iterated zero times!"); 95 | } 96 | 97 | - (void)testZeroInterval 98 | { 99 | __attribute__((objc_precise_lifetime)) NNTestObject *obj = [NNTestObject new]; 100 | obj.interval = 0.0; 101 | 102 | NSDate *until = [NSDate dateWithTimeIntervalSinceNow:0.01]; 103 | while ([[NSDate date] compare:until] == NSOrderedAscending && iterations < 2) { 104 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:until]; 105 | } 106 | XCTAssert(iterations == 1, @"Polling object iterated more than once!"); 107 | } 108 | 109 | - (void)testObjectDeath 110 | { 111 | @autoreleasepool { 112 | #pragma clang diagnostic push 113 | #pragma clang diagnostic ignored "-Wunused-variable" 114 | __attribute__((objc_precise_lifetime)) NNTestObject *obj = [NNTestObject new]; 115 | #pragma clang diagnostic pop 116 | } 117 | 118 | while(!despatch_group_yield(pollingObjectGroup)); 119 | iterations = 0; 120 | 121 | NSDate *until = [NSDate dateWithTimeIntervalSinceNow:0.05]; 122 | while ([[NSDate date] compare:until] == NSOrderedAscending && !iterations) { 123 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:until]; 124 | } 125 | XCTAssert(iterations == 0, @"Object continued polling after it was released!"); 126 | } 127 | 128 | - (void)testNoSubclassing; 129 | { 130 | @autoreleasepool { 131 | dispatch_queue_t q = dispatch_queue_create("testQ", DISPATCH_QUEUE_SERIAL); 132 | dispatch_suspend(q); 133 | NNPollingObject *obj = [[NNPollingObject alloc] initWithQueue:q]; 134 | obj.interval = -1; 135 | XCTAssertThrows([obj main]); 136 | } 137 | } 138 | 139 | - (void)objectNotification:(NSNotification *)notification; 140 | { 141 | XCTAssert([[NSThread currentThread] isMainThread], @"Poll notification was not dispatched on the main thread!"); 142 | 143 | iterations++; 144 | } 145 | 146 | @end 147 | -------------------------------------------------------------------------------- /NNKitTests/NNSelfInvalidatingObjectTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNSelfInvalidatingObjectTests.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | #import 18 | 19 | 20 | // Fuckin' state, man. 21 | static BOOL objectInvalidated; 22 | static BOOL objectDestroyed; 23 | 24 | 25 | @interface NNSelfInvalidatingObjectTests : XCTestCase 26 | // The XCT macros don't work outside of an XCTestCase object, so events have to be forwarded back to the test case. 27 | - (void)invalidated:(id)obj; 28 | - (void)destroyed:(id)obj; 29 | @end 30 | 31 | 32 | @interface NNInvalidatingTestObject : NNSelfInvalidatingObject 33 | 34 | @property (assign, readonly) NNSelfInvalidatingObjectTests *test; 35 | 36 | @end 37 | 38 | 39 | @implementation NNInvalidatingTestObject 40 | 41 | - (instancetype)initWithTestObject:(NNSelfInvalidatingObjectTests *)obj; 42 | { 43 | if (!(self = [super init])) { return nil; } 44 | 45 | _test = obj; 46 | 47 | return self; 48 | } 49 | 50 | - (void)invalidate; 51 | { 52 | [self.test invalidated:self]; 53 | 54 | [super invalidate]; 55 | } 56 | - (void)dealloc; 57 | { 58 | [_test destroyed:self]; 59 | 60 | [super dealloc]; 61 | } 62 | @end 63 | 64 | 65 | @implementation NNSelfInvalidatingObjectTests 66 | 67 | - (void)setUp 68 | { 69 | [super setUp]; 70 | 71 | objectInvalidated = NO; 72 | objectDestroyed = NO; 73 | } 74 | 75 | - (void)tearDown 76 | { 77 | // Put teardown code here. This method is called after the invocation of each test method in the class. 78 | [super tearDown]; 79 | } 80 | 81 | - (void)invalidated:(id)obj; 82 | { 83 | XCTAssertTrue([[NSThread currentThread] isMainThread], @"Invalidation happened on a queue other than the main queue!"); 84 | XCTAssertFalse(objectInvalidated, @"Object was invalidated multiple times!"); 85 | objectInvalidated = YES; 86 | } 87 | 88 | - (void)destroyed:(id)obj; 89 | { 90 | XCTAssertTrue(objectInvalidated, @"Object was destroyed before it was invalidated!"); 91 | objectDestroyed = YES; 92 | } 93 | 94 | - (void)testDeallocInvalidation 95 | { 96 | NNInvalidatingTestObject *obj = [[NNInvalidatingTestObject alloc] initWithTestObject:self]; 97 | 98 | XCTAssertFalse(objectInvalidated, @"Object was still valid before it was released"); 99 | 100 | [obj release]; 101 | obj = nil; 102 | 103 | while (!objectDestroyed) { 104 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 105 | } 106 | 107 | XCTAssertTrue(objectInvalidated, @"Object was not invalidated before it was destroyed"); 108 | } 109 | 110 | - (void)testManualInvalidation 111 | { 112 | NNInvalidatingTestObject *obj = [[NNInvalidatingTestObject alloc] initWithTestObject:self]; 113 | 114 | XCTAssertFalse(objectInvalidated, @"Object was still valid before it was released"); 115 | 116 | // Ensure the autorelease pool gets drained within the scope of this test. 117 | @autoreleasepool { 118 | [obj invalidate]; 119 | } 120 | 121 | XCTAssertTrue(objectInvalidated, @"Object was manually invalidated before it was destroyed"); 122 | 123 | [obj release]; 124 | obj = nil; 125 | 126 | while (!objectDestroyed) { 127 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 128 | } 129 | 130 | XCTAssertTrue(objectInvalidated, @"Object was not invalidated before it was destroyed"); 131 | } 132 | 133 | - (void)testManualDealloc 134 | { 135 | XCTAssertThrows([[NNSelfInvalidatingObject new] dealloc], @"Invalidating objects are not supposed to accept -dealloc quietly"); 136 | } 137 | 138 | @end 139 | -------------------------------------------------------------------------------- /NNKitTests/NNStrongifiedPropertiesTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNStrongifiedPropertiesTests.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | #import 18 | 19 | 20 | @interface NNStrongifiedPropertiesTests : XCTestCase 21 | 22 | @end 23 | 24 | 25 | 26 | @interface NNStrongifyTestClass : NNStrongifiedProperties 27 | 28 | @property (weak) id foo; 29 | @property id bar; 30 | @property (weak) id TLA; 31 | @property (weak) id qux; 32 | @property (weak) id Qux; 33 | 34 | @end 35 | @interface NNStrongifyTestClass (NNStrongGetters) 36 | 37 | - (id)strongFoo; // Good 38 | - (id)strongBar; // Bad -- strong 39 | - (id)strongTLA; // Good 40 | - (id)strongQux; // Bad -- ambiguous 41 | 42 | @end 43 | @implementation NNStrongifyTestClass 44 | @end 45 | 46 | 47 | 48 | @interface NNSwizzledStrongifierTestClass : NSObject 49 | 50 | @property (weak) id foo; 51 | @property id bar; 52 | @property (weak) id TLA; 53 | @property (weak) id qux; 54 | @property (weak) id Qux; 55 | 56 | @end 57 | @interface NNSwizzledStrongifierTestClass (NNStrongGetters) 58 | 59 | - (id)strongFoo; // Good 60 | - (id)strongBar; // Bad -- strong 61 | - (id)strongTLA; // Good 62 | - (id)strongQux; // Bad -- ambiguous 63 | 64 | @end 65 | @implementation NNSwizzledStrongifierTestClass 66 | 67 | 68 | @end 69 | 70 | 71 | 72 | @implementation NNStrongifiedPropertiesTests 73 | 74 | - (void)setUp 75 | { 76 | [super setUp]; 77 | // Put setup code here. This method is called before the invocation of each test method in the class. 78 | } 79 | 80 | - (void)tearDown 81 | { 82 | // Put teardown code here. This method is called after the invocation of each test method in the class. 83 | [super tearDown]; 84 | } 85 | 86 | - (void)testBasicStrongGetter 87 | { 88 | NNStrongifyTestClass *obj = [NNStrongifyTestClass new]; 89 | id boo = [NSObject new]; 90 | obj.foo = boo; 91 | XCTAssertEqual([obj strongFoo], boo, @"Basic weak property did not resolve a strong getter."); 92 | [boo self]; 93 | } 94 | 95 | - (void)testCapitalizedStrongGetter 96 | { 97 | NNStrongifyTestClass *obj = [NNStrongifyTestClass new]; 98 | id boo = [NSObject new]; 99 | obj.TLA = boo; 100 | XCTAssertEqual([obj strongTLA], boo, @"Capitalized weak property did not resolve a strong getter."); 101 | [boo self]; 102 | } 103 | 104 | - (void)testStrongGetterWithStrongProperty 105 | { 106 | XCTAssertThrows([[NNStrongifyTestClass new] strongBar], @"Strongified strong property access resulted in a valid IMP."); 107 | } 108 | 109 | - (void)testAmbiguousStrongGetter 110 | { 111 | XCTAssertThrows([[NNStrongifyTestClass new] strongQux], @"Ambiguous property access found an IMP."); 112 | } 113 | 114 | - (void)testNilling 115 | { 116 | NNStrongifyTestClass *obj = [NNStrongifyTestClass new]; 117 | @autoreleasepool { 118 | id boo = [NSObject new]; 119 | obj.foo = boo; 120 | [boo self]; 121 | } 122 | XCTAssertNil([obj strongFoo], @"Weak property did not nil as expected."); 123 | } 124 | 125 | - (void)testSwizzledBasicStrongGetter 126 | { 127 | NNSwizzledStrongifierTestClass *obj = [NNSwizzledStrongifierTestClass new]; 128 | BOOL swizzled = nn_object_swizzleIsa(obj, [NNStrongifiedProperties class]); 129 | XCTAssertTrue(swizzled); 130 | id boo = [NSObject new]; 131 | obj.foo = boo; 132 | XCTAssertEqual([obj strongFoo], boo, @"Basic weak property did not resolve a strong getter."); 133 | [boo self]; 134 | } 135 | 136 | - (void)testSwizzledCapitalizedStrongGetter 137 | { 138 | NNSwizzledStrongifierTestClass *obj = [NNSwizzledStrongifierTestClass new]; 139 | BOOL swizzled = nn_object_swizzleIsa(obj, [NNStrongifiedProperties class]); 140 | XCTAssertTrue(swizzled); 141 | id boo = [NSObject new]; 142 | obj.TLA = boo; 143 | XCTAssertEqual([obj strongTLA], boo, @"Capitalized weak property did not resolve a strong getter."); 144 | [boo self]; 145 | } 146 | 147 | - (void)testSwizzledStrongGetterWithStrongProperty 148 | { 149 | NNSwizzledStrongifierTestClass *obj = [NNSwizzledStrongifierTestClass new]; 150 | nn_object_swizzleIsa(obj, [NNStrongifiedProperties class]); 151 | XCTAssertThrows([obj strongBar], @"Strongified strong property access resulted in a valid IMP."); 152 | } 153 | 154 | - (void)testSwizzledAmbiguousStrongGetter 155 | { 156 | NNSwizzledStrongifierTestClass *obj = [NNSwizzledStrongifierTestClass new]; 157 | nn_object_swizzleIsa(obj, [NNStrongifiedProperties class]); 158 | XCTAssertThrows([obj strongQux], @"Ambiguous property access resulted in a single IMP."); 159 | } 160 | 161 | - (void)testSwizzledNilling 162 | { 163 | NNSwizzledStrongifierTestClass *obj = [NNSwizzledStrongifierTestClass new]; 164 | BOOL swizzled = nn_object_swizzleIsa(obj, [NNStrongifiedProperties class]); 165 | XCTAssertTrue(swizzled); 166 | @autoreleasepool { 167 | id boo = [NSObject new]; 168 | obj.foo = boo; 169 | [boo self]; 170 | } 171 | XCTAssertNil([obj strongFoo], @"Weak property did not nil as expected."); 172 | } 173 | 174 | @end 175 | -------------------------------------------------------------------------------- /NNKitTests/NNSynthesizedObjectStorageTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNSynthesizedObjectStorageTests.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 06/23/15. 6 | // Copyright © 2015 Scott Perry. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "runtime.h" 12 | 13 | #import "memoize.h" 14 | 15 | 16 | @interface NNSynthesizedObjectStorageTestObject : NSObject 17 | 18 | @property (nonatomic, strong) NSNumber *number; 19 | 20 | @end 21 | 22 | 23 | @implementation NNSynthesizedObjectStorageTestObject 24 | 25 | - (instancetype)init { 26 | if (!(self = [super init])) { return nil; } 27 | _number = @(arc4random()); 28 | return self; 29 | } 30 | 31 | - (nonnull id)copyWithZone:(nullable NSZone *)zone { 32 | NNSynthesizedObjectStorageTestObject *result = [NNSynthesizedObjectStorageTestObject new]; 33 | result.number = self.number; 34 | return result; 35 | } 36 | 37 | - (BOOL)isEqual:(id)object { 38 | return ([object isKindOfClass:[self class]] && [[object number] isEqualToNumber:self.number]); 39 | } 40 | 41 | @end 42 | 43 | 44 | @interface NNSynthesizedObjectStorageTests : XCTestCase 45 | @end 46 | 47 | 48 | @interface NNSynthesizedObjectStorageTests (NNSynthesizedProperties) 49 | 50 | @property (nonatomic, strong) NNSynthesizedObjectStorageTestObject *prop_ns; 51 | @property (nonatomic, assign) NNSynthesizedObjectStorageTestObject *prop_na; 52 | @property (nonatomic, weak) NNSynthesizedObjectStorageTestObject *prop_nw; 53 | @property (nonatomic, copy) NNSynthesizedObjectStorageTestObject *prop_nc; 54 | 55 | @end 56 | 57 | 58 | @implementation NNSynthesizedObjectStorageTests (NNSynthesizedProperties) 59 | 60 | NNSynthesizeObjectStorage(id, prop_ns, prop_ns, setProp_ns:) 61 | NNSynthesizeObjectStorage(id, prop_na, prop_na, setProp_na:) 62 | NNSynthesizeObjectStorage(id, prop_nw, prop_nw, setProp_nw:) 63 | NNSynthesizeObjectStorage(id, prop_nc, prop_nc, setProp_nc:) 64 | 65 | @end 66 | 67 | 68 | @implementation NNSynthesizedObjectStorageTests 69 | 70 | - (void)testTestObjects { 71 | XCTAssertNotNil([[NNSynthesizedObjectStorageTestObject new] number]); 72 | } 73 | 74 | - (void)testStrong { 75 | @autoreleasepool { 76 | id obj = [NNSynthesizedObjectStorageTestObject new]; 77 | self.prop_ns = obj; 78 | XCTAssertEqual(self.prop_ns, obj); 79 | } 80 | XCTAssertNotNil(self.prop_ns); 81 | XCTAssertNotNil([self.prop_ns number]); 82 | } 83 | 84 | - (void)testAssign { 85 | @autoreleasepool { 86 | id obj = [NNSynthesizedObjectStorageTestObject new]; 87 | self.prop_na = obj; 88 | XCTAssertEqual(self.prop_na, obj); 89 | } 90 | } 91 | 92 | #warning This is not implemented yet. 93 | //- (void)testWeak { 94 | // @autoreleasepool { 95 | // id obj = [NNSynthesizedObjectStorageTestObject new]; 96 | // self.prop_nw = obj; 97 | // XCTAssertEqual(self.prop_nw, obj); 98 | // } 99 | // XCTAssertNil(self.prop_nw); 100 | //} 101 | 102 | - (void)testCopy { 103 | @autoreleasepool { 104 | id obj = [NNSynthesizedObjectStorageTestObject new]; 105 | self.prop_nc = obj; 106 | XCTAssertEqualObjects(self.prop_nc, obj); 107 | XCTAssertNotEqual(self.prop_nc, obj); 108 | } 109 | XCTAssertNotNil(self.prop_nc); 110 | XCTAssertNotNil([self.prop_nc number]); 111 | } 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /NNKitTests/NNTestCase.h: -------------------------------------------------------------------------------- 1 | // 2 | // NNTestCase.h 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/20/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | @interface NNTestCase : XCTestCase 18 | 19 | - (BOOL)testForMemoryLeaksWithBlock:(void (^)())block iterations:(size_t)iterations; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /NNKitTests/NNTestCase.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNTestCase.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/20/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NNTestCase.h" 16 | 17 | #import 18 | 19 | 20 | static size_t report_memory(void) { 21 | struct task_basic_info info; 22 | mach_msg_type_number_t size = sizeof(info); 23 | kern_return_t kerr = task_info(mach_task_self(), 24 | TASK_BASIC_INFO, 25 | (task_info_t)&info, 26 | &size); 27 | if( kerr != KERN_SUCCESS ) { 28 | @throw [NSException exceptionWithName:@"wtf" reason:[NSString stringWithFormat:@"Error with task_info(): %s", mach_error_string(kerr)] userInfo:nil]; 29 | } 30 | return info.resident_size; 31 | } 32 | 33 | 34 | @interface NNTestCase () 35 | 36 | @end 37 | 38 | 39 | @implementation NNTestCase 40 | 41 | - (BOOL)testForMemoryLeaksWithBlock:(void (^)())block iterations:(size_t)iterations; 42 | { 43 | XCTAssertTrue(iterations > 4096, @"Memory leak tests are not accurate with iteration counts less than 4096!"); 44 | 45 | // Three bytes per iteration is allowed to leak because that's basically impossible. 46 | size_t bytes_expected = iterations * 3; 47 | size_t memory_usage_at_start = report_memory(); 48 | 49 | while (--iterations != 0) { 50 | @autoreleasepool { 51 | block(); 52 | } 53 | } 54 | 55 | size_t bytes_actual = report_memory() - memory_usage_at_start; 56 | BOOL memory_usage_is_good = bytes_actual < bytes_expected; 57 | NSLog(@"Memory usage increased by %zu bytes by end of test", bytes_actual); 58 | XCTAssertTrue(memory_usage_is_good, @"Memory usage increased by %zu bytes by end of test (expected < %zu)", bytes_actual, bytes_expected); 59 | 60 | return memory_usage_is_good; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /NNKitTests/NNWeakObserverTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNWeakObserverTests.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/14/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | #import 18 | 19 | 20 | static NSString *notificationName = @"somenotification"; 21 | unsigned receivedNotifications; 22 | 23 | 24 | @interface _NNWeakObserverTestObserver : NSObject 25 | @end 26 | @implementation _NNWeakObserverTestObserver 27 | - (void)notify:(NSNotification *)note; 28 | { 29 | receivedNotifications++; 30 | } 31 | - (void)dealloc; 32 | { 33 | NSLog(@"Destroyed object!"); 34 | } 35 | @end 36 | 37 | 38 | @interface NNWeakObserverTests : XCTestCase 39 | 40 | @end 41 | 42 | 43 | @implementation NNWeakObserverTests 44 | 45 | - (void)setUp 46 | { 47 | [super setUp]; 48 | 49 | receivedNotifications = 0; 50 | } 51 | 52 | - (void)testWeakObservers 53 | { 54 | @autoreleasepool { 55 | __attribute__((objc_precise_lifetime)) _NNWeakObserverTestObserver *observer = [_NNWeakObserverTestObserver new]; 56 | [[NSNotificationCenter defaultCenter] addWeakObserver:observer selector:@selector(notify:) name:notificationName object:nil]; 57 | [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self]; 58 | XCTAssertEqual(receivedNotifications, (unsigned)1, @""); 59 | } 60 | XCTAssertNoThrow([[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self], @""); 61 | XCTAssertEqual(receivedNotifications, (unsigned)1, @""); 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /NNKitTests/NNWeakSetTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NNWeakSetTests.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/18/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NNTestCase.h" 16 | 17 | #import 18 | 19 | 20 | @interface NNWeakSetTests : NNTestCase 21 | 22 | @end 23 | 24 | 25 | @implementation NNWeakSetTests 26 | 27 | - (void)testAddObject; 28 | { 29 | NNWeakSet *set = [NNWeakSet new]; 30 | __attribute__((objc_precise_lifetime)) id bar = [NSObject new]; 31 | [set addObject:bar]; 32 | XCTAssertEqual(set.count, (NSUInteger)1, @""); 33 | } 34 | 35 | - (void)testMemberExists; 36 | { 37 | NNWeakSet *set = [NNWeakSet new]; 38 | __attribute__((objc_precise_lifetime)) id foo = [NSObject new]; 39 | 40 | [set addObject:foo]; 41 | 42 | XCTAssertEqual(set.count, (NSUInteger)1, @""); 43 | XCTAssertEqualObjects([set member:foo], foo, @""); 44 | XCTAssertNil([set member:[NSObject new]], @""); 45 | } 46 | 47 | - (void)testMemberDoesNotExist; 48 | { 49 | NNWeakSet *set = [NNWeakSet new]; 50 | 51 | XCTAssertNil([set member:[NSObject new]], @""); 52 | 53 | __attribute__((objc_precise_lifetime)) id foo = [NSObject new]; 54 | [set addObject:foo]; 55 | XCTAssertNil([set member:[NSObject new]], @""); 56 | } 57 | 58 | - (void)testRemoveObject; 59 | { 60 | NNWeakSet *set = [NNWeakSet new]; 61 | __attribute__((objc_precise_lifetime)) id bar = [NSObject new]; 62 | 63 | [set addObject:bar]; 64 | XCTAssertEqual(set.count, (NSUInteger)1, @""); 65 | [set removeObject:bar]; 66 | XCTAssertEqual(set.count, (NSUInteger)0, @""); 67 | } 68 | 69 | - (void)testWeakRemoval; 70 | { 71 | NNWeakSet *set = [NNWeakSet new]; 72 | 73 | @autoreleasepool { 74 | @autoreleasepool { 75 | __attribute__((objc_precise_lifetime)) id foo = [NSObject new]; 76 | [set addObject:foo]; 77 | XCTAssertEqual(set.count, (NSUInteger)1, @""); 78 | } 79 | 80 | [set addObject:[NSObject new]]; 81 | [set addObject:[NSObject new]]; 82 | } 83 | 84 | XCTAssertEqual(set.count, (NSUInteger)0, @""); 85 | } 86 | 87 | - (void)testEnumeration; 88 | { 89 | NSUInteger enumCount = 0; 90 | NNWeakSet *set = [NNWeakSet new]; 91 | __attribute__((objc_precise_lifetime)) id foo = [NSObject new]; 92 | [set addObject:foo]; 93 | 94 | for (NSObject *obj in set) { 95 | enumCount++; 96 | XCTAssertEqualObjects(obj, foo, @""); 97 | } 98 | 99 | XCTAssertEqual(enumCount, set.count, @""); 100 | XCTAssertEqual(set.count, (NSUInteger)1, @""); 101 | } 102 | 103 | - (void)testMemoryLeaks; 104 | { 105 | NNWeakSet *set = [NNWeakSet new]; 106 | __attribute__((objc_precise_lifetime)) id bar = [NSObject new]; 107 | 108 | [self testForMemoryLeaksWithBlock:^{ 109 | [set addObject:bar]; 110 | [set removeObject:bar]; 111 | } iterations:1e4]; 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /NNKitTests/nn_autofreeTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // nn_autofreeTests.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 11/20/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import "NNTestCase.h" 16 | 17 | #import 18 | 19 | 20 | @interface nn_autofreeTests : NNTestCase 21 | 22 | @end 23 | 24 | 25 | @implementation nn_autofreeTests 26 | 27 | - (void)setUp 28 | { 29 | [super setUp]; 30 | // Put setup code here. This method is called before the invocation of each test method in the class. 31 | } 32 | 33 | - (void)tearDown 34 | { 35 | // Put teardown code here. This method is called after the invocation of each test method in the class. 36 | [super tearDown]; 37 | } 38 | 39 | - (void)testExample 40 | { 41 | [self testForMemoryLeaksWithBlock:^{ 42 | nn_autofree(malloc(2)); 43 | } iterations:1e5]; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /NNKitTests/nn_isaSwizzlingTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // nn_isaSwizzlingTests.m 3 | // NNKit 4 | // 5 | // Created by Scott Perry on 09/05/13. 6 | // Copyright © 2013 Scott Perry. 7 | // 8 | // 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: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | // 12 | // 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. 13 | // 14 | 15 | #import 16 | 17 | #import 18 | #import 19 | 20 | // Class ISAGood can be used for swizzling any NSObject 21 | @protocol ISAGood - (void)foo; @end 22 | @interface ISAGood : NSObject @end 23 | @implementation ISAGood - (void)foo { NSLog(@"foooooo! "); } - (void)doesNotRecognizeSelector:(__attribute__((unused)) SEL)aSelector { NSLog(@"FAUX NOES!"); } @end 24 | 25 | 26 | // Class ISANoSharedAncestor can only be used to swizzle instances that areKindOf NSArray 27 | @protocol ISANoSharedAncestor - (void)foo; @end 28 | @interface ISANoSharedAncestor : NSArray @end 29 | @implementation ISANoSharedAncestor - (void)foo { NSLog(@"foooooo! "); } @end 30 | 31 | 32 | // Class ISANoProtocol doesn't have a corersponding protocol and cannot be used for swizzling 33 | @interface ISANoProtocol : NSObject @end 34 | @implementation ISANoProtocol - (void)foo { NSLog(@"foooooo! "); } @end 35 | 36 | 37 | // Class ISAAddsProperties adds properties to its superclass and thus cannot be used for swizzling 38 | @protocol ISAAddsProperties - (void)foo; @end 39 | @interface ISAAddsProperties : NSObject @property (nonatomic, assign) NSUInteger bar; @end 40 | @implementation ISAAddsProperties - (void)foo { NSLog(@"foooooo! "); } @end 41 | 42 | 43 | // Class ISAAddsProperties adds legal properties to its superclass 44 | @protocol ISAAddsLegalProperties @end 45 | @interface ISAAddsLegalProperties : NSObject @property (nonatomic, readonly, assign) NSUInteger bar; @end 46 | @implementation ISAAddsLegalProperties @dynamic bar; - (NSUInteger)bar { NSLog(@"foooooo! "); return 7; } @end 47 | 48 | 49 | // Class ISAAddsIvars adds ivars to its superclass and thus cannot be used for swizzling 50 | @protocol ISAAddsIvars - (void)foo; @end 51 | @interface ISAAddsIvars : NSObject { NSUInteger bar; } @end 52 | @implementation ISAAddsIvars - (void)foo { NSLog(@"foooooo! "); } @end 53 | 54 | 55 | // Class ISAExtraProtocol adds an extra protocol that the swizzled object must conform to. 56 | @protocol ISAExtraProtocol - (void)foo; @end 57 | @interface ISAExtraProtocol : NSObject @end 58 | @implementation ISAExtraProtocol - (void)foo { NSLog(@"foooooo! "); } @end 59 | 60 | // Class ISANameConflicts can be used for swizzling any NSObject and provides a class and instance method with the same selector 61 | @protocol ISANameConflicts - (BOOL)isClassMethod; + (BOOL)isClassMethod; @end 62 | @interface ISANameConflicts : NSObject @end 63 | @implementation ISANameConflicts - (BOOL)isClassMethod { return NO; } + (BOOL)isClassMethod { return YES; } @end 64 | 65 | @interface nn_isaSwizzlingTests : XCTestCase 66 | 67 | @end 68 | 69 | @implementation nn_isaSwizzlingTests 70 | 71 | - (void)testInteractionWithKVO; 72 | { 73 | #pragma message "Not even sure how to do this without making the world's biggest mess. There's no reason why it shouldn't work, but it's not tested." 74 | } 75 | 76 | - (void)testExtraProtocol; 77 | { 78 | NSObject *bar = [[NSObject alloc] init]; 79 | 80 | XCTAssertFalse([bar conformsToProtocol:@protocol(ISAExtraProtocol)], @"Object is not virgin"); 81 | 82 | XCTAssertTrue(nn_object_swizzleIsa(bar, [ISAExtraProtocol class]), @"Failed to swizzle object"); 83 | 84 | XCTAssertTrue([bar conformsToProtocol:@protocol(ISAExtraProtocol)], @"Object is not swizzled correctly"); 85 | XCTAssertTrue([bar conformsToProtocol:@protocol(NSCacheDelegate)], @"Object is missing extra protocol"); 86 | } 87 | 88 | - (void)testAddsProperties; 89 | { 90 | NSObject *bar = [[NSObject alloc] init]; 91 | 92 | XCTAssertFalse(nn_object_swizzleIsa(bar, [ISAAddsProperties class]), @"Failed to fail to swizzle object"); 93 | } 94 | 95 | - (void)testAddsLegalProperties; 96 | { 97 | NSObject *bar = [[NSObject alloc] init]; 98 | 99 | XCTAssertTrue(nn_object_swizzleIsa(bar, [ISAAddsLegalProperties class]), @"Failed to swizzle object"); 100 | XCTAssertEqual(((ISAAddsLegalProperties *)bar).bar, (NSUInteger)7, @"Oops properties"); 101 | } 102 | 103 | - (void)testAddsIvars; 104 | { 105 | NSObject *bar = [[NSObject alloc] init]; 106 | 107 | XCTAssertFalse(nn_object_swizzleIsa(bar, [ISAAddsIvars class]), @"Failed to fail to swizzle object"); 108 | } 109 | 110 | - (void)testDoubleSwizzle; 111 | { 112 | NSObject *bar = [[NSObject alloc] init]; 113 | 114 | XCTAssertFalse([bar conformsToProtocol:@protocol(ISAGood)], @"Object is not virgin"); 115 | XCTAssertFalse([bar respondsToSelector:@selector(foo)], @"Object is not virgin"); 116 | 117 | XCTAssertThrows([(id)bar foo], @"foooooo!"); 118 | XCTAssertThrows([bar doesNotRecognizeSelector:nil], @"FAUX NOES!"); 119 | 120 | XCTAssertTrue(nn_object_swizzleIsa(bar, [ISAGood class]), @"Failed to swizzle object"); 121 | XCTAssertTrue(nn_object_swizzleIsa(bar, [ISAGood class]), @"Failed to swizzle object"); 122 | 123 | XCTAssertTrue([bar conformsToProtocol:@protocol(ISAGood)], @"Object is not swizzled correctly"); 124 | 125 | XCTAssertTrue([bar respondsToSelector:@selector(foo)], @"Object is not swizzled correctly"); 126 | 127 | XCTAssertNoThrow([(id)bar foo], @"foooooo!"); 128 | XCTAssertNoThrow([bar doesNotRecognizeSelector:nil], @"FAUX NOES!"); 129 | 130 | XCTAssertEqual([bar class], [NSObject class], @"Object should report itself as still being an NSObject"); 131 | } 132 | 133 | - (void)testSharedAncestor; 134 | { 135 | NSObject *bar = [[NSObject alloc] init]; 136 | NSArray *arr = [[NSArray alloc] init]; 137 | 138 | XCTAssertFalse(nn_object_swizzleIsa(bar, [ISANoSharedAncestor class]), @"Failed to fail to swizzle object"); 139 | XCTAssertTrue(nn_object_swizzleIsa(arr, [ISANoSharedAncestor class]), @"Failed to swizzle object"); 140 | } 141 | 142 | - (void)testNoProto; 143 | { 144 | NSObject *bar = [[NSObject alloc] init]; 145 | 146 | XCTAssertTrue(nn_object_swizzleIsa(bar, [ISANoProtocol class]), @"Failed to swizzle object"); 147 | } 148 | 149 | - (void)testImplementationDetails; 150 | { 151 | NSObject *bar = [[NSObject alloc] init]; 152 | 153 | # pragma clang diagnostic push 154 | # pragma clang diagnostic ignored "-Wundeclared-selector" 155 | 156 | XCTAssertFalse([bar respondsToSelector:@selector(actualClass)], @"Object is not virgin"); 157 | XCTAssertThrows([bar performSelector:@selector(actualClass)], @"actualClass exists?"); 158 | 159 | XCTAssertTrue(nn_object_swizzleIsa(bar, [ISAGood class]), @"Failed to swizzle object"); 160 | 161 | XCTAssertTrue([bar respondsToSelector:@selector(_swizzler_actualClass)], @"Object is not swizzled correctly"); 162 | XCTAssertNoThrow([bar performSelector:@selector(_swizzler_actualClass)], @"Internal swizzle method actualClass not implemented?"); 163 | 164 | # pragma clang diagnostic pop 165 | 166 | } 167 | 168 | - (void)testGood; 169 | { 170 | NSObject *bar = [[NSObject alloc] init]; 171 | 172 | XCTAssertFalse([bar conformsToProtocol:@protocol(ISAGood)], @"Object is not virgin"); 173 | XCTAssertFalse([bar respondsToSelector:@selector(foo)], @"Object is not virgin"); 174 | 175 | XCTAssertThrows([(id)bar foo], @"foooooo!"); 176 | XCTAssertThrows([bar doesNotRecognizeSelector:nil], @"FAUX NOES!"); 177 | 178 | XCTAssertTrue(nn_object_swizzleIsa(bar, [ISAGood class]), @"Failed to swizzle object"); 179 | 180 | XCTAssertTrue([bar conformsToProtocol:@protocol(ISAGood)], @"Object is not swizzled correctly"); 181 | XCTAssertTrue([bar isKindOfClass:[ISAGood class]], @"Object is not swizzled correctly"); 182 | 183 | XCTAssertTrue([bar respondsToSelector:@selector(foo)], @"Object is not swizzled correctly"); 184 | 185 | XCTAssertNoThrow([(id)bar foo], @"foooooo!"); 186 | XCTAssertNoThrow([bar doesNotRecognizeSelector:nil], @"FAUX NOES!"); 187 | 188 | XCTAssertEqual([bar class], [NSObject class], @"Object should report itself as still being an NSObject"); 189 | } 190 | 191 | - (void)testSelectorNameConflicts; 192 | { 193 | NSObject *bar = [[NSObject alloc] init]; 194 | XCTAssertTrue(nn_object_swizzleIsa(bar, [ISANameConflicts class]), @"Failed to swizzle object"); 195 | 196 | XCTAssertFalse([(id)bar isClassMethod], @"Instance method was swizzled with the class method"); 197 | XCTAssertTrue([object_getClass(bar) isClassMethod], @"Class method was swizzled with the instance method"); 198 | XCTAssertEqual([bar class], [NSObject class], @"Object should report itself as still being an NSObject"); 199 | } 200 | 201 | @end 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NNKit 2 | ===== 3 | 4 | This library represents a collection of well-tested, robust, occasionally idiotic, but always well-meaning widgets for the Objective-C developer. 5 | 6 | At the moment it is a poorly-documented collection of code samples for my [NSMeetup talk](http://www.meetup.com/nsmeetup/events/136648202/) as well as a shared repository of solutions to common problems for [Switch](https://github.com/numist/Switch) and tmbo, the latter being a sort of promise to keep on adding, testing, and documenting the framework. 7 | 8 | The framework is broken up by purpose, the most entertaining of which are likely [Swizzling](https://github.com/numist/NNKit/tree/master/NNKit/Swizzling) and [Hacks](https://github.com/numist/NNKit/tree/master/NNKit/Hacks). 9 | --------------------------------------------------------------------------------