├── Reflow
├── Reflow.h
├── NSArray+Functional.h
├── NSArray+Functional.m
├── RFStore.h
├── RFAspects.h
├── RFStore.m
└── RFAspects.m
├── Reflow.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── Example
├── Example.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── project.pbxproj
└── Example
│ ├── Todo.m
│ ├── TodoTableViewController.h
│ ├── AppDelegate.h
│ ├── main.m
│ ├── Todo.h
│ ├── TodoStore.h
│ ├── Info.plist
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
│ ├── AppDelegate.m
│ ├── TodoStore.m
│ └── TodoTableViewController.m
├── LICENSE
├── .gitignore
└── README.md
/Reflow/Reflow.h:
--------------------------------------------------------------------------------
1 | //
2 | // Reflow.h
3 | // Reflow
4 | //
5 | // Created by Zepo on 22/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import "RFStore.h"
10 |
--------------------------------------------------------------------------------
/Reflow.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/Example/Todo.m:
--------------------------------------------------------------------------------
1 | //
2 | // Todo.m
3 | // Example
4 | //
5 | // Created by Zepo on 22/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import "Todo.h"
10 |
11 | @implementation Todo
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Example/Example/TodoTableViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // TodoTableViewController.h
3 | // Example
4 | //
5 | // Created by Zepo on 25/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface TodoTableViewController : UITableViewController
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Example/Example/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // Example
4 | //
5 | // Created by Zepo on 22/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 |
16 | @end
17 |
18 |
--------------------------------------------------------------------------------
/Reflow/NSArray+Functional.h:
--------------------------------------------------------------------------------
1 | //
2 | // NSArray+Functional.h
3 | // Example
4 | //
5 | // Created by Zepo on 25/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface NSArray (Functional)
12 |
13 | - (NSArray *)map:(id (^)(id value))block;
14 | - (NSArray *)filter:(BOOL (^)(id value))block;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/Example/Example/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // Example
4 | //
5 | // Created by Zepo on 22/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "AppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | @autoreleasepool {
14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Example/Example/Todo.h:
--------------------------------------------------------------------------------
1 | //
2 | // Todo.h
3 | // Example
4 | //
5 | // Created by Zepo on 22/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface Todo : NSObject
12 |
13 | @property (nonatomic, assign) NSInteger todoId;
14 | @property (nonatomic, copy) NSString *text;
15 | @property (nonatomic, assign) BOOL completed;
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/Example/Example/TodoStore.h:
--------------------------------------------------------------------------------
1 | //
2 | // TodoStore.h
3 | // Example
4 | //
5 | // Created by Zepo on 22/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import "RFStore.h"
10 |
11 | typedef NS_ENUM(NSInteger, VisibilityFilter) {
12 | VisibilityFilterShowAll,
13 | VisibilityFilterShowActive,
14 | VisibilityFilterShowCompleted
15 | };
16 |
17 | @interface TodoStore : RFStore
18 |
19 | #pragma mark - Getters
20 |
21 | - (NSArray *)visibleTodos;
22 | - (VisibilityFilter)visibilityFilter;
23 |
24 | #pragma mark - Actions
25 |
26 | - (void)actionAddTodo:(NSString *)text;
27 | - (void)actionToggleTodo:(NSInteger)todoId;
28 |
29 | - (void)actionSetVisibilityFilter:(VisibilityFilter)filter;
30 |
31 | @end
32 |
--------------------------------------------------------------------------------
/Reflow/NSArray+Functional.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSArray+Functional.m
3 | // Example
4 | //
5 | // Created by Zepo on 25/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import "NSArray+Functional.h"
10 |
11 | @implementation NSArray (Functional)
12 |
13 | - (NSArray *)map:(id (^)(id))block {
14 | NSCParameterAssert(block);
15 |
16 | NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:self.count];
17 | for (id value in self) {
18 | [array addObject:block(value)];
19 | }
20 | return array;
21 | }
22 |
23 | - (NSArray *)filter:(BOOL (^)(id))block {
24 | NSCParameterAssert(block);
25 |
26 | NSMutableArray *array = [[NSMutableArray alloc] init];
27 | for (id value in self) {
28 | if (block(value)) {
29 | [array addObject:value];
30 | }
31 | }
32 | return array;
33 | }
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Example/Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | # CocoaPods
32 | #
33 | # We recommend against adding the Pods directory to your .gitignore. However
34 | # you should judge for yourself, the pros and cons are mentioned at:
35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
36 | #
37 | # Pods/
38 |
39 | # Carthage
40 | #
41 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
42 | # Carthage/Checkouts
43 |
44 | Carthage/Build
45 |
46 | # fastlane
47 | #
48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
49 | # screenshots whenever they are needed.
50 | # For more information about the recommended setup visit:
51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
52 |
53 | fastlane/report.xml
54 | fastlane/Preview.html
55 | fastlane/screenshots
56 | fastlane/test_output
57 |
58 | # Code Injection
59 | #
60 | # After new code Injection tools there's a generated folder /iOSInjectionProject
61 | # https://github.com/johnno1962/injectionforxcode
62 |
63 | iOSInjectionProject/
64 |
65 | .DS_Store
66 |
--------------------------------------------------------------------------------
/Example/Example/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | }
88 | ],
89 | "info" : {
90 | "version" : 1,
91 | "author" : "xcode"
92 | }
93 | }
--------------------------------------------------------------------------------
/Reflow/RFStore.h:
--------------------------------------------------------------------------------
1 | //
2 | // RFStore.h
3 | // Reflow
4 | //
5 | // Created by Zepo on 19/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface RFAction : NSObject
12 |
13 | @property (nonatomic, readonly) id object;
14 | @property (nonatomic, readonly) SEL selector;
15 | @property (nonatomic, readonly) NSArray *arguments;
16 |
17 | @end
18 |
19 | @interface RFSubscription : NSObject
20 |
21 | - (void)unsubscribe;
22 |
23 | @end
24 |
25 | @interface RFStore : NSObject
26 |
27 | /**
28 | * Subscribe to all actions of all stores, including both class and instance methods.
29 | *
30 | * @param listener The block to execute after an action being performed.
31 | *
32 | * @return You must retain the returned subscription.
33 | * Calling this method will NOT retain the returned subscription internally and
34 | * once there is no strong reference to it, it will unsubscribe automatically.
35 | */
36 | + (RFSubscription *)subscribeToAllStores:(void (^)(RFAction *action))listener;
37 |
38 | /**
39 | * Subscribe to actions that are class methods.
40 | *
41 | * @param listener The block to execute after an action being performed.
42 | *
43 | * @return You must retain the returned subscription.
44 | * Calling this method will NOT retain the returned subscription internally and
45 | * once there is no strong reference to it, it will unsubscribe automatically.
46 | */
47 | + (RFSubscription *)subscribe:(void (^)(RFAction *action))listener;
48 |
49 | /**
50 | * Subscribe to actions that are instance methods.
51 | *
52 | * @param listener The block to execute after an action being performed.
53 | *
54 | * @return You must retain the returned subscription.
55 | * Calling this method will NOT retain the returned subscription internally and
56 | * once there is no strong reference to it, it will unsubscribe automatically.
57 | */
58 | - (RFSubscription *)subscribe:(void (^)(RFAction *action))listener;
59 |
60 | @end
61 |
--------------------------------------------------------------------------------
/Example/Example/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // Example
4 | //
5 | // Created by Zepo on 22/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import "AppDelegate.h"
10 |
11 | @interface AppDelegate ()
12 |
13 | @end
14 |
15 | @implementation AppDelegate
16 |
17 |
18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
19 | // Override point for customization after application launch.
20 | return YES;
21 | }
22 |
23 |
24 | - (void)applicationWillResignActive:(UIApplication *)application {
25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
27 | }
28 |
29 |
30 | - (void)applicationDidEnterBackground:(UIApplication *)application {
31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
33 | }
34 |
35 |
36 | - (void)applicationWillEnterForeground:(UIApplication *)application {
37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
38 | }
39 |
40 |
41 | - (void)applicationDidBecomeActive:(UIApplication *)application {
42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
43 | }
44 |
45 |
46 | - (void)applicationWillTerminate:(UIApplication *)application {
47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
48 | }
49 |
50 |
51 | @end
52 |
--------------------------------------------------------------------------------
/Example/Example/TodoStore.m:
--------------------------------------------------------------------------------
1 | //
2 | // TodoStore.m
3 | // Example
4 | //
5 | // Created by Zepo on 22/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import "TodoStore.h"
10 | #import "Todo.h"
11 | #import "NSArray+Functional.h"
12 |
13 | @interface TodoStore ()
14 | @property (nonatomic, assign) NSInteger nextTodoId;
15 | @property (nonatomic, copy) NSArray *todos;
16 | @property (nonatomic, assign) VisibilityFilter filter;
17 | @end
18 |
19 | @implementation TodoStore
20 |
21 | - (instancetype)init {
22 | self = [super init];
23 | if (self) {
24 | Todo *todo1 = [[Todo alloc] init];
25 | todo1.todoId = ++_nextTodoId;
26 | todo1.text = @"Reflow";
27 |
28 | Todo *todo2 = [[Todo alloc] init];
29 | todo2.todoId = ++_nextTodoId;
30 | todo2.text = @"Immutable";
31 |
32 | Todo *todo3 = [[Todo alloc] init];
33 | todo3.todoId = ++_nextTodoId;
34 | todo3.text = @"Functional";
35 |
36 | _todos = @[ todo1, todo2, todo3 ];
37 | }
38 | return self;
39 | }
40 |
41 | #pragma mark - Getters
42 |
43 | - (NSArray *)visibleTodos {
44 | switch (self.filter) {
45 | case VisibilityFilterShowAll:
46 | return self.todos;
47 | break;
48 | case VisibilityFilterShowActive:
49 | return [self.todos filter:^BOOL(Todo *value) {
50 | return !value.completed;
51 | }];
52 | break;
53 | case VisibilityFilterShowCompleted:
54 | return [self.todos filter:^BOOL(Todo *value) {
55 | return value.completed;
56 | }];
57 | break;
58 | default:
59 | NSAssert(NO, @"Unknown filter:%ld", (long)self.filter);
60 | return nil;
61 | break;
62 | }
63 | }
64 |
65 | - (VisibilityFilter)visibilityFilter {
66 | return self.filter;
67 | }
68 |
69 | #pragma mark - Actions
70 |
71 | - (void)actionAddTodo:(NSString *)text {
72 | Todo *todo = [[Todo alloc] init];
73 | todo.todoId = ++self.nextTodoId;
74 | todo.text = text;
75 | todo.completed = NO;
76 |
77 | self.todos = [self.todos arrayByAddingObject:todo];
78 | }
79 |
80 | - (void)actionToggleTodo:(NSInteger)todoId {
81 | self.todos = [self.todos map:^id(Todo *value) {
82 | if (value.todoId == todoId) {
83 | Todo *todo = [[Todo alloc] init];
84 | todo.todoId = value.todoId;
85 | todo.text = value.text;
86 | todo.completed = !value.completed;
87 | return todo;
88 | }
89 | return value;
90 | }];
91 | }
92 |
93 | - (void)actionSetVisibilityFilter:(VisibilityFilter)filter {
94 | self.filter = filter;
95 | }
96 |
97 | @end
98 |
--------------------------------------------------------------------------------
/Reflow/RFAspects.h:
--------------------------------------------------------------------------------
1 | //
2 | // Aspects.h
3 | // Aspects - A delightful, simple library for aspect oriented programming.
4 | //
5 | // Copyright (c) 2014 Peter Steinberger. Licensed under the MIT license.
6 | //
7 |
8 | #import
9 |
10 | typedef NS_OPTIONS(NSUInteger, RFAspectOptions) {
11 | AspectPositionAfter = 0, /// Called after the original implementation (default)
12 | AspectPositionInstead = 1, /// Will replace the original implementation.
13 | AspectPositionBefore = 2, /// Called before the original implementation.
14 |
15 | AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
16 | };
17 |
18 | /// Opaque Aspect Token that allows to deregister the hook.
19 | @protocol RFAspectToken
20 |
21 | /// Deregisters an aspect.
22 | /// @return YES if deregistration is successful, otherwise NO.
23 | - (BOOL)remove;
24 |
25 | @end
26 |
27 | /// The RFAspectInfo protocol is the first parameter of our block syntax.
28 | @protocol RFAspectInfo
29 |
30 | /// The instance that is currently hooked.
31 | - (id)instance;
32 |
33 | /// The original invocation of the hooked method.
34 | - (NSInvocation *)originalInvocation;
35 |
36 | /// All method arguments, boxed. This is lazily evaluated.
37 | - (NSArray *)arguments;
38 |
39 | @end
40 |
41 | /**
42 | Aspects uses Objective-C message forwarding to hook into messages. This will create some overhead. Don't add aspects to methods that are called a lot. Aspects is meant for view/controller code that is not called a 1000 times per second.
43 |
44 | Adding aspects returns an opaque token which can be used to deregister again. All calls are thread safe.
45 | */
46 | @interface NSObject (RFAspects)
47 |
48 | /// Adds a block of code before/instead/after the current `selector` for a specific class.
49 | ///
50 | /// @param block Aspects replicates the type signature of the method being hooked.
51 | /// The first parameter will be `id`, followed by all parameters of the method.
52 | /// These parameters are optional and will be filled to match the block signature.
53 | /// You can even use an empty block, or one that simple gets `id`.
54 | ///
55 | /// @note Hooking static methods is not supported.
56 | /// @return A token which allows to later deregister the aspect.
57 | + (id)rfaspect_hookSelector:(SEL)selector
58 | withOptions:(RFAspectOptions)options
59 | usingBlock:(id)block
60 | error:(NSError **)error;
61 |
62 | /// Adds a block of code before/instead/after the current `selector` for a specific instance.
63 | - (id)rfaspect_hookSelector:(SEL)selector
64 | withOptions:(RFAspectOptions)options
65 | usingBlock:(id)block
66 | error:(NSError **)error;
67 |
68 | @end
69 |
70 |
71 | typedef NS_ENUM(NSUInteger, RFAspectErrorCode) {
72 | AspectErrorSelectorBlacklisted, /// Selectors like release, retain, autorelease are blacklisted.
73 | AspectErrorDoesNotRespondToSelector, /// Selector could not be found.
74 | AspectErrorSelectorDeallocPosition, /// When hooking dealloc, only AspectPositionBefore is allowed.
75 | AspectErrorSelectorAlreadyHookedInClassHierarchy, /// Statically hooking the same method in subclasses is not allowed.
76 | AspectErrorFailedToAllocateClassPair, /// The runtime failed creating a class pair.
77 | AspectErrorMissingBlockSignature, /// The block misses compile time signature info and can't be called.
78 | AspectErrorIncompatibleBlockSignature, /// The block signature does not match the method or is too large.
79 |
80 | AspectErrorRemoveObjectAlreadyDeallocated = 100 /// (for removing) The object hooked is already deallocated.
81 | };
82 |
83 | extern NSString *const RFAspectErrorDomain;
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Reflow
2 | A unidirectional data flow framework for Objective-C inspired by Flux, Redux and Vue.
3 |
4 | ## Motivation
5 |
6 | When writing Objective-C code, we are used to defining properties of models as `nonatomic`. However, doing this may lead to crash in multithread environment. For example, calling the method `methodA` below the app will crash with a `EXC_BAD_ACCESS` exception.
7 |
8 | ```objective-c
9 | // TodoStore.h
10 | @interface TodoStore : NSObject
11 | @property (nonatomic, copy) NSArray *todos;
12 | @end
13 |
14 | // xxx.m
15 | - (void)methodA
16 | {
17 | TodoStore *todoStore = [[TodoStore alloc] init];
18 |
19 | dispatch_async(dispatch_get_global_queue(0, 0), ^{
20 | while (1) {
21 | todoStore.todos = [[NSArray alloc] initWithObjects:@1, @2, @3, nil];
22 | }
23 | });
24 |
25 | dispatch_async(dispatch_get_global_queue(0, 0), ^{
26 | while (1) {
27 | NSLog(@"%@", todoStore.todos);
28 | }
29 | });
30 | }
31 | ```
32 |
33 | A simple approach to the crash here might be defining the properties as `atomic`. However this approach doesn't solve other issues like race condition.
34 |
35 | Another danger is not keeping views up to date since propeties are mutable and we can change them any where throughout the app. If the `todos` property from the above example is binding to the cells of a table view, adding or removing a todo item and forgetting to notify the table view to do `reloadData` will cause another crash with a `NSInternalInconsistencyException` in the table view.
36 |
37 | ## Core Concepts
38 |
39 | Reflow solves the above problems by imposing a more principled architecture, with the following concepts.
40 |
41 | ### Store
42 |
43 | Like Redux, the state of your whole application is stored in a certain layer, **store**, and you concentrate your model update logic in the store too. Note that store in Reflow is an abstract concept. The state in the store can be in a database or in memory or both. What really concerns is that we only have a single source of truth.
44 |
45 | As our application grows in scale, the whole state can get really bloated. Reflow allows us to divide our store into modules to help with that. Each store module is a subclass of `RFStore`.
46 |
47 | ```objective-c
48 | @interface TodoStore : RFStore
49 |
50 | #pragma mark - Getters
51 |
52 | - (NSArray *)visibleTodos;
53 | - (VisibilityFilter)visibilityFilter;
54 |
55 | #pragma mark - Actions
56 |
57 | - (void)actionAddTodo:(NSString *)text;
58 | - (void)actionToggleTodo:(NSInteger)todoId;
59 |
60 | - (void)actionSetVisibilityFilter:(VisibilityFilter)filter;
61 |
62 | @end
63 | ```
64 |
65 | ### Actions
66 |
67 | Actions are defined as normal methods on store modules, except with a name prefix "action". Reflow will do some magic tricks with that.
68 |
69 | ```objective-c
70 | @implementation TodoStore
71 |
72 | ...
73 |
74 | #pragma mark - Actions
75 |
76 | - (void)actionAddTodo:(NSString *)text {
77 | Todo *todo = ...
78 |
79 | self.todos = [self.todos arrayByAddingObject:todo];
80 | }
81 |
82 | - (void)actionToggleTodo:(NSInteger)todoId {
83 | self.todos = [self.todos map:^id(Todo *value) {
84 | if (value.todoId == todoId) {
85 | Todo *todo = ...
86 | return todo;
87 | }
88 | return value;
89 | }];
90 | }
91 |
92 | - (void)actionSetVisibilityFilter:(VisibilityFilter)filter {
93 | self.filter = filter;
94 | }
95 |
96 | @end
97 | ```
98 |
99 | ### Subscriptions
100 |
101 | When subclassing `RFStore`, we can subscribe to all actions on a store module.
102 |
103 | ```objective-c
104 | @implementation TodoTableViewController
105 |
106 | - (void)viewDidLoad {
107 | [super viewDidLoad];
108 |
109 | self.todoStore = [[TodoStore alloc] init];
110 | self.todos = [self.todoStore visibleTodos];
111 | self.filterButton.title = [self stringFromVisibilityFilter:[self.todoStore visibilityFilter]];
112 |
113 | self.subscription = [self.todoStore subscribe:^(RFAction *action) {
114 | if (action.selector == @selector(actionSetVisibilityFilter:)) {
115 | self.filterButton.title = [self stringFromVisibilityFilter:[self.todoStore visibilityFilter]];
116 | }
117 | self.todos = [self.todoStore visibleTodos];
118 | [self.tableView reloadData];
119 | }];
120 | }
121 |
122 | ...
123 | @end
124 | ```
125 |
126 | On completion of each call of a action method, a store module will execute the block passed in `subscribe:`, passing in an instance of `RFAction` as a parameter of the block. Each instance of `RFAction` contains infomation like the `object` that the action method is sent to, the `selector` of the action method and the `arguments` that are passed in to the action method.
127 |
128 | ```objective-c
129 | @interface RFAction : NSObject
130 |
131 | @property (nonatomic, readonly) id object;
132 | @property (nonatomic, readonly) SEL selector;
133 | @property (nonatomic, readonly) NSArray *arguments;
134 |
135 | @end
136 | ```
137 |
138 | Note that we should retain the returned `subscription` when calling `subscribe:`. Reflow will not retain it internally and when all strong references to the `subscription` are gone, the `subscription` will call `unsubscribe` automatically.
139 |
140 | We can subscribe to a single store module multiple times and we can subcribe to all actions of all store modules.
141 | ```objective-c
142 | [RFStore subscribeToAllStores:xxx];
143 | ```
144 |
145 | ## Principles
146 |
147 | Reflow is more of a pattern rather than a formal framework. It can be described in the following principles:
148 |
149 | * Immutable model
150 | * Single source of truth
151 | * Centralized model update
152 |
153 | ## Installation
154 |
155 | Just copy the source files into your project.
156 |
157 | Required
158 | * RFAspects.h/m
159 | * RFStore.h/m
160 |
161 | Optional
162 | * NSArray+Functional.h/m
163 |
--------------------------------------------------------------------------------
/Example/Example/TodoTableViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // TodoTableViewController.m
3 | // Example
4 | //
5 | // Created by Zepo on 25/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import "TodoTableViewController.h"
10 | #import "Todo.h"
11 | #import "TodoStore.h"
12 |
13 | @interface TodoTableViewController ()
14 | @property (nonatomic, strong) TodoStore *todoStore;
15 | @property (nonatomic, copy) NSArray *todos;
16 | @property (weak, nonatomic) IBOutlet UIBarButtonItem *filterButton;
17 |
18 | @property (nonatomic, strong) RFSubscription *subscription;
19 | @end
20 |
21 | @implementation TodoTableViewController
22 |
23 | - (void)viewDidLoad {
24 | [super viewDidLoad];
25 |
26 | self.todoStore = [[TodoStore alloc] init];
27 | self.todos = [self.todoStore visibleTodos];
28 | self.filterButton.title = [self stringFromVisibilityFilter:[self.todoStore visibilityFilter]];
29 |
30 | __weak typeof(self) weakSelf = self;
31 | self.subscription = [self.todoStore subscribe:^(RFAction *action) {
32 | __strong typeof(self) strongSelf = weakSelf;
33 | if (action.selector == @selector(actionSetVisibilityFilter:)) {
34 | strongSelf.filterButton.title = [strongSelf stringFromVisibilityFilter:[strongSelf.todoStore visibilityFilter]];
35 | }
36 | strongSelf.todos = [strongSelf.todoStore visibleTodos];
37 | [strongSelf.tableView reloadData];
38 | }];
39 | }
40 |
41 | - (NSString *)stringFromVisibilityFilter:(VisibilityFilter)filter {
42 | switch (filter) {
43 | case VisibilityFilterShowAll:
44 | return @"All";
45 | break;
46 | case VisibilityFilterShowActive:
47 | return @"Active";
48 | break;
49 | case VisibilityFilterShowCompleted:
50 | return @"Completed";
51 | break;
52 | default:
53 | NSAssert(NO, @"Unknown filter:%ld", (long)filter);
54 | return nil;
55 | break;
56 | }
57 | }
58 |
59 | - (void)didReceiveMemoryWarning {
60 | [super didReceiveMemoryWarning];
61 | // Dispose of any resources that can be recreated.
62 | }
63 |
64 | #pragma mark - Table view data source
65 |
66 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
67 | return 1;
68 | }
69 |
70 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
71 | return self.todos.count;
72 | }
73 |
74 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
75 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"reuseIdentifier" forIndexPath:indexPath];
76 |
77 | // Configure the cell...
78 | Todo *todo = [self.todos objectAtIndex:indexPath.row];
79 | cell.textLabel.text = todo.text;
80 | cell.accessoryType = todo.completed ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
81 |
82 | return cell;
83 | }
84 |
85 | #pragma mark - UITableViewDelegate
86 |
87 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
88 | Todo *todo = [self.todos objectAtIndex:indexPath.row];
89 | [self.todoStore actionToggleTodo:todo.todoId];
90 | }
91 |
92 | #pragma mark - Actions
93 |
94 | - (IBAction)addTodo:(id)sender {
95 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Add todo"
96 | message:nil
97 | preferredStyle:UIAlertControllerStyleAlert];
98 | [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
99 | textField.text = @"New todo";
100 | }];
101 |
102 | __weak UIAlertController *weakAlert = alert;
103 | UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"OK"
104 | style:UIAlertActionStyleDefault
105 | handler:^(UIAlertAction * action) {
106 | __strong UIAlertController *alert = weakAlert;
107 | UITextField *textField = alert.textFields.firstObject;
108 | [self.todoStore actionAddTodo:textField.text];
109 | }];
110 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel"
111 | style:UIAlertActionStyleCancel
112 | handler:^(UIAlertAction * action) {
113 | }];
114 |
115 | [alert addAction:defaultAction];
116 | [alert addAction:cancelAction];
117 | [self presentViewController:alert animated:YES completion:^ {
118 | UITextField *textField = alert.textFields.firstObject;
119 | textField.selectedTextRange = [textField textRangeFromPosition:[textField beginningOfDocument] toPosition:[textField endOfDocument]];
120 | }];
121 | }
122 |
123 | - (IBAction)changeFilter:(UIBarButtonItem *)sender {
124 | VisibilityFilter filter = [self.todoStore visibilityFilter];
125 | switch (filter) {
126 | case VisibilityFilterShowAll:
127 | [self.todoStore actionSetVisibilityFilter:VisibilityFilterShowActive];
128 | break;
129 | case VisibilityFilterShowActive:
130 | [self.todoStore actionSetVisibilityFilter:VisibilityFilterShowCompleted];
131 | break;
132 | case VisibilityFilterShowCompleted:
133 | [self.todoStore actionSetVisibilityFilter:VisibilityFilterShowAll];
134 | break;
135 | default:
136 | NSAssert(NO, @"Unknown filter:%ld", (long)filter);
137 | break;
138 | }
139 | }
140 |
141 | @end
142 |
--------------------------------------------------------------------------------
/Example/Example/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/Reflow/RFStore.m:
--------------------------------------------------------------------------------
1 | //
2 | // RFStore.m
3 | // Reflow
4 | //
5 | // Created by Zepo on 19/09/2017.
6 | // Copyright © 2017 Zepo. All rights reserved.
7 | //
8 |
9 | #import "RFStore.h"
10 | #import "RFAspects.h"
11 | #import
12 |
13 | static const void * const kListernersKey = &kListernersKey;
14 |
15 | @interface RFAction ()
16 |
17 | - (instancetype)initWithObject:(id)object selector:(SEL)selector arguments:(NSArray *)arguments;
18 |
19 | @end
20 |
21 | @interface RFSubscription ()
22 | @property (nonatomic, weak) id object;
23 | @property (nonatomic, strong) void (^block)(RFAction *);
24 | @end
25 |
26 | #pragma mark - RFStore
27 |
28 | @implementation RFStore
29 |
30 | #pragma mark - Public
31 |
32 | + (RFSubscription *)subscribeToAllStores:(void (^)(RFAction *))listener {
33 | NSCParameterAssert(listener);
34 |
35 | @autoreleasepool {
36 | Class class = [RFStore class];
37 | static const void * const kHasHookedKey = &kHasHookedKey;
38 | @synchronized(class) {
39 | id hasHooked = objc_getAssociatedObject(class, kHasHookedKey);
40 | if (!hasHooked) {
41 | NSArray *subclasses = [RFStore subclassesOfClass:class];
42 | for (Class subclass in subclasses) {
43 | [RFStore hookActionMethodsIfNeededForClass:subclass];
44 | [RFStore hookActionMethodsIfNeededForClass:object_getClass(subclass)];
45 | }
46 | objc_setAssociatedObject(class, kHasHookedKey, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
47 | }
48 | }
49 |
50 | return [RFStore subscriptionWithObject:class listener:listener];
51 | }
52 | }
53 |
54 | + (RFSubscription *)subscribe:(void (^)(RFAction *))listener {
55 | NSCParameterAssert(listener);
56 |
57 | if (self == [RFStore class]) {
58 | NSAssert(NO, @"Should be called on subclasses.");
59 | return nil;
60 | }
61 |
62 | @autoreleasepool {
63 | return [RFStore subscribeToObject:self listener:listener];
64 | }
65 | }
66 |
67 | - (RFSubscription *)subscribe:(void (^)(RFAction *))listener {
68 | NSCParameterAssert(listener);
69 |
70 | if ([self class] == [RFStore class]) {
71 | NSAssert(NO, @"Should be called on instances of subclasses.");
72 | return nil;
73 | }
74 | @autoreleasepool {
75 | return [RFStore subscribeToObject:self listener:listener];
76 | }
77 | }
78 |
79 | #pragma mark - Private
80 |
81 | + (NSArray *)subclassesOfClass:(Class)parentClass {
82 | int numClasses = objc_getClassList(NULL, 0);
83 | Class *classes = NULL;
84 |
85 | classes = (Class *)malloc(sizeof(Class) * numClasses);
86 | numClasses = objc_getClassList(classes, numClasses);
87 |
88 | NSMutableArray *result = [[NSMutableArray alloc] init];
89 | for (NSInteger i = 0; i < numClasses; ++i) {
90 | Class superClass = classes[i];
91 | do {
92 | superClass = class_getSuperclass(superClass);
93 | } while(superClass && superClass != parentClass);
94 |
95 | if (superClass == nil) {
96 | continue;
97 | }
98 |
99 | [result addObject:classes[i]];
100 | }
101 |
102 | free(classes);
103 |
104 | return result;
105 | }
106 |
107 | + (void)hookActionMethodsIfNeededForClass:(Class)class {
108 | static const void * const kHasHookedKey = &kHasHookedKey;
109 | @synchronized(class) {
110 | id hasHooked = objc_getAssociatedObject(class, kHasHookedKey);
111 | if (!hasHooked) {
112 | unsigned int outCount = 0;
113 | Method *methods = class_copyMethodList(class, &outCount);
114 | for (unsigned int i = 0; i < outCount; ++i) {
115 | Method method = methods[i];
116 | SEL selector = method_getName(method);
117 | NSString *methodName = NSStringFromSelector(selector);
118 | if (![methodName hasPrefix:@"action"]) {
119 | continue;
120 | }
121 |
122 | [RFStore registerActionForClass:class selector:selector];
123 | }
124 | objc_setAssociatedObject(class, kHasHookedKey, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
125 | }
126 | }
127 | }
128 |
129 | + (void)registerActionForClass:(Class)class selector:(SEL)selector {
130 | [class rfaspect_hookSelector:selector
131 | withOptions:AspectPositionAfter
132 | usingBlock:^(id aspectInfo) {
133 | RFAction *action = [[RFAction alloc] initWithObject:aspectInfo.instance
134 | selector:selector
135 | arguments:aspectInfo.arguments];
136 |
137 | NSArray *globalListeners = [objc_getAssociatedObject([RFStore class], kListernersKey) allObjects];
138 | NSArray *listeners = [objc_getAssociatedObject(action.object, kListernersKey) allObjects];
139 | dispatch_async(dispatch_get_main_queue(), ^{
140 | for (RFSubscription *subscription in globalListeners) {
141 | subscription.block(action);
142 | }
143 | for (RFSubscription *subscription in listeners) {
144 | subscription.block(action);
145 | }
146 | });
147 | }
148 | error:nil];
149 | }
150 |
151 | + (RFSubscription *)subscriptionWithObject:(id)object listener:(void (^)(RFAction *))listener {
152 | RFSubscription *subscription = [[RFSubscription alloc] init];
153 | subscription.object = object;
154 | subscription.block = listener;
155 | [RFStore associateObject:object withSubscription:subscription];
156 | return subscription;
157 | }
158 |
159 | + (void)associateObject:(id)object withSubscription:(RFSubscription *)subscription {
160 | @synchronized(object) {
161 | NSPointerArray *listeners = objc_getAssociatedObject(object, kListernersKey);
162 | if (!listeners) {
163 | listeners = [NSPointerArray weakObjectsPointerArray];
164 | objc_setAssociatedObject(object, kListernersKey, listeners, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
165 | }
166 | [listeners compact];
167 | [listeners addPointer:(void *)subscription];
168 | }
169 | }
170 |
171 | + (RFSubscription *)subscribeToObject:(id)object listener:(void (^)(RFAction *))listener {
172 | [RFStore hookActionMethodsIfNeededForClass:object_getClass(object)];
173 |
174 | return [RFStore subscriptionWithObject:object listener:listener];
175 | }
176 |
177 | @end
178 |
179 | #pragma mark - RFAction
180 |
181 | @implementation RFAction
182 |
183 | - (instancetype)initWithObject:(id)object selector:(SEL)selector arguments:(NSArray *)arguments {
184 | self = [super init];
185 | if (self) {
186 | _object = object;
187 | _selector = selector;
188 | _arguments = arguments;
189 | }
190 | return self;
191 | }
192 |
193 | @end
194 |
195 | #pragma mark - RFSubscription
196 |
197 | @implementation RFSubscription
198 |
199 | - (void)unsubscribe {
200 | id object = self.object;
201 | if (object) {
202 | @synchronized(object) {
203 | NSPointerArray *listeners = objc_getAssociatedObject(object, kListernersKey);
204 | NSInteger index = 0;
205 | for (RFSubscription *subscription in listeners) {
206 | if (subscription == self) {
207 | break;
208 | }
209 | ++index;
210 | }
211 | [listeners removePointerAtIndex:index];
212 | }
213 | }
214 | }
215 |
216 | @end
217 |
--------------------------------------------------------------------------------
/Reflow.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | B33673E11F74EAC400F22CB9 /* Reflow.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B33673DE1F74EAC400F22CB9 /* Reflow.h */; };
11 | B33673EB1F74EAED00F22CB9 /* RFAspects.m in Sources */ = {isa = PBXBuildFile; fileRef = B33673E81F74EAEC00F22CB9 /* RFAspects.m */; };
12 | B33673EC1F74EAED00F22CB9 /* RFStore.m in Sources */ = {isa = PBXBuildFile; fileRef = B33673E91F74EAED00F22CB9 /* RFStore.m */; };
13 | /* End PBXBuildFile section */
14 |
15 | /* Begin PBXCopyFilesBuildPhase section */
16 | B33673D91F74EAC400F22CB9 /* CopyFiles */ = {
17 | isa = PBXCopyFilesBuildPhase;
18 | buildActionMask = 2147483647;
19 | dstPath = "include/$(PRODUCT_NAME)";
20 | dstSubfolderSpec = 16;
21 | files = (
22 | B33673E11F74EAC400F22CB9 /* Reflow.h in CopyFiles */,
23 | );
24 | runOnlyForDeploymentPostprocessing = 0;
25 | };
26 | /* End PBXCopyFilesBuildPhase section */
27 |
28 | /* Begin PBXFileReference section */
29 | B33673DB1F74EAC400F22CB9 /* libReflow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReflow.a; sourceTree = BUILT_PRODUCTS_DIR; };
30 | B33673DE1F74EAC400F22CB9 /* Reflow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Reflow.h; sourceTree = ""; };
31 | B33673E71F74EAEC00F22CB9 /* RFStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RFStore.h; sourceTree = ""; };
32 | B33673E81F74EAEC00F22CB9 /* RFAspects.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RFAspects.m; sourceTree = ""; };
33 | B33673E91F74EAED00F22CB9 /* RFStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RFStore.m; sourceTree = ""; };
34 | B33673EA1F74EAED00F22CB9 /* RFAspects.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RFAspects.h; sourceTree = ""; };
35 | /* End PBXFileReference section */
36 |
37 | /* Begin PBXFrameworksBuildPhase section */
38 | B33673D81F74EAC400F22CB9 /* Frameworks */ = {
39 | isa = PBXFrameworksBuildPhase;
40 | buildActionMask = 2147483647;
41 | files = (
42 | );
43 | runOnlyForDeploymentPostprocessing = 0;
44 | };
45 | /* End PBXFrameworksBuildPhase section */
46 |
47 | /* Begin PBXGroup section */
48 | B33673D21F74EAC400F22CB9 = {
49 | isa = PBXGroup;
50 | children = (
51 | B33673DD1F74EAC400F22CB9 /* Reflow */,
52 | B33673DC1F74EAC400F22CB9 /* Products */,
53 | );
54 | sourceTree = "";
55 | };
56 | B33673DC1F74EAC400F22CB9 /* Products */ = {
57 | isa = PBXGroup;
58 | children = (
59 | B33673DB1F74EAC400F22CB9 /* libReflow.a */,
60 | );
61 | name = Products;
62 | sourceTree = "";
63 | };
64 | B33673DD1F74EAC400F22CB9 /* Reflow */ = {
65 | isa = PBXGroup;
66 | children = (
67 | B33673EA1F74EAED00F22CB9 /* RFAspects.h */,
68 | B33673E81F74EAEC00F22CB9 /* RFAspects.m */,
69 | B33673E71F74EAEC00F22CB9 /* RFStore.h */,
70 | B33673E91F74EAED00F22CB9 /* RFStore.m */,
71 | B33673DE1F74EAC400F22CB9 /* Reflow.h */,
72 | );
73 | path = Reflow;
74 | sourceTree = "";
75 | };
76 | /* End PBXGroup section */
77 |
78 | /* Begin PBXNativeTarget section */
79 | B33673DA1F74EAC400F22CB9 /* Reflow */ = {
80 | isa = PBXNativeTarget;
81 | buildConfigurationList = B33673E41F74EAC400F22CB9 /* Build configuration list for PBXNativeTarget "Reflow" */;
82 | buildPhases = (
83 | B33673D71F74EAC400F22CB9 /* Sources */,
84 | B33673D81F74EAC400F22CB9 /* Frameworks */,
85 | B33673D91F74EAC400F22CB9 /* CopyFiles */,
86 | );
87 | buildRules = (
88 | );
89 | dependencies = (
90 | );
91 | name = Reflow;
92 | productName = Reflow;
93 | productReference = B33673DB1F74EAC400F22CB9 /* libReflow.a */;
94 | productType = "com.apple.product-type.library.static";
95 | };
96 | /* End PBXNativeTarget section */
97 |
98 | /* Begin PBXProject section */
99 | B33673D31F74EAC400F22CB9 /* Project object */ = {
100 | isa = PBXProject;
101 | attributes = {
102 | LastUpgradeCheck = 0900;
103 | ORGANIZATIONNAME = Zepo;
104 | TargetAttributes = {
105 | B33673DA1F74EAC400F22CB9 = {
106 | CreatedOnToolsVersion = 9.0;
107 | ProvisioningStyle = Automatic;
108 | };
109 | };
110 | };
111 | buildConfigurationList = B33673D61F74EAC400F22CB9 /* Build configuration list for PBXProject "Reflow" */;
112 | compatibilityVersion = "Xcode 8.0";
113 | developmentRegion = en;
114 | hasScannedForEncodings = 0;
115 | knownRegions = (
116 | en,
117 | );
118 | mainGroup = B33673D21F74EAC400F22CB9;
119 | productRefGroup = B33673DC1F74EAC400F22CB9 /* Products */;
120 | projectDirPath = "";
121 | projectRoot = "";
122 | targets = (
123 | B33673DA1F74EAC400F22CB9 /* Reflow */,
124 | );
125 | };
126 | /* End PBXProject section */
127 |
128 | /* Begin PBXSourcesBuildPhase section */
129 | B33673D71F74EAC400F22CB9 /* Sources */ = {
130 | isa = PBXSourcesBuildPhase;
131 | buildActionMask = 2147483647;
132 | files = (
133 | B33673EC1F74EAED00F22CB9 /* RFStore.m in Sources */,
134 | B33673EB1F74EAED00F22CB9 /* RFAspects.m in Sources */,
135 | );
136 | runOnlyForDeploymentPostprocessing = 0;
137 | };
138 | /* End PBXSourcesBuildPhase section */
139 |
140 | /* Begin XCBuildConfiguration section */
141 | B33673E21F74EAC400F22CB9 /* Debug */ = {
142 | isa = XCBuildConfiguration;
143 | buildSettings = {
144 | ALWAYS_SEARCH_USER_PATHS = NO;
145 | CLANG_ANALYZER_NONNULL = YES;
146 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
147 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
148 | CLANG_CXX_LIBRARY = "libc++";
149 | CLANG_ENABLE_MODULES = YES;
150 | CLANG_ENABLE_OBJC_ARC = YES;
151 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
152 | CLANG_WARN_BOOL_CONVERSION = YES;
153 | CLANG_WARN_COMMA = YES;
154 | CLANG_WARN_CONSTANT_CONVERSION = YES;
155 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
156 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
157 | CLANG_WARN_EMPTY_BODY = YES;
158 | CLANG_WARN_ENUM_CONVERSION = YES;
159 | CLANG_WARN_INFINITE_RECURSION = YES;
160 | CLANG_WARN_INT_CONVERSION = YES;
161 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
162 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
163 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
164 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
165 | CLANG_WARN_STRICT_PROTOTYPES = YES;
166 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
167 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
168 | CLANG_WARN_UNREACHABLE_CODE = YES;
169 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
170 | CODE_SIGN_IDENTITY = "iPhone Developer";
171 | COPY_PHASE_STRIP = NO;
172 | DEBUG_INFORMATION_FORMAT = dwarf;
173 | ENABLE_STRICT_OBJC_MSGSEND = YES;
174 | ENABLE_TESTABILITY = YES;
175 | GCC_C_LANGUAGE_STANDARD = gnu11;
176 | GCC_DYNAMIC_NO_PIC = NO;
177 | GCC_NO_COMMON_BLOCKS = YES;
178 | GCC_OPTIMIZATION_LEVEL = 0;
179 | GCC_PREPROCESSOR_DEFINITIONS = (
180 | "DEBUG=1",
181 | "$(inherited)",
182 | );
183 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
184 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
185 | GCC_WARN_UNDECLARED_SELECTOR = YES;
186 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
187 | GCC_WARN_UNUSED_FUNCTION = YES;
188 | GCC_WARN_UNUSED_VARIABLE = YES;
189 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
190 | MTL_ENABLE_DEBUG_INFO = YES;
191 | ONLY_ACTIVE_ARCH = YES;
192 | SDKROOT = iphoneos;
193 | };
194 | name = Debug;
195 | };
196 | B33673E31F74EAC400F22CB9 /* Release */ = {
197 | isa = XCBuildConfiguration;
198 | buildSettings = {
199 | ALWAYS_SEARCH_USER_PATHS = NO;
200 | CLANG_ANALYZER_NONNULL = YES;
201 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
202 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
203 | CLANG_CXX_LIBRARY = "libc++";
204 | CLANG_ENABLE_MODULES = YES;
205 | CLANG_ENABLE_OBJC_ARC = YES;
206 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
207 | CLANG_WARN_BOOL_CONVERSION = YES;
208 | CLANG_WARN_COMMA = YES;
209 | CLANG_WARN_CONSTANT_CONVERSION = YES;
210 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
211 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
212 | CLANG_WARN_EMPTY_BODY = YES;
213 | CLANG_WARN_ENUM_CONVERSION = YES;
214 | CLANG_WARN_INFINITE_RECURSION = YES;
215 | CLANG_WARN_INT_CONVERSION = YES;
216 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
217 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
218 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
219 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
220 | CLANG_WARN_STRICT_PROTOTYPES = YES;
221 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
222 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
223 | CLANG_WARN_UNREACHABLE_CODE = YES;
224 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
225 | CODE_SIGN_IDENTITY = "iPhone Developer";
226 | COPY_PHASE_STRIP = NO;
227 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
228 | ENABLE_NS_ASSERTIONS = NO;
229 | ENABLE_STRICT_OBJC_MSGSEND = YES;
230 | GCC_C_LANGUAGE_STANDARD = gnu11;
231 | GCC_NO_COMMON_BLOCKS = YES;
232 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
233 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
234 | GCC_WARN_UNDECLARED_SELECTOR = YES;
235 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
236 | GCC_WARN_UNUSED_FUNCTION = YES;
237 | GCC_WARN_UNUSED_VARIABLE = YES;
238 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
239 | MTL_ENABLE_DEBUG_INFO = NO;
240 | SDKROOT = iphoneos;
241 | VALIDATE_PRODUCT = YES;
242 | };
243 | name = Release;
244 | };
245 | B33673E51F74EAC400F22CB9 /* Debug */ = {
246 | isa = XCBuildConfiguration;
247 | buildSettings = {
248 | CODE_SIGN_STYLE = Automatic;
249 | DEVELOPMENT_TEAM = 6A9AHK6F98;
250 | OTHER_LDFLAGS = "-ObjC";
251 | PRODUCT_NAME = "$(TARGET_NAME)";
252 | SKIP_INSTALL = YES;
253 | TARGETED_DEVICE_FAMILY = "1,2";
254 | };
255 | name = Debug;
256 | };
257 | B33673E61F74EAC400F22CB9 /* Release */ = {
258 | isa = XCBuildConfiguration;
259 | buildSettings = {
260 | CODE_SIGN_STYLE = Automatic;
261 | DEVELOPMENT_TEAM = 6A9AHK6F98;
262 | OTHER_LDFLAGS = "-ObjC";
263 | PRODUCT_NAME = "$(TARGET_NAME)";
264 | SKIP_INSTALL = YES;
265 | TARGETED_DEVICE_FAMILY = "1,2";
266 | };
267 | name = Release;
268 | };
269 | /* End XCBuildConfiguration section */
270 |
271 | /* Begin XCConfigurationList section */
272 | B33673D61F74EAC400F22CB9 /* Build configuration list for PBXProject "Reflow" */ = {
273 | isa = XCConfigurationList;
274 | buildConfigurations = (
275 | B33673E21F74EAC400F22CB9 /* Debug */,
276 | B33673E31F74EAC400F22CB9 /* Release */,
277 | );
278 | defaultConfigurationIsVisible = 0;
279 | defaultConfigurationName = Release;
280 | };
281 | B33673E41F74EAC400F22CB9 /* Build configuration list for PBXNativeTarget "Reflow" */ = {
282 | isa = XCConfigurationList;
283 | buildConfigurations = (
284 | B33673E51F74EAC400F22CB9 /* Debug */,
285 | B33673E61F74EAC400F22CB9 /* Release */,
286 | );
287 | defaultConfigurationIsVisible = 0;
288 | defaultConfigurationName = Release;
289 | };
290 | /* End XCConfigurationList section */
291 | };
292 | rootObject = B33673D31F74EAC400F22CB9 /* Project object */;
293 | }
294 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | B33673FB1F74EC3200F22CB9 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B33673FA1F74EC3200F22CB9 /* AppDelegate.m */; };
11 | B33674011F74EC3200F22CB9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B33673FF1F74EC3200F22CB9 /* Main.storyboard */; };
12 | B33674031F74EC3200F22CB9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B33674021F74EC3200F22CB9 /* Assets.xcassets */; };
13 | B33674061F74EC3200F22CB9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B33674041F74EC3200F22CB9 /* LaunchScreen.storyboard */; };
14 | B33674091F74EC3200F22CB9 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B33674081F74EC3200F22CB9 /* main.m */; };
15 | B33674171F74F03F00F22CB9 /* TodoStore.m in Sources */ = {isa = PBXBuildFile; fileRef = B33674161F74F03F00F22CB9 /* TodoStore.m */; };
16 | B33674181F74F15000F22CB9 /* RFStore.m in Sources */ = {isa = PBXBuildFile; fileRef = B33674141F74EC6600F22CB9 /* RFStore.m */; };
17 | B33674191F74F15300F22CB9 /* RFAspects.m in Sources */ = {isa = PBXBuildFile; fileRef = B33674121F74EC6600F22CB9 /* RFAspects.m */; };
18 | B336741C1F7504D900F22CB9 /* Todo.m in Sources */ = {isa = PBXBuildFile; fileRef = B336741B1F7504D900F22CB9 /* Todo.m */; };
19 | B37BAC0D1F789D43003702E1 /* NSArray+Functional.m in Sources */ = {isa = PBXBuildFile; fileRef = B37BAC0C1F789D43003702E1 /* NSArray+Functional.m */; };
20 | B39AF4841F78A107004BA2E8 /* TodoTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B39AF4831F78A107004BA2E8 /* TodoTableViewController.m */; };
21 | /* End PBXBuildFile section */
22 |
23 | /* Begin PBXFileReference section */
24 | B33673F61F74EC3200F22CB9 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
25 | B33673F91F74EC3200F22CB9 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
26 | B33673FA1F74EC3200F22CB9 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
27 | B33674001F74EC3200F22CB9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
28 | B33674021F74EC3200F22CB9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
29 | B33674051F74EC3200F22CB9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
30 | B33674071F74EC3200F22CB9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
31 | B33674081F74EC3200F22CB9 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
32 | B33674101F74EC6600F22CB9 /* Reflow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Reflow.h; sourceTree = ""; };
33 | B33674111F74EC6600F22CB9 /* RFAspects.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RFAspects.h; sourceTree = ""; };
34 | B33674121F74EC6600F22CB9 /* RFAspects.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RFAspects.m; sourceTree = ""; };
35 | B33674131F74EC6600F22CB9 /* RFStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RFStore.h; sourceTree = ""; };
36 | B33674141F74EC6600F22CB9 /* RFStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RFStore.m; sourceTree = ""; };
37 | B33674151F74F03F00F22CB9 /* TodoStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TodoStore.h; sourceTree = ""; };
38 | B33674161F74F03F00F22CB9 /* TodoStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TodoStore.m; sourceTree = ""; };
39 | B336741A1F7504D900F22CB9 /* Todo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Todo.h; sourceTree = ""; };
40 | B336741B1F7504D900F22CB9 /* Todo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Todo.m; sourceTree = ""; };
41 | B37BAC0B1F789D43003702E1 /* NSArray+Functional.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSArray+Functional.h"; sourceTree = ""; };
42 | B37BAC0C1F789D43003702E1 /* NSArray+Functional.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Functional.m"; sourceTree = ""; };
43 | B39AF4821F78A107004BA2E8 /* TodoTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TodoTableViewController.h; sourceTree = ""; };
44 | B39AF4831F78A107004BA2E8 /* TodoTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TodoTableViewController.m; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | B33673F31F74EC3200F22CB9 /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | B33673ED1F74EC3200F22CB9 = {
59 | isa = PBXGroup;
60 | children = (
61 | B33673F81F74EC3200F22CB9 /* Example */,
62 | B33673F71F74EC3200F22CB9 /* Products */,
63 | );
64 | sourceTree = "";
65 | };
66 | B33673F71F74EC3200F22CB9 /* Products */ = {
67 | isa = PBXGroup;
68 | children = (
69 | B33673F61F74EC3200F22CB9 /* Example.app */,
70 | );
71 | name = Products;
72 | sourceTree = "";
73 | };
74 | B33673F81F74EC3200F22CB9 /* Example */ = {
75 | isa = PBXGroup;
76 | children = (
77 | B336740F1F74EC6600F22CB9 /* Reflow */,
78 | B33673F91F74EC3200F22CB9 /* AppDelegate.h */,
79 | B33673FA1F74EC3200F22CB9 /* AppDelegate.m */,
80 | B33673FF1F74EC3200F22CB9 /* Main.storyboard */,
81 | B33674021F74EC3200F22CB9 /* Assets.xcassets */,
82 | B33674041F74EC3200F22CB9 /* LaunchScreen.storyboard */,
83 | B33674071F74EC3200F22CB9 /* Info.plist */,
84 | B33674081F74EC3200F22CB9 /* main.m */,
85 | B33674151F74F03F00F22CB9 /* TodoStore.h */,
86 | B33674161F74F03F00F22CB9 /* TodoStore.m */,
87 | B336741A1F7504D900F22CB9 /* Todo.h */,
88 | B336741B1F7504D900F22CB9 /* Todo.m */,
89 | B39AF4821F78A107004BA2E8 /* TodoTableViewController.h */,
90 | B39AF4831F78A107004BA2E8 /* TodoTableViewController.m */,
91 | );
92 | path = Example;
93 | sourceTree = "";
94 | };
95 | B336740F1F74EC6600F22CB9 /* Reflow */ = {
96 | isa = PBXGroup;
97 | children = (
98 | B33674101F74EC6600F22CB9 /* Reflow.h */,
99 | B33674111F74EC6600F22CB9 /* RFAspects.h */,
100 | B33674121F74EC6600F22CB9 /* RFAspects.m */,
101 | B33674131F74EC6600F22CB9 /* RFStore.h */,
102 | B33674141F74EC6600F22CB9 /* RFStore.m */,
103 | B37BAC0B1F789D43003702E1 /* NSArray+Functional.h */,
104 | B37BAC0C1F789D43003702E1 /* NSArray+Functional.m */,
105 | );
106 | name = Reflow;
107 | path = ../../Reflow;
108 | sourceTree = "";
109 | };
110 | /* End PBXGroup section */
111 |
112 | /* Begin PBXNativeTarget section */
113 | B33673F51F74EC3200F22CB9 /* Example */ = {
114 | isa = PBXNativeTarget;
115 | buildConfigurationList = B336740C1F74EC3200F22CB9 /* Build configuration list for PBXNativeTarget "Example" */;
116 | buildPhases = (
117 | B33673F21F74EC3200F22CB9 /* Sources */,
118 | B33673F31F74EC3200F22CB9 /* Frameworks */,
119 | B33673F41F74EC3200F22CB9 /* Resources */,
120 | );
121 | buildRules = (
122 | );
123 | dependencies = (
124 | );
125 | name = Example;
126 | productName = Example;
127 | productReference = B33673F61F74EC3200F22CB9 /* Example.app */;
128 | productType = "com.apple.product-type.application";
129 | };
130 | /* End PBXNativeTarget section */
131 |
132 | /* Begin PBXProject section */
133 | B33673EE1F74EC3200F22CB9 /* Project object */ = {
134 | isa = PBXProject;
135 | attributes = {
136 | LastUpgradeCheck = 0900;
137 | ORGANIZATIONNAME = Zepo;
138 | TargetAttributes = {
139 | B33673F51F74EC3200F22CB9 = {
140 | CreatedOnToolsVersion = 9.0;
141 | ProvisioningStyle = Automatic;
142 | };
143 | };
144 | };
145 | buildConfigurationList = B33673F11F74EC3200F22CB9 /* Build configuration list for PBXProject "Example" */;
146 | compatibilityVersion = "Xcode 8.0";
147 | developmentRegion = en;
148 | hasScannedForEncodings = 0;
149 | knownRegions = (
150 | en,
151 | Base,
152 | );
153 | mainGroup = B33673ED1F74EC3200F22CB9;
154 | productRefGroup = B33673F71F74EC3200F22CB9 /* Products */;
155 | projectDirPath = "";
156 | projectRoot = "";
157 | targets = (
158 | B33673F51F74EC3200F22CB9 /* Example */,
159 | );
160 | };
161 | /* End PBXProject section */
162 |
163 | /* Begin PBXResourcesBuildPhase section */
164 | B33673F41F74EC3200F22CB9 /* Resources */ = {
165 | isa = PBXResourcesBuildPhase;
166 | buildActionMask = 2147483647;
167 | files = (
168 | B33674061F74EC3200F22CB9 /* LaunchScreen.storyboard in Resources */,
169 | B33674031F74EC3200F22CB9 /* Assets.xcassets in Resources */,
170 | B33674011F74EC3200F22CB9 /* Main.storyboard in Resources */,
171 | );
172 | runOnlyForDeploymentPostprocessing = 0;
173 | };
174 | /* End PBXResourcesBuildPhase section */
175 |
176 | /* Begin PBXSourcesBuildPhase section */
177 | B33673F21F74EC3200F22CB9 /* Sources */ = {
178 | isa = PBXSourcesBuildPhase;
179 | buildActionMask = 2147483647;
180 | files = (
181 | B37BAC0D1F789D43003702E1 /* NSArray+Functional.m in Sources */,
182 | B33674171F74F03F00F22CB9 /* TodoStore.m in Sources */,
183 | B39AF4841F78A107004BA2E8 /* TodoTableViewController.m in Sources */,
184 | B33674091F74EC3200F22CB9 /* main.m in Sources */,
185 | B33674181F74F15000F22CB9 /* RFStore.m in Sources */,
186 | B33673FB1F74EC3200F22CB9 /* AppDelegate.m in Sources */,
187 | B33674191F74F15300F22CB9 /* RFAspects.m in Sources */,
188 | B336741C1F7504D900F22CB9 /* Todo.m in Sources */,
189 | );
190 | runOnlyForDeploymentPostprocessing = 0;
191 | };
192 | /* End PBXSourcesBuildPhase section */
193 |
194 | /* Begin PBXVariantGroup section */
195 | B33673FF1F74EC3200F22CB9 /* Main.storyboard */ = {
196 | isa = PBXVariantGroup;
197 | children = (
198 | B33674001F74EC3200F22CB9 /* Base */,
199 | );
200 | name = Main.storyboard;
201 | sourceTree = "";
202 | };
203 | B33674041F74EC3200F22CB9 /* LaunchScreen.storyboard */ = {
204 | isa = PBXVariantGroup;
205 | children = (
206 | B33674051F74EC3200F22CB9 /* Base */,
207 | );
208 | name = LaunchScreen.storyboard;
209 | sourceTree = "";
210 | };
211 | /* End PBXVariantGroup section */
212 |
213 | /* Begin XCBuildConfiguration section */
214 | B336740A1F74EC3200F22CB9 /* Debug */ = {
215 | isa = XCBuildConfiguration;
216 | buildSettings = {
217 | ALWAYS_SEARCH_USER_PATHS = NO;
218 | CLANG_ANALYZER_NONNULL = YES;
219 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
220 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
221 | CLANG_CXX_LIBRARY = "libc++";
222 | CLANG_ENABLE_MODULES = YES;
223 | CLANG_ENABLE_OBJC_ARC = YES;
224 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
225 | CLANG_WARN_BOOL_CONVERSION = YES;
226 | CLANG_WARN_COMMA = YES;
227 | CLANG_WARN_CONSTANT_CONVERSION = YES;
228 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
229 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
230 | CLANG_WARN_EMPTY_BODY = YES;
231 | CLANG_WARN_ENUM_CONVERSION = YES;
232 | CLANG_WARN_INFINITE_RECURSION = YES;
233 | CLANG_WARN_INT_CONVERSION = YES;
234 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
235 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
236 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
237 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
238 | CLANG_WARN_STRICT_PROTOTYPES = YES;
239 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
240 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
241 | CLANG_WARN_UNREACHABLE_CODE = YES;
242 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
243 | CODE_SIGN_IDENTITY = "iPhone Developer";
244 | COPY_PHASE_STRIP = NO;
245 | DEBUG_INFORMATION_FORMAT = dwarf;
246 | ENABLE_STRICT_OBJC_MSGSEND = YES;
247 | ENABLE_TESTABILITY = YES;
248 | GCC_C_LANGUAGE_STANDARD = gnu11;
249 | GCC_DYNAMIC_NO_PIC = NO;
250 | GCC_NO_COMMON_BLOCKS = YES;
251 | GCC_OPTIMIZATION_LEVEL = 0;
252 | GCC_PREPROCESSOR_DEFINITIONS = (
253 | "DEBUG=1",
254 | "$(inherited)",
255 | );
256 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
257 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
258 | GCC_WARN_UNDECLARED_SELECTOR = YES;
259 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
260 | GCC_WARN_UNUSED_FUNCTION = YES;
261 | GCC_WARN_UNUSED_VARIABLE = YES;
262 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
263 | MTL_ENABLE_DEBUG_INFO = YES;
264 | ONLY_ACTIVE_ARCH = YES;
265 | SDKROOT = iphoneos;
266 | };
267 | name = Debug;
268 | };
269 | B336740B1F74EC3200F22CB9 /* Release */ = {
270 | isa = XCBuildConfiguration;
271 | buildSettings = {
272 | ALWAYS_SEARCH_USER_PATHS = NO;
273 | CLANG_ANALYZER_NONNULL = YES;
274 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
275 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
276 | CLANG_CXX_LIBRARY = "libc++";
277 | CLANG_ENABLE_MODULES = YES;
278 | CLANG_ENABLE_OBJC_ARC = YES;
279 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
280 | CLANG_WARN_BOOL_CONVERSION = YES;
281 | CLANG_WARN_COMMA = YES;
282 | CLANG_WARN_CONSTANT_CONVERSION = YES;
283 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
284 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
285 | CLANG_WARN_EMPTY_BODY = YES;
286 | CLANG_WARN_ENUM_CONVERSION = YES;
287 | CLANG_WARN_INFINITE_RECURSION = YES;
288 | CLANG_WARN_INT_CONVERSION = YES;
289 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
290 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
291 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
292 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
293 | CLANG_WARN_STRICT_PROTOTYPES = YES;
294 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
295 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
296 | CLANG_WARN_UNREACHABLE_CODE = YES;
297 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
298 | CODE_SIGN_IDENTITY = "iPhone Developer";
299 | COPY_PHASE_STRIP = NO;
300 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
301 | ENABLE_NS_ASSERTIONS = NO;
302 | ENABLE_STRICT_OBJC_MSGSEND = YES;
303 | GCC_C_LANGUAGE_STANDARD = gnu11;
304 | GCC_NO_COMMON_BLOCKS = YES;
305 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
306 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
307 | GCC_WARN_UNDECLARED_SELECTOR = YES;
308 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
309 | GCC_WARN_UNUSED_FUNCTION = YES;
310 | GCC_WARN_UNUSED_VARIABLE = YES;
311 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
312 | MTL_ENABLE_DEBUG_INFO = NO;
313 | SDKROOT = iphoneos;
314 | VALIDATE_PRODUCT = YES;
315 | };
316 | name = Release;
317 | };
318 | B336740D1F74EC3200F22CB9 /* Debug */ = {
319 | isa = XCBuildConfiguration;
320 | buildSettings = {
321 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
322 | CODE_SIGN_STYLE = Automatic;
323 | DEVELOPMENT_TEAM = 6A9AHK6F98;
324 | INFOPLIST_FILE = Example/Info.plist;
325 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
326 | PRODUCT_BUNDLE_IDENTIFIER = io.github.zepo.Example;
327 | PRODUCT_NAME = "$(TARGET_NAME)";
328 | TARGETED_DEVICE_FAMILY = "1,2";
329 | };
330 | name = Debug;
331 | };
332 | B336740E1F74EC3200F22CB9 /* Release */ = {
333 | isa = XCBuildConfiguration;
334 | buildSettings = {
335 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
336 | CODE_SIGN_STYLE = Automatic;
337 | DEVELOPMENT_TEAM = 6A9AHK6F98;
338 | INFOPLIST_FILE = Example/Info.plist;
339 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
340 | PRODUCT_BUNDLE_IDENTIFIER = io.github.zepo.Example;
341 | PRODUCT_NAME = "$(TARGET_NAME)";
342 | TARGETED_DEVICE_FAMILY = "1,2";
343 | };
344 | name = Release;
345 | };
346 | /* End XCBuildConfiguration section */
347 |
348 | /* Begin XCConfigurationList section */
349 | B33673F11F74EC3200F22CB9 /* Build configuration list for PBXProject "Example" */ = {
350 | isa = XCConfigurationList;
351 | buildConfigurations = (
352 | B336740A1F74EC3200F22CB9 /* Debug */,
353 | B336740B1F74EC3200F22CB9 /* Release */,
354 | );
355 | defaultConfigurationIsVisible = 0;
356 | defaultConfigurationName = Release;
357 | };
358 | B336740C1F74EC3200F22CB9 /* Build configuration list for PBXNativeTarget "Example" */ = {
359 | isa = XCConfigurationList;
360 | buildConfigurations = (
361 | B336740D1F74EC3200F22CB9 /* Debug */,
362 | B336740E1F74EC3200F22CB9 /* Release */,
363 | );
364 | defaultConfigurationIsVisible = 0;
365 | defaultConfigurationName = Release;
366 | };
367 | /* End XCConfigurationList section */
368 | };
369 | rootObject = B33673EE1F74EC3200F22CB9 /* Project object */;
370 | }
371 |
--------------------------------------------------------------------------------
/Reflow/RFAspects.m:
--------------------------------------------------------------------------------
1 | //
2 | // Aspects.m
3 | // Aspects - A delightful, simple library for aspect oriented programming.
4 | //
5 | // Copyright (c) 2014 Peter Steinberger. Licensed under the MIT license.
6 | //
7 |
8 | #import "RFAspects.h"
9 | #import
10 | #import
11 | #import
12 |
13 | #define AspectLog(...)
14 | //#define AspectLog(...) do { NSLog(__VA_ARGS__); }while(0)
15 | #define AspectLogError(...) do { NSLog(__VA_ARGS__); }while(0)
16 |
17 | // Block internals.
18 | typedef NS_OPTIONS(int, AspectBlockFlags) {
19 | AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
20 | AspectBlockFlagsHasSignature = (1 << 30)
21 | };
22 | typedef struct _AspectBlock {
23 | __unused Class isa;
24 | AspectBlockFlags flags;
25 | __unused int reserved;
26 | void (__unused *invoke)(struct _AspectBlock *block, ...);
27 | struct {
28 | unsigned long int reserved;
29 | unsigned long int size;
30 | // requires AspectBlockFlagsHasCopyDisposeHelpers
31 | void (*copy)(void *dst, const void *src);
32 | void (*dispose)(const void *);
33 | // requires AspectBlockFlagsHasSignature
34 | const char *signature;
35 | const char *layout;
36 | } *descriptor;
37 | // imported variables
38 | } *AspectBlockRef;
39 |
40 | @interface RFAspectInfo : NSObject
41 | - (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
42 | @property (nonatomic, unsafe_unretained, readonly) id instance;
43 | @property (nonatomic, strong, readonly) NSArray *arguments;
44 | @property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
45 | @end
46 |
47 | // Tracks a single aspect.
48 | @interface RFAspectIdentifier : NSObject
49 | + (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(RFAspectOptions)options block:(id)block error:(NSError **)error;
50 | - (BOOL)invokeWithInfo:(id)info;
51 | @property (nonatomic, assign) SEL selector;
52 | @property (nonatomic, strong) id block;
53 | @property (nonatomic, strong) NSMethodSignature *blockSignature;
54 | @property (nonatomic, weak) id object;
55 | @property (nonatomic, assign) RFAspectOptions options;
56 | @end
57 |
58 | // Tracks all aspects for an object/class.
59 | @interface RFAspectsContainer : NSObject
60 | - (void)addAspect:(RFAspectIdentifier *)aspect withOptions:(RFAspectOptions)injectPosition;
61 | - (BOOL)removeAspect:(id)aspect;
62 | - (BOOL)hasAspects;
63 | @property (atomic, copy) NSArray *beforeAspects;
64 | @property (atomic, copy) NSArray *insteadAspects;
65 | @property (atomic, copy) NSArray *afterAspects;
66 | @end
67 |
68 | @interface RFAspectTracker : NSObject
69 | - (id)initWithTrackedClass:(Class)trackedClass;
70 | @property (nonatomic, strong) Class trackedClass;
71 | @property (nonatomic, readonly) NSString *trackedClassName;
72 | @property (nonatomic, strong) NSMutableSet *selectorNames;
73 | @property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
74 | - (void)addSubclassTracker:(RFAspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
75 | - (void)removeSubclassTracker:(RFAspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
76 | - (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName;
77 | - (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName;
78 | @end
79 |
80 | @interface NSInvocation (RFAspects)
81 | - (NSArray *)aspects_arguments;
82 | @end
83 |
84 | #define AspectPositionFilter 0x07
85 |
86 | #define AspectError(errorCode, errorDescription) do { \
87 | AspectLogError(@"Aspects: %@", errorDescription); \
88 | if (error) { *error = [NSError errorWithDomain:RFAspectErrorDomain code:errorCode userInfo:@{NSLocalizedDescriptionKey: errorDescription}]; }}while(0)
89 |
90 | NSString *const RFAspectErrorDomain = @"RFAspectErrorDomain";
91 | static NSString *const AspectsSubclassSuffix = @"_RFAspects_";
92 | static NSString *const AspectsMessagePrefix = @"rfaspects_";
93 |
94 | static const void * const kClassHasBeenHookedKey = &kClassHasBeenHookedKey;
95 |
96 | @implementation NSObject (RFAspects)
97 |
98 | ///////////////////////////////////////////////////////////////////////////////////////////
99 | #pragma mark - Public Aspects API
100 |
101 | + (id)rfaspect_hookSelector:(SEL)selector
102 | withOptions:(RFAspectOptions)options
103 | usingBlock:(id)block
104 | error:(NSError **)error {
105 | return aspect_add((id)self, selector, options, block, error);
106 | }
107 |
108 | /// @return A token which allows to later deregister the aspect.
109 | - (id)rfaspect_hookSelector:(SEL)selector
110 | withOptions:(RFAspectOptions)options
111 | usingBlock:(id)block
112 | error:(NSError **)error {
113 | return aspect_add(self, selector, options, block, error);
114 | }
115 |
116 | ///////////////////////////////////////////////////////////////////////////////////////////
117 | #pragma mark - Private Helper
118 |
119 | static id aspect_add(id self, SEL selector, RFAspectOptions options, id block, NSError **error) {
120 | NSCParameterAssert(self);
121 | NSCParameterAssert(selector);
122 | NSCParameterAssert(block);
123 |
124 | __block RFAspectIdentifier *identifier = nil;
125 | aspect_performLocked(^{
126 | if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
127 | RFAspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
128 | identifier = [RFAspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
129 | if (identifier) {
130 | [aspectContainer addAspect:identifier withOptions:options];
131 |
132 | // Modify the class to allow message interception.
133 | aspect_prepareClassAndHookSelector(self, selector, error);
134 | }
135 | }
136 | });
137 | return identifier;
138 | }
139 |
140 | static BOOL aspect_remove(RFAspectIdentifier *aspect, NSError **error) {
141 | NSCAssert([aspect isKindOfClass:RFAspectIdentifier.class], @"Must have correct type.");
142 |
143 | __block BOOL success = NO;
144 | aspect_performLocked(^{
145 | id self = aspect.object; // strongify
146 | if (self) {
147 | RFAspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector);
148 | success = [aspectContainer removeAspect:aspect];
149 |
150 | aspect_cleanupHookedClassAndSelector(self, aspect.selector);
151 | // destroy token
152 | aspect.object = nil;
153 | aspect.block = nil;
154 | aspect.selector = NULL;
155 | }else {
156 | NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", aspect];
157 | AspectError(AspectErrorRemoveObjectAlreadyDeallocated, errrorDesc);
158 | }
159 | });
160 | return success;
161 | }
162 |
163 | static void aspect_performLocked(dispatch_block_t block) {
164 | static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
165 | OSSpinLockLock(&aspect_lock);
166 | block();
167 | OSSpinLockUnlock(&aspect_lock);
168 | }
169 |
170 | static SEL aspect_aliasForSelector(SEL selector) {
171 | NSCParameterAssert(selector);
172 | return NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]);
173 | }
174 |
175 | static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
176 | AspectBlockRef layout = (__bridge void *)block;
177 | if (!(layout->flags & AspectBlockFlagsHasSignature)) {
178 | NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
179 | AspectError(AspectErrorMissingBlockSignature, description);
180 | return nil;
181 | }
182 | void *desc = layout->descriptor;
183 | desc += 2 * sizeof(unsigned long int);
184 | if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
185 | desc += 2 * sizeof(void *);
186 | }
187 | if (!desc) {
188 | NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
189 | AspectError(AspectErrorMissingBlockSignature, description);
190 | return nil;
191 | }
192 | const char *signature = (*(const char **)desc);
193 | return [NSMethodSignature signatureWithObjCTypes:signature];
194 | }
195 |
196 | static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
197 | NSCParameterAssert(blockSignature);
198 | NSCParameterAssert(object);
199 | NSCParameterAssert(selector);
200 |
201 | BOOL signaturesMatch = YES;
202 | NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
203 | if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
204 | signaturesMatch = NO;
205 | }else {
206 | if (blockSignature.numberOfArguments > 1) {
207 | const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
208 | if (blockType[0] != '@') {
209 | signaturesMatch = NO;
210 | }
211 | }
212 | // Argument 0 is self/block, argument 1 is SEL or id. We start comparing at argument 2.
213 | // The block can have less arguments than the method, that's ok.
214 | if (signaturesMatch) {
215 | for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) {
216 | const char *methodType = [methodSignature getArgumentTypeAtIndex:idx];
217 | const char *blockType = [blockSignature getArgumentTypeAtIndex:idx];
218 | // Only compare parameter, not the optional type data.
219 | if (!methodType || !blockType || methodType[0] != blockType[0]) {
220 | signaturesMatch = NO; break;
221 | }
222 | }
223 | }
224 | }
225 |
226 | if (!signaturesMatch) {
227 | NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
228 | AspectError(AspectErrorIncompatibleBlockSignature, description);
229 | return NO;
230 | }
231 | return YES;
232 | }
233 |
234 | ///////////////////////////////////////////////////////////////////////////////////////////
235 | #pragma mark - Class + Selector Preparation
236 |
237 | static BOOL aspect_isMsgForwardIMP(IMP impl) {
238 | return impl == _objc_msgForward
239 | #if !defined(__arm64__)
240 | || impl == (IMP)_objc_msgForward_stret
241 | #endif
242 | ;
243 | }
244 |
245 | static IMP aspect_getMsgForwardIMP(NSObject *self, SEL selector) {
246 | IMP msgForwardIMP = _objc_msgForward;
247 | #if !defined(__arm64__)
248 | // As an ugly internal runtime implementation detail in the 32bit runtime, we need to determine of the method we hook returns a struct or anything larger than id.
249 | // https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html
250 | // https://github.com/ReactiveCocoa/ReactiveCocoa/issues/783
251 | // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf (Section 5.4)
252 | Method method = class_getInstanceMethod(self.class, selector);
253 | const char *encoding = method_getTypeEncoding(method);
254 | BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B;
255 | if (methodReturnsStructValue) {
256 | @try {
257 | NSUInteger valueSize = 0;
258 | NSGetSizeAndAlignment(encoding, &valueSize, NULL);
259 |
260 | if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) {
261 | methodReturnsStructValue = NO;
262 | }
263 | } @catch (__unused NSException *e) {}
264 | }
265 | if (methodReturnsStructValue) {
266 | msgForwardIMP = (IMP)_objc_msgForward_stret;
267 | }
268 | #endif
269 | return msgForwardIMP;
270 | }
271 |
272 | static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
273 | NSCParameterAssert(selector);
274 | Class klass = aspect_hookClass(self, error);
275 | Method targetMethod = class_getInstanceMethod(klass, selector);
276 | IMP targetMethodIMP = method_getImplementation(targetMethod);
277 | if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
278 | // Make a method alias for the existing method implementation, it not already copied.
279 | const char *typeEncoding = method_getTypeEncoding(targetMethod);
280 | SEL aliasSelector = aspect_aliasForSelector(selector);
281 | if (![klass instancesRespondToSelector:aliasSelector]) {
282 | __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
283 | NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
284 | }
285 |
286 | // We use forwardInvocation to hook in.
287 | class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
288 | AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
289 | }
290 | }
291 |
292 | // Will undo the runtime changes made.
293 | static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) {
294 | NSCParameterAssert(self);
295 | NSCParameterAssert(selector);
296 |
297 | Class klass = object_getClass(self);
298 | BOOL isMetaClass = class_isMetaClass(klass);
299 | if (isMetaClass) {
300 | klass = (Class)self;
301 | }
302 |
303 | // Check if the method is marked as forwarded and undo that.
304 | Method targetMethod = class_getInstanceMethod(klass, selector);
305 | IMP targetMethodIMP = method_getImplementation(targetMethod);
306 | if (aspect_isMsgForwardIMP(targetMethodIMP)) {
307 | // Restore the original method implementation.
308 | const char *typeEncoding = method_getTypeEncoding(targetMethod);
309 | SEL aliasSelector = aspect_aliasForSelector(selector);
310 | Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
311 | IMP originalIMP = method_getImplementation(originalMethod);
312 | NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
313 |
314 | class_replaceMethod(klass, selector, originalIMP, typeEncoding);
315 | AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
316 | }
317 |
318 | // Deregister global tracked selector
319 | aspect_deregisterTrackedSelector(self, selector);
320 |
321 | // Get the aspect container and check if there are any hooks remaining. Clean up if there are not.
322 | RFAspectsContainer *container = aspect_getContainerForObject(self, selector);
323 | if (!container.hasAspects) {
324 | // Destroy the container
325 | aspect_destroyContainerForObject(self, selector);
326 |
327 | // Figure out how the class was modified to undo the changes.
328 | NSString *className = NSStringFromClass(klass);
329 | if ([className hasSuffix:AspectsSubclassSuffix]) {
330 | Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]);
331 | NSCAssert(originalClass != nil, @"Original class must exist");
332 | object_setClass(self, originalClass);
333 | AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass));
334 |
335 | // We can only dispose the class pair if we can ensure that no instances exist using our subclass.
336 | // Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around.
337 | //objc_disposeClassPair(object.class);
338 | }else {
339 | // Class is most likely swizzled in place. Undo that.
340 | if (isMetaClass) {
341 | aspect_undoSwizzleClassInPlace((Class)self);
342 | }else if (self.class != klass) {
343 | aspect_undoSwizzleClassInPlace(klass);
344 | }
345 | }
346 | }
347 | }
348 |
349 | ///////////////////////////////////////////////////////////////////////////////////////////
350 | #pragma mark - Hook Class
351 |
352 | static Class aspect_hookClass(NSObject *self, NSError **error) {
353 | NSCParameterAssert(self);
354 | Class statedClass = self.class;
355 | Class baseClass = object_getClass(self);
356 | NSString *className = NSStringFromClass(baseClass);
357 |
358 | // Already subclassed
359 | if ([className hasSuffix:AspectsSubclassSuffix]) {
360 | return baseClass;
361 |
362 | // We swizzle a class object, not a single object.
363 | }else if (class_isMetaClass(baseClass)) {
364 | return aspect_swizzleClassInPlace((Class)self);
365 | // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
366 | }else if (statedClass != baseClass) {
367 | return aspect_swizzleClassInPlace(baseClass);
368 | }
369 |
370 | // Default case. Create dynamic subclass.
371 | const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
372 | Class subclass = objc_getClass(subclassName);
373 |
374 | if (subclass == nil) {
375 | subclass = objc_allocateClassPair(baseClass, subclassName, 0);
376 | if (subclass == nil) {
377 | NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
378 | AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
379 | return nil;
380 | }
381 |
382 | aspect_swizzleForwardInvocation(subclass);
383 | aspect_hookedGetClass(subclass, statedClass);
384 | aspect_hookedGetClass(object_getClass(subclass), statedClass);
385 | objc_registerClassPair(subclass);
386 | }
387 |
388 | object_setClass(self, subclass);
389 | return subclass;
390 | }
391 |
392 | static NSString *const AspectsForwardInvocationSelectorName = @"__rfaspects_forwardInvocation:";
393 | static void aspect_swizzleForwardInvocation(Class klass) {
394 | NSCParameterAssert(klass);
395 | // If there is no method, replace will act like class_addMethod.
396 | IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__RFASPECTS_ARE_BEING_CALLED__, "v@:@");
397 | if (originalImplementation) {
398 | class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
399 | }
400 | AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
401 | }
402 |
403 | static void aspect_undoSwizzleForwardInvocation(Class klass) {
404 | NSCParameterAssert(klass);
405 | Method originalMethod = class_getInstanceMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName));
406 | Method objectMethod = class_getInstanceMethod(NSObject.class, @selector(forwardInvocation:));
407 | // There is no class_removeMethod, so the best we can do is to retore the original implementation, or use a dummy.
408 | IMP originalImplementation = method_getImplementation(originalMethod ?: objectMethod);
409 | class_replaceMethod(klass, @selector(forwardInvocation:), originalImplementation, "v@:@");
410 |
411 | AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(klass));
412 | }
413 |
414 | static void aspect_hookedGetClass(Class class, Class statedClass) {
415 | NSCParameterAssert(class);
416 | NSCParameterAssert(statedClass);
417 | Method method = class_getInstanceMethod(class, @selector(class));
418 | IMP newIMP = imp_implementationWithBlock(^(id self) {
419 | return statedClass;
420 | });
421 | class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
422 | }
423 |
424 | ///////////////////////////////////////////////////////////////////////////////////////////
425 | #pragma mark - Swizzle Class In Place
426 |
427 | //static void _aspect_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) {
428 | // static NSMutableSet *swizzledClasses;
429 | // static dispatch_once_t pred;
430 | // dispatch_once(&pred, ^{
431 | // swizzledClasses = [NSMutableSet new];
432 | // });
433 | // @synchronized(swizzledClasses) {
434 | // block(swizzledClasses);
435 | // }
436 | //}
437 |
438 | static Class aspect_swizzleClassInPlace(Class klass) {
439 | NSCParameterAssert(klass);
440 | // NSString *className = NSStringFromClass(klass);
441 | //
442 | // _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
443 | // if (![swizzledClasses containsObject:className]) {
444 | // aspect_swizzleForwardInvocation(klass);
445 | // [swizzledClasses addObject:className];
446 | // }
447 | // });
448 | NSNumber *hasBeenHooked = objc_getAssociatedObject(klass, kClassHasBeenHookedKey);
449 | if (!hasBeenHooked.boolValue) {
450 | aspect_swizzleForwardInvocation(klass);
451 | objc_setAssociatedObject(klass, kClassHasBeenHookedKey, @YES, OBJC_ASSOCIATION_RETAIN);
452 | }
453 | return klass;
454 | }
455 |
456 | static void aspect_undoSwizzleClassInPlace(Class klass) {
457 | NSCParameterAssert(klass);
458 | // NSString *className = NSStringFromClass(klass);
459 | //
460 | // _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
461 | // if ([swizzledClasses containsObject:className]) {
462 | // aspect_undoSwizzleForwardInvocation(klass);
463 | // [swizzledClasses removeObject:className];
464 | // }
465 | // });
466 | NSNumber *hasBeenHooked = objc_getAssociatedObject(klass, kClassHasBeenHookedKey);
467 | if (hasBeenHooked.boolValue) {
468 | aspect_undoSwizzleForwardInvocation(klass);
469 | objc_setAssociatedObject(klass, kClassHasBeenHookedKey, nil, OBJC_ASSOCIATION_RETAIN);
470 | }
471 | }
472 |
473 | ///////////////////////////////////////////////////////////////////////////////////////////
474 | #pragma mark - Aspect Invoke Point
475 |
476 | // This is a macro so we get a cleaner stack trace.
477 | #define aspect_invoke(aspects, info) \
478 | for (RFAspectIdentifier *aspect in aspects) {\
479 | [aspect invokeWithInfo:info];\
480 | if (aspect.options & AspectOptionAutomaticRemoval) { \
481 | aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
482 | } \
483 | }
484 |
485 | // This is the swizzled forwardInvocation: method.
486 | static void __RFASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
487 | NSCParameterAssert(self);
488 | NSCParameterAssert(invocation);
489 | SEL originalSelector = invocation.selector;
490 | SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
491 | invocation.selector = aliasSelector;
492 | RFAspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
493 | RFAspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
494 | RFAspectInfo *info = [[RFAspectInfo alloc] initWithInstance:self invocation:invocation];
495 | NSArray *aspectsToRemove = nil;
496 |
497 | // Before hooks.
498 | aspect_invoke(classContainer.beforeAspects, info);
499 | // aspect_invoke(objectContainer.beforeAspects, info);
500 |
501 | // Instead hooks.
502 | BOOL respondsToAlias = YES;
503 | if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
504 | aspect_invoke(classContainer.insteadAspects, info);
505 | // aspect_invoke(objectContainer.insteadAspects, info);
506 | }else {
507 | Class klass = object_getClass(invocation.target);
508 | do {
509 | if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
510 | [invocation invoke];
511 | break;
512 | }
513 | }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
514 | }
515 |
516 | // After hooks.
517 | aspect_invoke(classContainer.afterAspects, info);
518 | // aspect_invoke(objectContainer.afterAspects, info);
519 |
520 | // If no hooks are installed, call original implementation (usually to throw an exception)
521 | if (!respondsToAlias) {
522 | invocation.selector = originalSelector;
523 | SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
524 | if ([self respondsToSelector:originalForwardInvocationSEL]) {
525 | ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
526 | }else {
527 | [self doesNotRecognizeSelector:invocation.selector];
528 | }
529 | }
530 |
531 | // Remove any hooks that are queued for deregistration.
532 | [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
533 | }
534 | #undef aspect_invoke
535 |
536 | ///////////////////////////////////////////////////////////////////////////////////////////
537 | #pragma mark - Aspect Container Management
538 |
539 | // Loads or creates the aspect container.
540 | static RFAspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
541 | NSCParameterAssert(self);
542 | SEL aliasSelector = aspect_aliasForSelector(selector);
543 | RFAspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
544 | if (!aspectContainer) {
545 | aspectContainer = [RFAspectsContainer new];
546 | objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
547 | }
548 | return aspectContainer;
549 | }
550 |
551 | static RFAspectsContainer *aspect_getContainerForClass(Class klass, SEL selector) {
552 | NSCParameterAssert(klass);
553 | RFAspectsContainer *classContainer = nil;
554 | do {
555 | classContainer = objc_getAssociatedObject(klass, selector);
556 | if (classContainer.hasAspects) break;
557 | }while ((klass = class_getSuperclass(klass)));
558 |
559 | return classContainer;
560 | }
561 |
562 | static void aspect_destroyContainerForObject(id self, SEL selector) {
563 | NSCParameterAssert(self);
564 | SEL aliasSelector = aspect_aliasForSelector(selector);
565 | objc_setAssociatedObject(self, aliasSelector, nil, OBJC_ASSOCIATION_RETAIN);
566 | }
567 |
568 | ///////////////////////////////////////////////////////////////////////////////////////////
569 | #pragma mark - Selector Blacklist Checking
570 |
571 | static NSMutableDictionary *aspect_getSwizzledClassesDict() {
572 | static NSMutableDictionary *swizzledClassesDict;
573 | static dispatch_once_t pred;
574 | dispatch_once(&pred, ^{
575 | swizzledClassesDict = [NSMutableDictionary new];
576 | });
577 | return swizzledClassesDict;
578 | }
579 |
580 | static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, RFAspectOptions options, NSError **error) {
581 | static NSSet *disallowedSelectorList;
582 | static dispatch_once_t pred;
583 | dispatch_once(&pred, ^{
584 | disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
585 | });
586 |
587 | // Check against the blacklist.
588 | NSString *selectorName = NSStringFromSelector(selector);
589 | if ([disallowedSelectorList containsObject:selectorName]) {
590 | NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
591 | AspectError(AspectErrorSelectorBlacklisted, errorDescription);
592 | return NO;
593 | }
594 |
595 | // Additional checks.
596 | RFAspectOptions position = options&AspectPositionFilter;
597 | if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
598 | NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
599 | AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
600 | return NO;
601 | }
602 |
603 | if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
604 | NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];
605 | AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
606 | return NO;
607 | }
608 |
609 | // Search for the current class and the class hierarchy IF we are modifying a class object
610 | if (class_isMetaClass(object_getClass(self))) {
611 | Class klass = [self class];
612 | NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
613 | Class currentClass = [self class];
614 |
615 | RFAspectTracker *tracker = swizzledClassesDict[currentClass];
616 | if ([tracker subclassHasHookedSelectorName:selectorName]) {
617 | NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName];
618 | NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"];
619 | NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked subclasses: %@. A method can only be hooked once per class hierarchy.", selectorName, subclassNames];
620 | AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
621 | return NO;
622 | }
623 |
624 | do {
625 | tracker = swizzledClassesDict[currentClass];
626 | if ([tracker.selectorNames containsObject:selectorName]) {
627 | if (klass == currentClass) {
628 | // Already modified and topmost!
629 | return YES;
630 | }
631 | NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)];
632 | AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
633 | return NO;
634 | }
635 | } while ((currentClass = class_getSuperclass(currentClass)));
636 |
637 | // Add the selector as being modified.
638 | currentClass = klass;
639 | RFAspectTracker *subclassTracker = nil;
640 | do {
641 | tracker = swizzledClassesDict[currentClass];
642 | if (!tracker) {
643 | tracker = [[RFAspectTracker alloc] initWithTrackedClass:currentClass];
644 | swizzledClassesDict[(id)currentClass] = tracker;
645 | }
646 | if (subclassTracker) {
647 | [tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
648 | } else {
649 | [tracker.selectorNames addObject:selectorName];
650 | }
651 |
652 | // All superclasses get marked as having a subclass that is modified.
653 | subclassTracker = tracker;
654 | }while ((currentClass = class_getSuperclass(currentClass)));
655 | } else {
656 | return YES;
657 | }
658 |
659 | return YES;
660 | }
661 |
662 | static void aspect_deregisterTrackedSelector(id self, SEL selector) {
663 | if (!class_isMetaClass(object_getClass(self))) return;
664 |
665 | NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
666 | NSString *selectorName = NSStringFromSelector(selector);
667 | Class currentClass = [self class];
668 | RFAspectTracker *subclassTracker = nil;
669 | do {
670 | RFAspectTracker *tracker = swizzledClassesDict[currentClass];
671 | if (subclassTracker) {
672 | [tracker removeSubclassTracker:subclassTracker hookingSelectorName:selectorName];
673 | } else {
674 | [tracker.selectorNames removeObject:selectorName];
675 | }
676 | if (tracker.selectorNames.count == 0 && tracker.selectorNamesToSubclassTrackers) {
677 | [swizzledClassesDict removeObjectForKey:currentClass];
678 | }
679 | subclassTracker = tracker;
680 | }while ((currentClass = class_getSuperclass(currentClass)));
681 | }
682 |
683 | @end
684 |
685 | @implementation RFAspectTracker
686 |
687 | - (id)initWithTrackedClass:(Class)trackedClass {
688 | if (self = [super init]) {
689 | _trackedClass = trackedClass;
690 | _selectorNames = [NSMutableSet new];
691 | _selectorNamesToSubclassTrackers = [NSMutableDictionary new];
692 | }
693 | return self;
694 | }
695 |
696 | - (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName {
697 | return self.selectorNamesToSubclassTrackers[selectorName] != nil;
698 | }
699 |
700 | - (void)addSubclassTracker:(RFAspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName {
701 | NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName];
702 | if (!trackerSet) {
703 | trackerSet = [NSMutableSet new];
704 | self.selectorNamesToSubclassTrackers[selectorName] = trackerSet;
705 | }
706 | [trackerSet addObject:subclassTracker];
707 | }
708 | - (void)removeSubclassTracker:(RFAspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName {
709 | NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName];
710 | [trackerSet removeObject:subclassTracker];
711 | if (trackerSet.count == 0) {
712 | [self.selectorNamesToSubclassTrackers removeObjectForKey:selectorName];
713 | }
714 | }
715 | - (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName {
716 | NSMutableSet *hookingSubclassTrackers = [NSMutableSet new];
717 | for (RFAspectTracker *tracker in self.selectorNamesToSubclassTrackers[selectorName]) {
718 | if ([tracker.selectorNames containsObject:selectorName]) {
719 | [hookingSubclassTrackers addObject:tracker];
720 | }
721 | [hookingSubclassTrackers unionSet:[tracker subclassTrackersHookingSelectorName:selectorName]];
722 | }
723 | return hookingSubclassTrackers;
724 | }
725 | - (NSString *)trackedClassName {
726 | return NSStringFromClass(self.trackedClass);
727 | }
728 |
729 | - (NSString *)description {
730 | return [NSString stringWithFormat:@"<%@: %@, trackedClass: %@, selectorNames:%@, subclass selector names: %@>", self.class, self, NSStringFromClass(self.trackedClass), self.selectorNames, self.selectorNamesToSubclassTrackers.allKeys];
731 | }
732 |
733 | @end
734 |
735 | ///////////////////////////////////////////////////////////////////////////////////////////
736 | #pragma mark - NSInvocation (RFAspects)
737 |
738 | @implementation NSInvocation (RFAspects)
739 |
740 | // Thanks to the ReactiveCocoa team for providing a generic solution for this.
741 | - (id)aspect_argumentAtIndex:(NSUInteger)index {
742 | const char *argType = [self.methodSignature getArgumentTypeAtIndex:index];
743 | // Skip const type qualifier.
744 | if (argType[0] == _C_CONST) argType++;
745 |
746 | #define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0)
747 | if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) {
748 | __autoreleasing id returnObj;
749 | [self getArgument:&returnObj atIndex:(NSInteger)index];
750 | return returnObj;
751 | } else if (strcmp(argType, @encode(SEL)) == 0) {
752 | SEL selector = 0;
753 | [self getArgument:&selector atIndex:(NSInteger)index];
754 | return NSStringFromSelector(selector);
755 | } else if (strcmp(argType, @encode(Class)) == 0) {
756 | __autoreleasing Class theClass = Nil;
757 | [self getArgument:&theClass atIndex:(NSInteger)index];
758 | return theClass;
759 | // Using this list will box the number with the appropriate constructor, instead of the generic NSValue.
760 | } else if (strcmp(argType, @encode(char)) == 0) {
761 | WRAP_AND_RETURN(char);
762 | } else if (strcmp(argType, @encode(int)) == 0) {
763 | WRAP_AND_RETURN(int);
764 | } else if (strcmp(argType, @encode(short)) == 0) {
765 | WRAP_AND_RETURN(short);
766 | } else if (strcmp(argType, @encode(long)) == 0) {
767 | WRAP_AND_RETURN(long);
768 | } else if (strcmp(argType, @encode(long long)) == 0) {
769 | WRAP_AND_RETURN(long long);
770 | } else if (strcmp(argType, @encode(unsigned char)) == 0) {
771 | WRAP_AND_RETURN(unsigned char);
772 | } else if (strcmp(argType, @encode(unsigned int)) == 0) {
773 | WRAP_AND_RETURN(unsigned int);
774 | } else if (strcmp(argType, @encode(unsigned short)) == 0) {
775 | WRAP_AND_RETURN(unsigned short);
776 | } else if (strcmp(argType, @encode(unsigned long)) == 0) {
777 | WRAP_AND_RETURN(unsigned long);
778 | } else if (strcmp(argType, @encode(unsigned long long)) == 0) {
779 | WRAP_AND_RETURN(unsigned long long);
780 | } else if (strcmp(argType, @encode(float)) == 0) {
781 | WRAP_AND_RETURN(float);
782 | } else if (strcmp(argType, @encode(double)) == 0) {
783 | WRAP_AND_RETURN(double);
784 | } else if (strcmp(argType, @encode(BOOL)) == 0) {
785 | WRAP_AND_RETURN(BOOL);
786 | } else if (strcmp(argType, @encode(bool)) == 0) {
787 | WRAP_AND_RETURN(BOOL);
788 | } else if (strcmp(argType, @encode(char *)) == 0) {
789 | WRAP_AND_RETURN(const char *);
790 | } else if (strcmp(argType, @encode(void (^)(void))) == 0) {
791 | __unsafe_unretained id block = nil;
792 | [self getArgument:&block atIndex:(NSInteger)index];
793 | return [block copy];
794 | } else {
795 | NSUInteger valueSize = 0;
796 | NSGetSizeAndAlignment(argType, &valueSize, NULL);
797 |
798 | unsigned char valueBytes[valueSize];
799 | [self getArgument:valueBytes atIndex:(NSInteger)index];
800 |
801 | return [NSValue valueWithBytes:valueBytes objCType:argType];
802 | }
803 | return nil;
804 | #undef WRAP_AND_RETURN
805 | }
806 |
807 | - (NSArray *)aspects_arguments {
808 | NSMutableArray *argumentsArray = [NSMutableArray array];
809 | for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) {
810 | [argumentsArray addObject:[self aspect_argumentAtIndex:idx] ?: NSNull.null];
811 | }
812 | return [argumentsArray copy];
813 | }
814 |
815 | @end
816 |
817 | ///////////////////////////////////////////////////////////////////////////////////////////
818 | #pragma mark - AspectIdentifier
819 |
820 | @implementation RFAspectIdentifier
821 |
822 | + (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(RFAspectOptions)options block:(id)block error:(NSError **)error {
823 | NSCParameterAssert(block);
824 | NSCParameterAssert(selector);
825 | NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
826 | if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
827 | return nil;
828 | }
829 |
830 | RFAspectIdentifier *identifier = nil;
831 | if (blockSignature) {
832 | identifier = [RFAspectIdentifier new];
833 | identifier.selector = selector;
834 | identifier.block = block;
835 | identifier.blockSignature = blockSignature;
836 | identifier.options = options;
837 | identifier.object = object; // weak
838 | }
839 | return identifier;
840 | }
841 |
842 | - (BOOL)invokeWithInfo:(id)info {
843 | NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
844 | NSInvocation *originalInvocation = info.originalInvocation;
845 | NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
846 |
847 | // Be extra paranoid. We already check that on hook registration.
848 | if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
849 | AspectLogError(@"Block has too many arguments. Not calling %@", info);
850 | return NO;
851 | }
852 |
853 | // The `self` of the block will be the RFAspectInfo. Optional.
854 | if (numberOfArguments > 1) {
855 | [blockInvocation setArgument:&info atIndex:1];
856 | }
857 |
858 | void *argBuf = NULL;
859 | for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
860 | const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
861 | NSUInteger argSize;
862 | NSGetSizeAndAlignment(type, &argSize, NULL);
863 |
864 | if (!(argBuf = reallocf(argBuf, argSize))) {
865 | AspectLogError(@"Failed to allocate memory for block invocation.");
866 | return NO;
867 | }
868 |
869 | [originalInvocation getArgument:argBuf atIndex:idx];
870 | [blockInvocation setArgument:argBuf atIndex:idx];
871 | }
872 |
873 | [blockInvocation invokeWithTarget:self.block];
874 |
875 | if (argBuf != NULL) {
876 | free(argBuf);
877 | }
878 | return YES;
879 | }
880 |
881 | - (NSString *)description {
882 | return [NSString stringWithFormat:@"<%@: %p, SEL:%@ object:%@ options:%tu block:%@ (#%tu args)>", self.class, self, NSStringFromSelector(self.selector), self.object, self.options, self.block, self.blockSignature.numberOfArguments];
883 | }
884 |
885 | - (BOOL)remove {
886 | return aspect_remove(self, NULL);
887 | }
888 |
889 | @end
890 |
891 | ///////////////////////////////////////////////////////////////////////////////////////////
892 | #pragma mark - AspectsContainer
893 |
894 | @implementation RFAspectsContainer
895 |
896 | - (BOOL)hasAspects {
897 | return self.beforeAspects.count > 0 || self.insteadAspects.count > 0 || self.afterAspects.count > 0;
898 | }
899 |
900 | - (void)addAspect:(RFAspectIdentifier *)aspect withOptions:(RFAspectOptions)options {
901 | NSParameterAssert(aspect);
902 | NSUInteger position = options&AspectPositionFilter;
903 | switch (position) {
904 | case AspectPositionBefore: self.beforeAspects = [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break;
905 | case AspectPositionInstead: self.insteadAspects = [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break;
906 | case AspectPositionAfter: self.afterAspects = [(self.afterAspects ?:@[]) arrayByAddingObject:aspect]; break;
907 | }
908 | }
909 |
910 | - (BOOL)removeAspect:(id)aspect {
911 | for (NSString *aspectArrayName in @[NSStringFromSelector(@selector(beforeAspects)),
912 | NSStringFromSelector(@selector(insteadAspects)),
913 | NSStringFromSelector(@selector(afterAspects))]) {
914 | NSArray *array = [self valueForKey:aspectArrayName];
915 | NSUInteger index = [array indexOfObjectIdenticalTo:aspect];
916 | if (array && index != NSNotFound) {
917 | NSMutableArray *newArray = [NSMutableArray arrayWithArray:array];
918 | [newArray removeObjectAtIndex:index];
919 | [self setValue:newArray forKey:aspectArrayName];
920 | return YES;
921 | }
922 | }
923 | return NO;
924 | }
925 |
926 | - (NSString *)description {
927 | return [NSString stringWithFormat:@"<%@: %p, before:%@, instead:%@, after:%@>", self.class, self, self.beforeAspects, self.insteadAspects, self.afterAspects];
928 | }
929 |
930 | @end
931 |
932 | ///////////////////////////////////////////////////////////////////////////////////////////
933 | #pragma mark - AspectInfo
934 |
935 | @implementation RFAspectInfo
936 |
937 | @synthesize arguments = _arguments;
938 |
939 | - (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation {
940 | NSCParameterAssert(instance);
941 | NSCParameterAssert(invocation);
942 | if (self = [super init]) {
943 | _instance = instance;
944 | _originalInvocation = invocation;
945 | }
946 | return self;
947 | }
948 |
949 | - (NSArray *)arguments {
950 | // Lazily evaluate arguments, boxing is expensive.
951 | if (!_arguments) {
952 | _arguments = self.originalInvocation.aspects_arguments;
953 | }
954 | return _arguments;
955 | }
956 |
957 | @end
958 |
--------------------------------------------------------------------------------