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