├── plugin.gif
├── Screenshot.png
├── TOCAssetCatalogBackground.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcshareddata
│ └── xcschemes
│ │ └── TOCAssetCatalogBackground.xcscheme
└── project.pbxproj
├── TOCAssetCatalogBackground
├── TOCAssetCatalogBackground.h
├── TOCAssetCatalogBackgroundButtonTarget.h
├── Info.plist
├── TOCAssetCatalogBackgroundButtonTarget.m
└── TOCAssetCatalogBackground.m
├── Readme.md
├── LICENSE.txt
└── contrib
├── LICENSE_Aspects.txt
├── Aspects.h
└── Aspects.m
/plugin.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toco/TOCAssetCatalogBackground/HEAD/plugin.gif
--------------------------------------------------------------------------------
/Screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toco/TOCAssetCatalogBackground/HEAD/Screenshot.png
--------------------------------------------------------------------------------
/TOCAssetCatalogBackground.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TOCAssetCatalogBackground/TOCAssetCatalogBackground.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOCAssetCatalogBackground.h
3 | // TOCAssetCatalogBackground
4 | //
5 | // Created by Tobias Conradi on 17.05.15.
6 | // Copyright (c) 2015 Tobias Conradi. Licensed under the MIT license.
7 | //
8 |
9 | #import
10 |
11 | @interface TOCAssetCatalogBackground : NSObject
12 |
13 | + (instancetype)sharedPlugin;
14 |
15 | @property (nonatomic, strong, readonly) NSBundle* bundle;
16 | @end
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # TOCAssetCatalogBackground
2 | Xcode plugin to switch between white and dark background color in the asset catalog viewer.
3 |
4 | 
5 |
6 | ## Installation
7 |
8 | Either
9 |
10 | - Clone and build the plugin yourself, it will be installed to the right location automatically by building it.
11 |
12 | or
13 |
14 | - Install it via [Alcatraz](http://alcatraz.io/)
15 |
16 | In any case, relaunch Xcode to load it.
17 |
18 | ## License
19 | MIT License, see LICENSE.txt.
--------------------------------------------------------------------------------
/TOCAssetCatalogBackground/TOCAssetCatalogBackgroundButtonTarget.h:
--------------------------------------------------------------------------------
1 | //
2 | // TOCAssetCatalogBackgroundButtonTarget.h
3 | // TOCAssetCatalogBackground
4 | //
5 | // Created by Tobias Conradi on 17.05.15.
6 | // Copyright (c) 2015 Tobias Conradi. Licensed under the MIT license.
7 | //
8 |
9 | #import
10 | typedef NS_ENUM(NSUInteger, TOCAssetCatalogBackgroundType) {
11 | TOCAssetCatalogBackgroundTypeLightBackground,
12 | TOCAssetCatalogBackgroundTypeDarkBackground
13 | };
14 | extern NSString *const TOCAssetCatalogBackgroundColorChangedNotification;
15 | extern TOCAssetCatalogBackgroundType TOCAssetCatalogBackgroundCurrentBackgroundType;
16 |
17 | @interface TOCAssetCatalogBackgroundButtonTarget : NSObject
18 | @property (nonatomic, weak) NSScrollView *scrollView;
19 | - (void)updateBackgroundColor;
20 | - (void)segmentedControlChanged:(id)sender;
21 | @end
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Tobias Conradi, github@tobias-conradi.de
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.
--------------------------------------------------------------------------------
/contrib/LICENSE_Aspects.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Peter Steinberger, steipete@gmail.com
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.
--------------------------------------------------------------------------------
/TOCAssetCatalogBackground/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | English
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | de.tobias-conradi.$(PRODUCT_NAME:rfc1034identifier)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | BNDL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | DVTPlugInCompatibilityUUIDs
26 |
27 | 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90
28 | C4A681B0-4A26-480E-93EC-1218098B9AA0
29 | AABB7188-E14E-4433-AD3B-5CD791EAD9A3
30 | AD68E85B-441B-4301-B564-A45E4919A6AD
31 | A16FF353-8441-459E-A50C-B071F53F51B7
32 | 9F75337B-21B4-4ADC-B558-F9CADF7073A7
33 | E969541F-E6F9-4D25-8158-72DC3545A6C6
34 |
35 | LSMinimumSystemVersion
36 | $(MACOSX_DEPLOYMENT_TARGET)
37 | NSPrincipalClass
38 | TOCAssetCatalogBackground
39 | XC4Compatible
40 |
41 | XCPluginHasUI
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/TOCAssetCatalogBackground/TOCAssetCatalogBackgroundButtonTarget.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOCAssetCatalogBackgroundButtonTarget.m
3 | // TOCAssetCatalogBackground
4 | //
5 | // Created by Tobias Conradi on 17.05.15.
6 | // Copyright (c) 2015 Tobias Conradi. Licensed under the MIT license.
7 | //
8 |
9 | #import "TOCAssetCatalogBackgroundButtonTarget.h"
10 |
11 | NSString *const TOCAssetCatalogBackgroundColorChangedNotification = @"TOCAssetCatalogBackgroundColorChanged";
12 | TOCAssetCatalogBackgroundType TOCAssetCatalogBackgroundCurrentBackgroundType = TOCAssetCatalogBackgroundTypeLightBackground;
13 |
14 | @implementation TOCAssetCatalogBackgroundButtonTarget
15 |
16 | - (instancetype)init
17 | {
18 | self = [super init];
19 | if (self) {
20 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateBackgroundColor) name:TOCAssetCatalogBackgroundColorChangedNotification object:nil];
21 | }
22 | return self;
23 | }
24 |
25 | - (void)dealloc
26 | {
27 | [[NSNotificationCenter defaultCenter] removeObserver:self];
28 | }
29 |
30 | - (void)updateBackgroundColor
31 | {
32 | NSColor *nextColor = nil;
33 | switch (TOCAssetCatalogBackgroundCurrentBackgroundType) {
34 | case TOCAssetCatalogBackgroundTypeDarkBackground:
35 | nextColor = [NSColor colorWithWhite:0.1 alpha:1.0];
36 | break;
37 | case TOCAssetCatalogBackgroundTypeLightBackground:
38 | default:
39 | nextColor = [NSColor whiteColor];
40 | }
41 | self.scrollView.backgroundColor = nextColor;
42 | }
43 |
44 | - (void)segmentedControlChanged:(id)sender
45 | {
46 | switch (TOCAssetCatalogBackgroundCurrentBackgroundType) {
47 | case TOCAssetCatalogBackgroundTypeDarkBackground:
48 | TOCAssetCatalogBackgroundCurrentBackgroundType = TOCAssetCatalogBackgroundTypeLightBackground;
49 | break;
50 | case TOCAssetCatalogBackgroundTypeLightBackground:
51 | default:
52 | TOCAssetCatalogBackgroundCurrentBackgroundType = TOCAssetCatalogBackgroundTypeDarkBackground;
53 | }
54 | [[NSNotificationCenter defaultCenter] postNotificationName:TOCAssetCatalogBackgroundColorChangedNotification object:self];
55 | }
56 |
57 | @end
58 |
--------------------------------------------------------------------------------
/TOCAssetCatalogBackground.xcodeproj/xcshareddata/xcschemes/TOCAssetCatalogBackground.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
46 |
47 |
58 |
60 |
61 |
62 |
68 |
69 |
70 |
71 |
72 |
73 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/contrib/Aspects.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, AspectOptions) {
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 TOCAssetCatalogBackground_AspectToken
20 |
21 | /// Deregisters an aspect.
22 | /// @return YES if deregistration is successful, otherwise NO.
23 | - (BOOL)remove;
24 |
25 | @end
26 |
27 | /// The TOCAssetCatalogBackground_AspectInfo protocol is the first parameter of our block syntax.
28 | @protocol TOCAssetCatalogBackground_AspectInfo
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 (Aspects)
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)tocassetcatalogbackground_aspect_hookSelector:(SEL)selector
58 | withOptions:(AspectOptions)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)tocassetcatalogbackground_aspect_hookSelector:(SEL)selector
64 | withOptions:(AspectOptions)options
65 | usingBlock:(id)block
66 | error:(NSError **)error;
67 |
68 | @end
69 |
70 |
71 | typedef NS_ENUM(NSUInteger, AspectErrorCode) {
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 TOCAssetCatalogBackground_AspectErrorDomain;
84 |
--------------------------------------------------------------------------------
/TOCAssetCatalogBackground/TOCAssetCatalogBackground.m:
--------------------------------------------------------------------------------
1 | //
2 | // TOCAssetCatalogBackground.m
3 | // TOCAssetCatalogBackground
4 | //
5 | // Created by Tobias Conradi on 17.05.15.
6 | // Copyright (c) 2015 Tobias Conradi. Licensed under the MIT license.
7 | //
8 |
9 | #import
10 | #import "TOCAssetCatalogBackground.h"
11 | #import "TOCAssetCatalogBackgroundButtonTarget.h"
12 | #import "Aspects.h"
13 |
14 | static TOCAssetCatalogBackground *sharedPlugin;
15 |
16 | @interface NSObject (ShutUpWarnings)
17 | + (id)barButtonWithTitle:(id)arg1;
18 | - (id)effectiveTitleColor;
19 | - (void)refreshHighlightState;
20 | @end
21 |
22 | @interface TOCAssetCatalogBackground()
23 |
24 | @property (nonatomic, strong, readwrite) NSBundle *bundle;
25 | @property (nonatomic) BOOL hookedIBICAbstractCatalogDetailController;
26 | @property (nonatomic) BOOL hookIBICMultipartImageView;
27 |
28 | @end
29 |
30 | @implementation TOCAssetCatalogBackground
31 |
32 | + (void)pluginDidLoad:(NSBundle *)plugin
33 | {
34 | static dispatch_once_t onceToken;
35 | NSString *currentApplicationName = [[NSBundle mainBundle] infoDictionary][@"CFBundleName"];
36 | if ([currentApplicationName isEqual:@"Xcode"]) {
37 | dispatch_once(&onceToken, ^{
38 | sharedPlugin = [[self alloc] initWithBundle:plugin];
39 | });
40 | }
41 | }
42 |
43 | + (instancetype)sharedPlugin
44 | {
45 | return sharedPlugin;
46 | }
47 |
48 | - (id)initWithBundle:(NSBundle *)plugin
49 | {
50 | if (self = [super init]) {
51 | // reference to plugin's bundle, for resource access
52 | self.bundle = plugin;
53 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hookClasses) name:NSBundleDidLoadNotification object:nil];
54 | [self hookClasses];
55 |
56 | }
57 | return self;
58 | }
59 |
60 | // since official plugins might not be loaded when we try to hook the classes,
61 | // we might need to wait for the bundles to load.
62 | // therefore hookClasses gets called every time a bundle is loaded, so we keep state
63 | // which class was hooked successfully.
64 | - (void)hookClasses {
65 | if (!self.hookedIBICAbstractCatalogDetailController) {
66 | self.hookedIBICAbstractCatalogDetailController = [self hookIBICAbstractCatalogDetailController];
67 | }
68 | if (!self.hookIBICMultipartImageView) {
69 | self.hookIBICMultipartImageView = [self hookIBICMultipartImageView];
70 | }
71 |
72 | BOOL allHooked = self.hookedIBICAbstractCatalogDetailController && self.hookIBICMultipartImageView;
73 | if (allHooked) {
74 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSBundleDidLoadNotification object:nil];
75 | }
76 | }
77 |
78 | - (BOOL)hookIBICAbstractCatalogDetailController
79 | {
80 |
81 | id catalogControllerClass = NSClassFromString(@"IBICAbstractCatalogDetailController");
82 | if (!catalogControllerClass) {
83 | return NO;
84 | }
85 |
86 | NSError *error;
87 | [catalogControllerClass tocassetcatalogbackground_aspect_hookSelector:@selector(viewDidLoad)
88 | withOptions:AspectPositionAfter
89 | usingBlock:^(id info) {
90 | [self abstractCatalogDetailControllerDidLoad:(NSViewController *)info.instance];
91 | }
92 | error:&error];
93 | if (error != nil) {
94 | NSLog(@"Failed to hook -[IBICAbstractCatalogDetailController viewDidLoad] with error: %@",error);
95 | }
96 |
97 | return YES;
98 | }
99 |
100 | - (void)abstractCatalogDetailControllerDidLoad:(NSViewController *)controller
101 | {
102 | NSSegmentedControl *barButton = [NSClassFromString(@"IBAccessorizedScrollViewButtonBar") barButtonWithTitle:@"Change Background Color"];
103 |
104 | NSScrollView *scrollView = [controller valueForKey:@"scrollView"];
105 | id buttonBar = [scrollView valueForKey:@"buttonBar"];
106 |
107 | NSMutableArray *buttonsArray = [[buttonBar valueForKey:@"rightViews"] mutableCopy];
108 | [buttonsArray insertObject:barButton atIndex:0];
109 | [buttonBar setValue:[buttonsArray copy] forKey:@"rightViews"];
110 |
111 | TOCAssetCatalogBackgroundButtonTarget *target = [TOCAssetCatalogBackgroundButtonTarget new];
112 | target.scrollView = scrollView;
113 | barButton.target = target;
114 | barButton.action = @selector(segmentedControlChanged:);
115 | [target updateBackgroundColor];
116 |
117 | const void *key = @selector(segmentedControlChanged:); // just need a key
118 | objc_setAssociatedObject(barButton,key, target, OBJC_ASSOCIATION_RETAIN);
119 | }
120 |
121 | - (BOOL)hookIBICMultipartImageView
122 | {
123 | id multiPartImageViewClass = NSClassFromString(@"IBICMultipartImageView");
124 | if (!multiPartImageViewClass) {
125 | return NO;
126 | }
127 | NSError *error;
128 |
129 | void (^effectiveTitleColorBlock)(id) =
130 | ^(id info){
131 | BOOL replaceColor = YES;
132 | id instance = [info instance];
133 | NSInvocation *invocation = [info originalInvocation];
134 |
135 | if (TOCAssetCatalogBackgroundCurrentBackgroundType != TOCAssetCatalogBackgroundTypeDarkBackground) {
136 | replaceColor = NO;
137 | }
138 | if ([[instance valueForKey:@"wholeSetShowsSelection"] boolValue]) {
139 | replaceColor = NO;
140 | }
141 | if (replaceColor) {
142 | __unsafe_unretained NSColor *color = [NSColor whiteColor];
143 | [invocation setReturnValue:(void *)&color];
144 | } else {
145 | [invocation invoke];
146 | }
147 | };
148 |
149 | [multiPartImageViewClass tocassetcatalogbackground_aspect_hookSelector:@selector(effectiveTitleColor)
150 | withOptions:AspectPositionInstead
151 | usingBlock:effectiveTitleColorBlock
152 | error:&error];
153 | if (error != nil) {
154 | NSLog(@"Failed to hook -[IBICMultipartImageView effectiveTitleColor] with error: %@",error);
155 | error = nil;
156 | }
157 |
158 |
159 | void (^viewDidMoveToWindowBlock)(id) =
160 | ^(id info){
161 | NSView *instance = [info instance];
162 | if (instance.window) {
163 | [[NSNotificationCenter defaultCenter] addObserver:instance selector:@selector(refreshHighlightState) name:TOCAssetCatalogBackgroundColorChangedNotification object:nil];
164 | } else {
165 | [[NSNotificationCenter defaultCenter] removeObserver:instance name:TOCAssetCatalogBackgroundColorChangedNotification object:nil];
166 | }
167 | };
168 |
169 | [multiPartImageViewClass tocassetcatalogbackground_aspect_hookSelector:@selector(viewDidMoveToWindow)
170 | withOptions:AspectPositionAfter
171 | usingBlock:viewDidMoveToWindowBlock
172 | error:&error];
173 | if (error != nil) {
174 | NSLog(@"Failed to hook -[IBICMultipartImageView viewDidMoveToWindow] with error: %@",error);
175 | }
176 | return YES;
177 | }
178 |
179 | - (void)dealloc
180 | {
181 | [[NSNotificationCenter defaultCenter] removeObserver:self];
182 | }
183 |
184 | @end
185 |
--------------------------------------------------------------------------------
/TOCAssetCatalogBackground.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | DD0386A01B08C3BD004925EA /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD03869F1B08C3BD004925EA /* AppKit.framework */; };
11 | DD0386A21B08C3BD004925EA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD0386A11B08C3BD004925EA /* Foundation.framework */; };
12 | DD0386A71B08C3BD004925EA /* TOCAssetCatalogBackground.xcscheme in Resources */ = {isa = PBXBuildFile; fileRef = DD0386A61B08C3BD004925EA /* TOCAssetCatalogBackground.xcscheme */; };
13 | DD0386AA1B08C3BD004925EA /* TOCAssetCatalogBackground.m in Sources */ = {isa = PBXBuildFile; fileRef = DD0386A91B08C3BD004925EA /* TOCAssetCatalogBackground.m */; };
14 | DD27848E1B092FBB00FF617B /* TOCAssetCatalogBackgroundButtonTarget.m in Sources */ = {isa = PBXBuildFile; fileRef = DD27848D1B092FBB00FF617B /* TOCAssetCatalogBackgroundButtonTarget.m */; };
15 | DD2784901B0930CC00FF617B /* LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = DD27848F1B0930CC00FF617B /* LICENSE.txt */; };
16 | DD4C2D951B09233700170340 /* Aspects.m in Sources */ = {isa = PBXBuildFile; fileRef = DD4C2D931B09233700170340 /* Aspects.m */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXFileReference section */
20 | DD03869C1B08C3BD004925EA /* TOCAssetCatalogBackground.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TOCAssetCatalogBackground.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; };
21 | DD03869F1B08C3BD004925EA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; };
22 | DD0386A11B08C3BD004925EA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; };
23 | DD0386A51B08C3BD004925EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
24 | DD0386A61B08C3BD004925EA /* TOCAssetCatalogBackground.xcscheme */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = TOCAssetCatalogBackground.xcscheme; path = TOCAssetCatalogBackground.xcodeproj/xcshareddata/xcschemes/TOCAssetCatalogBackground.xcscheme; sourceTree = SOURCE_ROOT; };
25 | DD0386A81B08C3BD004925EA /* TOCAssetCatalogBackground.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TOCAssetCatalogBackground.h; sourceTree = ""; };
26 | DD0386A91B08C3BD004925EA /* TOCAssetCatalogBackground.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TOCAssetCatalogBackground.m; sourceTree = ""; };
27 | DD27848C1B092FBB00FF617B /* TOCAssetCatalogBackgroundButtonTarget.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TOCAssetCatalogBackgroundButtonTarget.h; sourceTree = ""; };
28 | DD27848D1B092FBB00FF617B /* TOCAssetCatalogBackgroundButtonTarget.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TOCAssetCatalogBackgroundButtonTarget.m; sourceTree = ""; };
29 | DD27848F1B0930CC00FF617B /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE.txt; path = TOCAssetCatalogBackground.xcodeproj/../LICENSE.txt; sourceTree = SOURCE_ROOT; };
30 | DD4C2D921B09233700170340 /* Aspects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Aspects.h; sourceTree = ""; };
31 | DD4C2D931B09233700170340 /* Aspects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Aspects.m; sourceTree = ""; };
32 | DD4C2D941B09233700170340 /* LICENSE_Aspects.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE_Aspects.txt; sourceTree = ""; };
33 | /* End PBXFileReference section */
34 |
35 | /* Begin PBXFrameworksBuildPhase section */
36 | DD03869A1B08C3BD004925EA /* Frameworks */ = {
37 | isa = PBXFrameworksBuildPhase;
38 | buildActionMask = 2147483647;
39 | files = (
40 | DD0386A01B08C3BD004925EA /* AppKit.framework in Frameworks */,
41 | DD0386A21B08C3BD004925EA /* Foundation.framework in Frameworks */,
42 | );
43 | runOnlyForDeploymentPostprocessing = 0;
44 | };
45 | /* End PBXFrameworksBuildPhase section */
46 |
47 | /* Begin PBXGroup section */
48 | DD0386931B08C3BD004925EA = {
49 | isa = PBXGroup;
50 | children = (
51 | DD4C2D911B09233700170340 /* contrib */,
52 | DD0386A31B08C3BD004925EA /* TOCAssetCatalogBackground */,
53 | DD03869E1B08C3BD004925EA /* Frameworks */,
54 | DD03869D1B08C3BD004925EA /* Products */,
55 | );
56 | sourceTree = "";
57 | };
58 | DD03869D1B08C3BD004925EA /* Products */ = {
59 | isa = PBXGroup;
60 | children = (
61 | DD03869C1B08C3BD004925EA /* TOCAssetCatalogBackground.xcplugin */,
62 | );
63 | name = Products;
64 | sourceTree = "";
65 | };
66 | DD03869E1B08C3BD004925EA /* Frameworks */ = {
67 | isa = PBXGroup;
68 | children = (
69 | DD03869F1B08C3BD004925EA /* AppKit.framework */,
70 | DD0386A11B08C3BD004925EA /* Foundation.framework */,
71 | );
72 | name = Frameworks;
73 | sourceTree = "";
74 | };
75 | DD0386A31B08C3BD004925EA /* TOCAssetCatalogBackground */ = {
76 | isa = PBXGroup;
77 | children = (
78 | DD0386A81B08C3BD004925EA /* TOCAssetCatalogBackground.h */,
79 | DD0386A91B08C3BD004925EA /* TOCAssetCatalogBackground.m */,
80 | DD27848C1B092FBB00FF617B /* TOCAssetCatalogBackgroundButtonTarget.h */,
81 | DD27848D1B092FBB00FF617B /* TOCAssetCatalogBackgroundButtonTarget.m */,
82 | DD0386A41B08C3BD004925EA /* Supporting Files */,
83 | );
84 | path = TOCAssetCatalogBackground;
85 | sourceTree = "";
86 | };
87 | DD0386A41B08C3BD004925EA /* Supporting Files */ = {
88 | isa = PBXGroup;
89 | children = (
90 | DD27848F1B0930CC00FF617B /* LICENSE.txt */,
91 | DD0386A51B08C3BD004925EA /* Info.plist */,
92 | DD0386A61B08C3BD004925EA /* TOCAssetCatalogBackground.xcscheme */,
93 | );
94 | name = "Supporting Files";
95 | sourceTree = "";
96 | };
97 | DD4C2D911B09233700170340 /* contrib */ = {
98 | isa = PBXGroup;
99 | children = (
100 | DD4C2D921B09233700170340 /* Aspects.h */,
101 | DD4C2D931B09233700170340 /* Aspects.m */,
102 | DD4C2D941B09233700170340 /* LICENSE_Aspects.txt */,
103 | );
104 | path = contrib;
105 | sourceTree = "";
106 | };
107 | /* End PBXGroup section */
108 |
109 | /* Begin PBXNativeTarget section */
110 | DD03869B1B08C3BD004925EA /* TOCAssetCatalogBackground */ = {
111 | isa = PBXNativeTarget;
112 | buildConfigurationList = DD0386AD1B08C3BD004925EA /* Build configuration list for PBXNativeTarget "TOCAssetCatalogBackground" */;
113 | buildPhases = (
114 | DD0386981B08C3BD004925EA /* Sources */,
115 | DD0386991B08C3BD004925EA /* Resources */,
116 | DD03869A1B08C3BD004925EA /* Frameworks */,
117 | );
118 | buildRules = (
119 | );
120 | dependencies = (
121 | );
122 | name = TOCAssetCatalogBackground;
123 | productName = TOCAssetCatalogBackground;
124 | productReference = DD03869C1B08C3BD004925EA /* TOCAssetCatalogBackground.xcplugin */;
125 | productType = "com.apple.product-type.bundle";
126 | };
127 | /* End PBXNativeTarget section */
128 |
129 | /* Begin PBXProject section */
130 | DD0386941B08C3BD004925EA /* Project object */ = {
131 | isa = PBXProject;
132 | attributes = {
133 | LastUpgradeCheck = 0630;
134 | ORGANIZATIONNAME = "Tobias Conradi";
135 | TargetAttributes = {
136 | DD03869B1B08C3BD004925EA = {
137 | CreatedOnToolsVersion = 6.3.1;
138 | };
139 | };
140 | };
141 | buildConfigurationList = DD0386971B08C3BD004925EA /* Build configuration list for PBXProject "TOCAssetCatalogBackground" */;
142 | compatibilityVersion = "Xcode 3.2";
143 | developmentRegion = English;
144 | hasScannedForEncodings = 0;
145 | knownRegions = (
146 | en,
147 | );
148 | mainGroup = DD0386931B08C3BD004925EA;
149 | productRefGroup = DD03869D1B08C3BD004925EA /* Products */;
150 | projectDirPath = "";
151 | projectRoot = "";
152 | targets = (
153 | DD03869B1B08C3BD004925EA /* TOCAssetCatalogBackground */,
154 | );
155 | };
156 | /* End PBXProject section */
157 |
158 | /* Begin PBXResourcesBuildPhase section */
159 | DD0386991B08C3BD004925EA /* Resources */ = {
160 | isa = PBXResourcesBuildPhase;
161 | buildActionMask = 2147483647;
162 | files = (
163 | DD2784901B0930CC00FF617B /* LICENSE.txt in Resources */,
164 | DD0386A71B08C3BD004925EA /* TOCAssetCatalogBackground.xcscheme in Resources */,
165 | );
166 | runOnlyForDeploymentPostprocessing = 0;
167 | };
168 | /* End PBXResourcesBuildPhase section */
169 |
170 | /* Begin PBXSourcesBuildPhase section */
171 | DD0386981B08C3BD004925EA /* Sources */ = {
172 | isa = PBXSourcesBuildPhase;
173 | buildActionMask = 2147483647;
174 | files = (
175 | DD0386AA1B08C3BD004925EA /* TOCAssetCatalogBackground.m in Sources */,
176 | DD4C2D951B09233700170340 /* Aspects.m in Sources */,
177 | DD27848E1B092FBB00FF617B /* TOCAssetCatalogBackgroundButtonTarget.m in Sources */,
178 | );
179 | runOnlyForDeploymentPostprocessing = 0;
180 | };
181 | /* End PBXSourcesBuildPhase section */
182 |
183 | /* Begin XCBuildConfiguration section */
184 | DD0386AB1B08C3BD004925EA /* Debug */ = {
185 | isa = XCBuildConfiguration;
186 | buildSettings = {
187 | ALWAYS_SEARCH_USER_PATHS = NO;
188 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
189 | CLANG_CXX_LIBRARY = "libc++";
190 | CLANG_ENABLE_MODULES = YES;
191 | CLANG_ENABLE_OBJC_ARC = YES;
192 | CLANG_WARN_BOOL_CONVERSION = YES;
193 | CLANG_WARN_CONSTANT_CONVERSION = YES;
194 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
195 | CLANG_WARN_EMPTY_BODY = YES;
196 | CLANG_WARN_ENUM_CONVERSION = YES;
197 | CLANG_WARN_INT_CONVERSION = YES;
198 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
199 | CLANG_WARN_UNREACHABLE_CODE = YES;
200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
201 | COPY_PHASE_STRIP = NO;
202 | ENABLE_STRICT_OBJC_MSGSEND = YES;
203 | GCC_C_LANGUAGE_STANDARD = gnu99;
204 | GCC_DYNAMIC_NO_PIC = NO;
205 | GCC_NO_COMMON_BLOCKS = YES;
206 | GCC_OPTIMIZATION_LEVEL = 0;
207 | GCC_PREPROCESSOR_DEFINITIONS = (
208 | "DEBUG=1",
209 | "$(inherited)",
210 | );
211 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
212 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
213 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
214 | GCC_WARN_UNDECLARED_SELECTOR = YES;
215 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
216 | GCC_WARN_UNUSED_FUNCTION = YES;
217 | GCC_WARN_UNUSED_VARIABLE = YES;
218 | MTL_ENABLE_DEBUG_INFO = YES;
219 | ONLY_ACTIVE_ARCH = YES;
220 | };
221 | name = Debug;
222 | };
223 | DD0386AC1B08C3BD004925EA /* Release */ = {
224 | isa = XCBuildConfiguration;
225 | buildSettings = {
226 | ALWAYS_SEARCH_USER_PATHS = NO;
227 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
228 | CLANG_CXX_LIBRARY = "libc++";
229 | CLANG_ENABLE_MODULES = YES;
230 | CLANG_ENABLE_OBJC_ARC = YES;
231 | CLANG_WARN_BOOL_CONVERSION = YES;
232 | CLANG_WARN_CONSTANT_CONVERSION = YES;
233 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
234 | CLANG_WARN_EMPTY_BODY = YES;
235 | CLANG_WARN_ENUM_CONVERSION = YES;
236 | CLANG_WARN_INT_CONVERSION = YES;
237 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
238 | CLANG_WARN_UNREACHABLE_CODE = YES;
239 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
240 | COPY_PHASE_STRIP = NO;
241 | ENABLE_NS_ASSERTIONS = NO;
242 | ENABLE_STRICT_OBJC_MSGSEND = YES;
243 | GCC_C_LANGUAGE_STANDARD = gnu99;
244 | GCC_NO_COMMON_BLOCKS = YES;
245 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
246 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
247 | GCC_WARN_UNDECLARED_SELECTOR = YES;
248 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
249 | GCC_WARN_UNUSED_FUNCTION = YES;
250 | GCC_WARN_UNUSED_VARIABLE = YES;
251 | MTL_ENABLE_DEBUG_INFO = NO;
252 | };
253 | name = Release;
254 | };
255 | DD0386AE1B08C3BD004925EA /* Debug */ = {
256 | isa = XCBuildConfiguration;
257 | buildSettings = {
258 | COMBINE_HIDPI_IMAGES = YES;
259 | DEPLOYMENT_LOCATION = YES;
260 | DSTROOT = "$(HOME)";
261 | INFOPLIST_FILE = TOCAssetCatalogBackground/Info.plist;
262 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins";
263 | MACOSX_DEPLOYMENT_TARGET = 10.10;
264 | PRODUCT_NAME = "$(TARGET_NAME)";
265 | SDKROOT = macosx;
266 | WRAPPER_EXTENSION = xcplugin;
267 | };
268 | name = Debug;
269 | };
270 | DD0386AF1B08C3BD004925EA /* Release */ = {
271 | isa = XCBuildConfiguration;
272 | buildSettings = {
273 | COMBINE_HIDPI_IMAGES = YES;
274 | DEPLOYMENT_LOCATION = YES;
275 | DSTROOT = "$(HOME)";
276 | INFOPLIST_FILE = TOCAssetCatalogBackground/Info.plist;
277 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins";
278 | MACOSX_DEPLOYMENT_TARGET = 10.10;
279 | PRODUCT_NAME = "$(TARGET_NAME)";
280 | SDKROOT = macosx;
281 | WRAPPER_EXTENSION = xcplugin;
282 | };
283 | name = Release;
284 | };
285 | /* End XCBuildConfiguration section */
286 |
287 | /* Begin XCConfigurationList section */
288 | DD0386971B08C3BD004925EA /* Build configuration list for PBXProject "TOCAssetCatalogBackground" */ = {
289 | isa = XCConfigurationList;
290 | buildConfigurations = (
291 | DD0386AB1B08C3BD004925EA /* Debug */,
292 | DD0386AC1B08C3BD004925EA /* Release */,
293 | );
294 | defaultConfigurationIsVisible = 0;
295 | defaultConfigurationName = Release;
296 | };
297 | DD0386AD1B08C3BD004925EA /* Build configuration list for PBXNativeTarget "TOCAssetCatalogBackground" */ = {
298 | isa = XCConfigurationList;
299 | buildConfigurations = (
300 | DD0386AE1B08C3BD004925EA /* Debug */,
301 | DD0386AF1B08C3BD004925EA /* Release */,
302 | );
303 | defaultConfigurationIsVisible = 0;
304 | defaultConfigurationName = Release;
305 | };
306 | /* End XCConfigurationList section */
307 | };
308 | rootObject = DD0386941B08C3BD004925EA /* Project object */;
309 | }
310 |
--------------------------------------------------------------------------------
/contrib/Aspects.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 "Aspects.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 _TOCAssetCatalogBackground_AspectBlock {
23 | __unused Class isa;
24 | AspectBlockFlags flags;
25 | __unused int reserved;
26 | void (__unused *invoke)(struct _TOCAssetCatalogBackground_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 | } *TOCAssetCatalogBackground_AspectBlockRef;
39 |
40 | @interface TOCAssetCatalogBackground_AspectInfo : 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 TOCAssetCatalogBackground_AspectIdentifier : NSObject
49 | + (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)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) AspectOptions options;
56 | @end
57 |
58 | // Tracks all aspects for an object/class.
59 | @interface TOCAssetCatalogBackground_AspectsContainer : NSObject
60 | - (void)addAspect:(TOCAssetCatalogBackground_AspectIdentifier *)aspect withOptions:(AspectOptions)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 TOCAssetCatalogBackground_AspectTracker : NSObject
69 | - (id)initWithTrackedClass:(Class)trackedClass parent:(TOCAssetCatalogBackground_AspectTracker *)parent;
70 | @property (nonatomic, strong) Class trackedClass;
71 | @property (nonatomic, strong) NSMutableSet *selectorNames;
72 | @property (nonatomic, weak) TOCAssetCatalogBackground_AspectTracker *parentEntry;
73 | @end
74 |
75 | @interface NSInvocation (Aspects)
76 | - (NSArray *)tocassetcatalogbackground_aspects_arguments;
77 | @end
78 |
79 | #define AspectPositionFilter 0x07
80 |
81 | #define AspectError(errorCode, errorDescription) do { \
82 | AspectLogError(@"Aspects: %@", errorDescription); \
83 | if (error) { *error = [NSError errorWithDomain:TOCAssetCatalogBackground_AspectErrorDomain code:errorCode userInfo:@{NSLocalizedDescriptionKey: errorDescription}]; }}while(0)
84 |
85 | NSString *const TOCAssetCatalogBackground_AspectErrorDomain = @"TOCAssetCatalogBackground_AspectErrorDomain";
86 | static NSString *const AspectsSubclassSuffix = @"_TOCAssetCatalogBackground_Aspects_";
87 | static NSString *const AspectsMessagePrefix = @"aspects_";
88 |
89 | @implementation NSObject (Aspects)
90 |
91 | ///////////////////////////////////////////////////////////////////////////////////////////
92 | #pragma mark - Public Aspects API
93 |
94 | + (id)tocassetcatalogbackground_aspect_hookSelector:(SEL)selector
95 | withOptions:(AspectOptions)options
96 | usingBlock:(id)block
97 | error:(NSError **)error {
98 | return aspect_add((id)self, selector, options, block, error);
99 | }
100 |
101 | /// @return A token which allows to later deregister the aspect.
102 | - (id)tocassetcatalogbackground_aspect_hookSelector:(SEL)selector
103 | withOptions:(AspectOptions)options
104 | usingBlock:(id)block
105 | error:(NSError **)error {
106 | return aspect_add(self, selector, options, block, error);
107 | }
108 |
109 | ///////////////////////////////////////////////////////////////////////////////////////////
110 | #pragma mark - Private Helper
111 |
112 | static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
113 | NSCParameterAssert(self);
114 | NSCParameterAssert(selector);
115 | NSCParameterAssert(block);
116 |
117 | __block TOCAssetCatalogBackground_AspectIdentifier *identifier = nil;
118 | aspect_performLocked(^{
119 | if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
120 | TOCAssetCatalogBackground_AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
121 | identifier = [TOCAssetCatalogBackground_AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
122 | if (identifier) {
123 | [aspectContainer addAspect:identifier withOptions:options];
124 |
125 | // Modify the class to allow message interception.
126 | aspect_prepareClassAndHookSelector(self, selector, error);
127 | }
128 | }
129 | });
130 | return identifier;
131 | }
132 |
133 | static BOOL aspect_remove(TOCAssetCatalogBackground_AspectIdentifier *aspect, NSError **error) {
134 | NSCAssert([aspect isKindOfClass:TOCAssetCatalogBackground_AspectIdentifier.class], @"Must have correct type.");
135 |
136 | __block BOOL success = NO;
137 | aspect_performLocked(^{
138 | id self = aspect.object; // strongify
139 | if (self) {
140 | TOCAssetCatalogBackground_AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector);
141 | success = [aspectContainer removeAspect:aspect];
142 |
143 | aspect_cleanupHookedClassAndSelector(self, aspect.selector);
144 | // destroy token
145 | aspect.object = nil;
146 | aspect.block = nil;
147 | aspect.selector = NULL;
148 | }else {
149 | NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", aspect];
150 | AspectError(AspectErrorRemoveObjectAlreadyDeallocated, errrorDesc);
151 | }
152 | });
153 | return success;
154 | }
155 |
156 | static void aspect_performLocked(dispatch_block_t block) {
157 | static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
158 | OSSpinLockLock(&aspect_lock);
159 | block();
160 | OSSpinLockUnlock(&aspect_lock);
161 | }
162 |
163 | static SEL aspect_aliasForSelector(SEL selector) {
164 | NSCParameterAssert(selector);
165 | return NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]);
166 | }
167 |
168 | static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
169 | TOCAssetCatalogBackground_AspectBlockRef layout = (__bridge void *)block;
170 | if (!(layout->flags & AspectBlockFlagsHasSignature)) {
171 | NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
172 | AspectError(AspectErrorMissingBlockSignature, description);
173 | return nil;
174 | }
175 | void *desc = layout->descriptor;
176 | desc += 2 * sizeof(unsigned long int);
177 | if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
178 | desc += 2 * sizeof(void *);
179 | }
180 | if (!desc) {
181 | NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
182 | AspectError(AspectErrorMissingBlockSignature, description);
183 | return nil;
184 | }
185 | const char *signature = (*(const char **)desc);
186 | return [NSMethodSignature signatureWithObjCTypes:signature];
187 | }
188 |
189 | static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
190 | NSCParameterAssert(blockSignature);
191 | NSCParameterAssert(object);
192 | NSCParameterAssert(selector);
193 |
194 | BOOL signaturesMatch = YES;
195 | NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
196 | if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
197 | signaturesMatch = NO;
198 | }else {
199 | if (blockSignature.numberOfArguments > 1) {
200 | const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
201 | if (blockType[0] != '@') {
202 | signaturesMatch = NO;
203 | }
204 | }
205 | // Argument 0 is self/block, argument 1 is SEL or id. We start comparing at argument 2.
206 | // The block can have less arguments than the method, that's ok.
207 | if (signaturesMatch) {
208 | for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) {
209 | const char *methodType = [methodSignature getArgumentTypeAtIndex:idx];
210 | const char *blockType = [blockSignature getArgumentTypeAtIndex:idx];
211 | // Only compare parameter, not the optional type data.
212 | if (!methodType || !blockType || methodType[0] != blockType[0]) {
213 | signaturesMatch = NO; break;
214 | }
215 | }
216 | }
217 | }
218 |
219 | if (!signaturesMatch) {
220 | NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
221 | AspectError(AspectErrorIncompatibleBlockSignature, description);
222 | return NO;
223 | }
224 | return YES;
225 | }
226 |
227 | ///////////////////////////////////////////////////////////////////////////////////////////
228 | #pragma mark - Class + Selector Preparation
229 |
230 | static BOOL aspect_isMsgForwardIMP(IMP impl) {
231 | return impl == _objc_msgForward
232 | #if !defined(__arm64__)
233 | || impl == (IMP)_objc_msgForward_stret
234 | #endif
235 | ;
236 | }
237 |
238 | static IMP aspect_getMsgForwardIMP(NSObject *self, SEL selector) {
239 | IMP msgForwardIMP = _objc_msgForward;
240 | #if !defined(__arm64__)
241 | // 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.
242 | // https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html
243 | // https://github.com/ReactiveCocoa/ReactiveCocoa/issues/783
244 | // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf (Section 5.4)
245 | Method method = class_getInstanceMethod(self.class, selector);
246 | const char *encoding = method_getTypeEncoding(method);
247 | BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B;
248 | if (methodReturnsStructValue) {
249 | @try {
250 | NSUInteger valueSize = 0;
251 | NSGetSizeAndAlignment(encoding, &valueSize, NULL);
252 |
253 | if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) {
254 | methodReturnsStructValue = NO;
255 | }
256 | } @catch (__unused NSException *e) {}
257 | }
258 | if (methodReturnsStructValue) {
259 | msgForwardIMP = (IMP)_objc_msgForward_stret;
260 | }
261 | #endif
262 | return msgForwardIMP;
263 | }
264 |
265 | static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
266 | NSCParameterAssert(selector);
267 | Class klass = aspect_hookClass(self, error);
268 | Method targetMethod = class_getInstanceMethod(klass, selector);
269 | IMP targetMethodIMP = method_getImplementation(targetMethod);
270 | if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
271 | // Make a method alias for the existing method implementation, it not already copied.
272 | const char *typeEncoding = method_getTypeEncoding(targetMethod);
273 | SEL aliasSelector = aspect_aliasForSelector(selector);
274 | if (![klass instancesRespondToSelector:aliasSelector]) {
275 | __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
276 | NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
277 | }
278 |
279 | // We use forwardInvocation to hook in.
280 | class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
281 | AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
282 | }
283 | }
284 |
285 | // Will undo the runtime changes made.
286 | static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) {
287 | NSCParameterAssert(self);
288 | NSCParameterAssert(selector);
289 |
290 | Class klass = object_getClass(self);
291 | BOOL isMetaClass = class_isMetaClass(klass);
292 | if (isMetaClass) {
293 | klass = (Class)self;
294 | }
295 |
296 | // Check if the method is marked as forwarded and undo that.
297 | Method targetMethod = class_getInstanceMethod(klass, selector);
298 | IMP targetMethodIMP = method_getImplementation(targetMethod);
299 | if (aspect_isMsgForwardIMP(targetMethodIMP)) {
300 | // Restore the original method implementation.
301 | const char *typeEncoding = method_getTypeEncoding(targetMethod);
302 | SEL aliasSelector = aspect_aliasForSelector(selector);
303 | Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
304 | IMP originalIMP = method_getImplementation(originalMethod);
305 | NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
306 |
307 | class_replaceMethod(klass, selector, originalIMP, typeEncoding);
308 | AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
309 | }
310 |
311 | // Deregister global tracked selector
312 | aspect_deregisterTrackedSelector(self, selector);
313 |
314 | // Get the aspect container and check if there are any hooks remaining. Clean up if there are not.
315 | TOCAssetCatalogBackground_AspectsContainer *container = aspect_getContainerForObject(self, selector);
316 | if (!container.hasAspects) {
317 | // Destroy the container
318 | aspect_destroyContainerForObject(self, selector);
319 |
320 | // Figure out how the class was modified to undo the changes.
321 | NSString *className = NSStringFromClass(klass);
322 | if ([className hasSuffix:AspectsSubclassSuffix]) {
323 | Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]);
324 | NSCAssert(originalClass != nil, @"Original class must exist");
325 | object_setClass(self, originalClass);
326 | AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass));
327 |
328 | // We can only dispose the class pair if we can ensure that no instances exist using our subclass.
329 | // Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around.
330 | //objc_disposeClassPair(object.class);
331 | }else {
332 | // Class is most likely swizzled in place. Undo that.
333 | if (isMetaClass) {
334 | aspect_undoSwizzleClassInPlace((Class)self);
335 | }else if (self.class != klass) {
336 | aspect_undoSwizzleClassInPlace(klass);
337 | }
338 | }
339 | }
340 | }
341 |
342 | ///////////////////////////////////////////////////////////////////////////////////////////
343 | #pragma mark - Hook Class
344 |
345 | static Class aspect_hookClass(NSObject *self, NSError **error) {
346 | NSCParameterAssert(self);
347 | Class statedClass = self.class;
348 | Class baseClass = object_getClass(self);
349 | NSString *className = NSStringFromClass(baseClass);
350 |
351 | // Already subclassed
352 | if ([className hasSuffix:AspectsSubclassSuffix]) {
353 | return baseClass;
354 |
355 | // We swizzle a class object, not a single object.
356 | }else if (class_isMetaClass(baseClass)) {
357 | return aspect_swizzleClassInPlace((Class)self);
358 | // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
359 | }else if (statedClass != baseClass) {
360 | return aspect_swizzleClassInPlace(baseClass);
361 | }
362 |
363 | // Default case. Create dynamic subclass.
364 | const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
365 | Class subclass = objc_getClass(subclassName);
366 |
367 | if (subclass == nil) {
368 | subclass = objc_allocateClassPair(baseClass, subclassName, 0);
369 | if (subclass == nil) {
370 | NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
371 | AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
372 | return nil;
373 | }
374 |
375 | aspect_swizzleForwardInvocation(subclass);
376 | aspect_hookedGetClass(subclass, statedClass);
377 | aspect_hookedGetClass(object_getClass(subclass), statedClass);
378 | objc_registerClassPair(subclass);
379 | }
380 |
381 | object_setClass(self, subclass);
382 | return subclass;
383 | }
384 |
385 | static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
386 | static void aspect_swizzleForwardInvocation(Class klass) {
387 | NSCParameterAssert(klass);
388 | // If there is no method, replace will act like class_addMethod.
389 | IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
390 | if (originalImplementation) {
391 | class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
392 | }
393 | AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
394 | }
395 |
396 | static void aspect_undoSwizzleForwardInvocation(Class klass) {
397 | NSCParameterAssert(klass);
398 | Method originalMethod = class_getInstanceMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName));
399 | Method objectMethod = class_getInstanceMethod(NSObject.class, @selector(forwardInvocation:));
400 | // There is no class_removeMethod, so the best we can do is to retore the original implementation, or use a dummy.
401 | IMP originalImplementation = method_getImplementation(originalMethod ?: objectMethod);
402 | class_replaceMethod(klass, @selector(forwardInvocation:), originalImplementation, "v@:@");
403 |
404 | AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(klass));
405 | }
406 |
407 | static void aspect_hookedGetClass(Class class, Class statedClass) {
408 | NSCParameterAssert(class);
409 | NSCParameterAssert(statedClass);
410 | Method method = class_getInstanceMethod(class, @selector(class));
411 | IMP newIMP = imp_implementationWithBlock(^(id self) {
412 | return statedClass;
413 | });
414 | class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
415 | }
416 |
417 | ///////////////////////////////////////////////////////////////////////////////////////////
418 | #pragma mark - Swizzle Class In Place
419 |
420 | static void _aspect_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) {
421 | static NSMutableSet *swizzledClasses;
422 | static dispatch_once_t pred;
423 | dispatch_once(&pred, ^{
424 | swizzledClasses = [NSMutableSet new];
425 | });
426 | @synchronized(swizzledClasses) {
427 | block(swizzledClasses);
428 | }
429 | }
430 |
431 | static Class aspect_swizzleClassInPlace(Class klass) {
432 | NSCParameterAssert(klass);
433 | NSString *className = NSStringFromClass(klass);
434 |
435 | _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
436 | if (![swizzledClasses containsObject:className]) {
437 | aspect_swizzleForwardInvocation(klass);
438 | [swizzledClasses addObject:className];
439 | }
440 | });
441 | return klass;
442 | }
443 |
444 | static void aspect_undoSwizzleClassInPlace(Class klass) {
445 | NSCParameterAssert(klass);
446 | NSString *className = NSStringFromClass(klass);
447 |
448 | _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
449 | if ([swizzledClasses containsObject:className]) {
450 | aspect_undoSwizzleForwardInvocation(klass);
451 | [swizzledClasses removeObject:className];
452 | }
453 | });
454 | }
455 |
456 | ///////////////////////////////////////////////////////////////////////////////////////////
457 | #pragma mark - Aspect Invoke Point
458 |
459 | // This is a macro so we get a cleaner stack trace.
460 | #define aspect_invoke(aspects, info) \
461 | for (TOCAssetCatalogBackground_AspectIdentifier *aspect in aspects) {\
462 | [aspect invokeWithInfo:info];\
463 | if (aspect.options & AspectOptionAutomaticRemoval) { \
464 | aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
465 | } \
466 | }
467 |
468 | // This is the swizzled forwardInvocation: method.
469 | static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
470 | NSCParameterAssert(self);
471 | NSCParameterAssert(invocation);
472 | SEL originalSelector = invocation.selector;
473 | SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
474 | invocation.selector = aliasSelector;
475 | TOCAssetCatalogBackground_AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
476 | TOCAssetCatalogBackground_AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
477 | TOCAssetCatalogBackground_AspectInfo *info = [[TOCAssetCatalogBackground_AspectInfo alloc] initWithInstance:self invocation:invocation];
478 | NSArray *aspectsToRemove = nil;
479 |
480 | // Before hooks.
481 | aspect_invoke(classContainer.beforeAspects, info);
482 | aspect_invoke(objectContainer.beforeAspects, info);
483 |
484 | // Instead hooks.
485 | BOOL respondsToAlias = YES;
486 | if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
487 | aspect_invoke(classContainer.insteadAspects, info);
488 | aspect_invoke(objectContainer.insteadAspects, info);
489 | }else {
490 | Class klass = object_getClass(invocation.target);
491 | do {
492 | if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
493 | [invocation invoke];
494 | break;
495 | }
496 | }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
497 | }
498 |
499 | // After hooks.
500 | aspect_invoke(classContainer.afterAspects, info);
501 | aspect_invoke(objectContainer.afterAspects, info);
502 |
503 | // If no hooks are installed, call original implementation (usually to throw an exception)
504 | if (!respondsToAlias) {
505 | invocation.selector = originalSelector;
506 | SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
507 | if ([self respondsToSelector:originalForwardInvocationSEL]) {
508 | ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
509 | }else {
510 | [self doesNotRecognizeSelector:invocation.selector];
511 | }
512 | }
513 |
514 | // Remove any hooks that are queued for deregistration.
515 | [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
516 | }
517 | #undef aspect_invoke
518 |
519 | ///////////////////////////////////////////////////////////////////////////////////////////
520 | #pragma mark - Aspect Container Management
521 |
522 | // Loads or creates the aspect container.
523 | static TOCAssetCatalogBackground_AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
524 | NSCParameterAssert(self);
525 | SEL aliasSelector = aspect_aliasForSelector(selector);
526 | TOCAssetCatalogBackground_AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
527 | if (!aspectContainer) {
528 | aspectContainer = [TOCAssetCatalogBackground_AspectsContainer new];
529 | objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
530 | }
531 | return aspectContainer;
532 | }
533 |
534 | static TOCAssetCatalogBackground_AspectsContainer *aspect_getContainerForClass(Class klass, SEL selector) {
535 | NSCParameterAssert(klass);
536 | TOCAssetCatalogBackground_AspectsContainer *classContainer = nil;
537 | do {
538 | classContainer = objc_getAssociatedObject(klass, selector);
539 | if (classContainer.hasAspects) break;
540 | }while ((klass = class_getSuperclass(klass)));
541 |
542 | return classContainer;
543 | }
544 |
545 | static void aspect_destroyContainerForObject(id self, SEL selector) {
546 | NSCParameterAssert(self);
547 | SEL aliasSelector = aspect_aliasForSelector(selector);
548 | objc_setAssociatedObject(self, aliasSelector, nil, OBJC_ASSOCIATION_RETAIN);
549 | }
550 |
551 | ///////////////////////////////////////////////////////////////////////////////////////////
552 | #pragma mark - Selector Blacklist Checking
553 |
554 | static NSMutableDictionary *aspect_getSwizzledClassesDict() {
555 | static NSMutableDictionary *swizzledClassesDict;
556 | static dispatch_once_t pred;
557 | dispatch_once(&pred, ^{
558 | swizzledClassesDict = [NSMutableDictionary new];
559 | });
560 | return swizzledClassesDict;
561 | }
562 |
563 | static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) {
564 | static NSSet *disallowedSelectorList;
565 | static dispatch_once_t pred;
566 | dispatch_once(&pred, ^{
567 | disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
568 | });
569 |
570 | // Check against the blacklist.
571 | NSString *selectorName = NSStringFromSelector(selector);
572 | if ([disallowedSelectorList containsObject:selectorName]) {
573 | NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
574 | AspectError(AspectErrorSelectorBlacklisted, errorDescription);
575 | return NO;
576 | }
577 |
578 | // Additional checks.
579 | AspectOptions position = options&AspectPositionFilter;
580 | if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
581 | NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
582 | AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
583 | return NO;
584 | }
585 |
586 | if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
587 | NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];
588 | AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
589 | return NO;
590 | }
591 |
592 | // Search for the current class and the class hierarchy IF we are modifying a class object
593 | if (class_isMetaClass(object_getClass(self))) {
594 | Class klass = [self class];
595 | NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
596 | Class currentClass = [self class];
597 | do {
598 | TOCAssetCatalogBackground_AspectTracker *tracker = swizzledClassesDict[currentClass];
599 | if ([tracker.selectorNames containsObject:selectorName]) {
600 |
601 | // Find the topmost class for the log.
602 | if (tracker.parentEntry) {
603 | TOCAssetCatalogBackground_AspectTracker *topmostEntry = tracker.parentEntry;
604 | while (topmostEntry.parentEntry) {
605 | topmostEntry = topmostEntry.parentEntry;
606 | }
607 | NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(topmostEntry.trackedClass)];
608 | AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
609 | return NO;
610 | }else if (klass == currentClass) {
611 | // Already modified and topmost!
612 | return YES;
613 | }
614 | }
615 | }while ((currentClass = class_getSuperclass(currentClass)));
616 |
617 | // Add the selector as being modified.
618 | currentClass = klass;
619 | TOCAssetCatalogBackground_AspectTracker *parentTracker = nil;
620 | do {
621 | TOCAssetCatalogBackground_AspectTracker *tracker = swizzledClassesDict[currentClass];
622 | if (!tracker) {
623 | tracker = [[TOCAssetCatalogBackground_AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];
624 | swizzledClassesDict[(id)currentClass] = tracker;
625 | }
626 | [tracker.selectorNames addObject:selectorName];
627 | // All superclasses get marked as having a subclass that is modified.
628 | parentTracker = tracker;
629 | }while ((currentClass = class_getSuperclass(currentClass)));
630 | }
631 |
632 | return YES;
633 | }
634 |
635 | static void aspect_deregisterTrackedSelector(id self, SEL selector) {
636 | if (!class_isMetaClass(object_getClass(self))) return;
637 |
638 | NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
639 | NSString *selectorName = NSStringFromSelector(selector);
640 | Class currentClass = [self class];
641 | do {
642 | TOCAssetCatalogBackground_AspectTracker *tracker = swizzledClassesDict[currentClass];
643 | if (tracker) {
644 | [tracker.selectorNames removeObject:selectorName];
645 | if (tracker.selectorNames.count == 0) {
646 | [swizzledClassesDict removeObjectForKey:tracker];
647 | }
648 | }
649 | }while ((currentClass = class_getSuperclass(currentClass)));
650 | }
651 |
652 | @end
653 |
654 | @implementation TOCAssetCatalogBackground_AspectTracker
655 |
656 | - (id)initWithTrackedClass:(Class)trackedClass parent:(TOCAssetCatalogBackground_AspectTracker *)parent {
657 | if (self = [super init]) {
658 | _trackedClass = trackedClass;
659 | _parentEntry = parent;
660 | _selectorNames = [NSMutableSet new];
661 | }
662 | return self;
663 | }
664 | - (NSString *)description {
665 | return [NSString stringWithFormat:@"<%@: %@, trackedClass: %@, selectorNames:%@, parent:%p>", self.class, self, NSStringFromClass(self.trackedClass), self.selectorNames, self.parentEntry];
666 | }
667 |
668 | @end
669 |
670 | ///////////////////////////////////////////////////////////////////////////////////////////
671 | #pragma mark - NSInvocation (Aspects)
672 |
673 | @implementation NSInvocation (Aspects)
674 |
675 | // Thanks to the ReactiveCocoa team for providing a generic solution for this.
676 | - (id)tocassetcatalogbackground_aspect_argumentAtIndex:(NSUInteger)index {
677 | const char *argType = [self.methodSignature getArgumentTypeAtIndex:index];
678 | // Skip const type qualifier.
679 | if (argType[0] == _C_CONST) argType++;
680 |
681 | #define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0)
682 | if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) {
683 | __autoreleasing id returnObj;
684 | [self getArgument:&returnObj atIndex:(NSInteger)index];
685 | return returnObj;
686 | } else if (strcmp(argType, @encode(SEL)) == 0) {
687 | SEL selector = 0;
688 | [self getArgument:&selector atIndex:(NSInteger)index];
689 | return NSStringFromSelector(selector);
690 | } else if (strcmp(argType, @encode(Class)) == 0) {
691 | __autoreleasing Class theClass = Nil;
692 | [self getArgument:&theClass atIndex:(NSInteger)index];
693 | return theClass;
694 | // Using this list will box the number with the appropriate constructor, instead of the generic NSValue.
695 | } else if (strcmp(argType, @encode(char)) == 0) {
696 | WRAP_AND_RETURN(char);
697 | } else if (strcmp(argType, @encode(int)) == 0) {
698 | WRAP_AND_RETURN(int);
699 | } else if (strcmp(argType, @encode(short)) == 0) {
700 | WRAP_AND_RETURN(short);
701 | } else if (strcmp(argType, @encode(long)) == 0) {
702 | WRAP_AND_RETURN(long);
703 | } else if (strcmp(argType, @encode(long long)) == 0) {
704 | WRAP_AND_RETURN(long long);
705 | } else if (strcmp(argType, @encode(unsigned char)) == 0) {
706 | WRAP_AND_RETURN(unsigned char);
707 | } else if (strcmp(argType, @encode(unsigned int)) == 0) {
708 | WRAP_AND_RETURN(unsigned int);
709 | } else if (strcmp(argType, @encode(unsigned short)) == 0) {
710 | WRAP_AND_RETURN(unsigned short);
711 | } else if (strcmp(argType, @encode(unsigned long)) == 0) {
712 | WRAP_AND_RETURN(unsigned long);
713 | } else if (strcmp(argType, @encode(unsigned long long)) == 0) {
714 | WRAP_AND_RETURN(unsigned long long);
715 | } else if (strcmp(argType, @encode(float)) == 0) {
716 | WRAP_AND_RETURN(float);
717 | } else if (strcmp(argType, @encode(double)) == 0) {
718 | WRAP_AND_RETURN(double);
719 | } else if (strcmp(argType, @encode(BOOL)) == 0) {
720 | WRAP_AND_RETURN(BOOL);
721 | } else if (strcmp(argType, @encode(bool)) == 0) {
722 | WRAP_AND_RETURN(BOOL);
723 | } else if (strcmp(argType, @encode(char *)) == 0) {
724 | WRAP_AND_RETURN(const char *);
725 | } else if (strcmp(argType, @encode(void (^)(void))) == 0) {
726 | __unsafe_unretained id block = nil;
727 | [self getArgument:&block atIndex:(NSInteger)index];
728 | return [block copy];
729 | } else {
730 | NSUInteger valueSize = 0;
731 | NSGetSizeAndAlignment(argType, &valueSize, NULL);
732 |
733 | unsigned char valueBytes[valueSize];
734 | [self getArgument:valueBytes atIndex:(NSInteger)index];
735 |
736 | return [NSValue valueWithBytes:valueBytes objCType:argType];
737 | }
738 | return nil;
739 | #undef WRAP_AND_RETURN
740 | }
741 |
742 | - (NSArray *)tocassetcatalogbackground_aspects_arguments {
743 | NSMutableArray *argumentsArray = [NSMutableArray array];
744 | for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) {
745 | [argumentsArray addObject:[self tocassetcatalogbackground_aspect_argumentAtIndex:idx] ?: NSNull.null];
746 | }
747 | return [argumentsArray copy];
748 | }
749 |
750 | @end
751 |
752 | ///////////////////////////////////////////////////////////////////////////////////////////
753 | #pragma mark - TOCAssetCatalogBackground_AspectIdentifier
754 |
755 | @implementation TOCAssetCatalogBackground_AspectIdentifier
756 |
757 | + (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {
758 | NSCParameterAssert(block);
759 | NSCParameterAssert(selector);
760 | NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
761 | if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
762 | return nil;
763 | }
764 |
765 | TOCAssetCatalogBackground_AspectIdentifier *identifier = nil;
766 | if (blockSignature) {
767 | identifier = [TOCAssetCatalogBackground_AspectIdentifier new];
768 | identifier.selector = selector;
769 | identifier.block = block;
770 | identifier.blockSignature = blockSignature;
771 | identifier.options = options;
772 | identifier.object = object; // weak
773 | }
774 | return identifier;
775 | }
776 |
777 | - (BOOL)invokeWithInfo:(id)info {
778 | NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
779 | NSInvocation *originalInvocation = info.originalInvocation;
780 | NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
781 |
782 | // Be extra paranoid. We already check that on hook registration.
783 | if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
784 | AspectLogError(@"Block has too many arguments. Not calling %@", info);
785 | return NO;
786 | }
787 |
788 | // The `self` of the block will be the TOCAssetCatalogBackground_AspectInfo. Optional.
789 | if (numberOfArguments > 1) {
790 | [blockInvocation setArgument:&info atIndex:1];
791 | }
792 |
793 | void *argBuf = NULL;
794 | for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
795 | const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
796 | NSUInteger argSize;
797 | NSGetSizeAndAlignment(type, &argSize, NULL);
798 |
799 | if (!(argBuf = reallocf(argBuf, argSize))) {
800 | AspectLogError(@"Failed to allocate memory for block invocation.");
801 | return NO;
802 | }
803 |
804 | [originalInvocation getArgument:argBuf atIndex:idx];
805 | [blockInvocation setArgument:argBuf atIndex:idx];
806 | }
807 |
808 | [blockInvocation invokeWithTarget:self.block];
809 |
810 | if (argBuf != NULL) {
811 | free(argBuf);
812 | }
813 | return YES;
814 | }
815 |
816 | - (NSString *)description {
817 | 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];
818 | }
819 |
820 | - (BOOL)remove {
821 | return aspect_remove(self, NULL);
822 | }
823 |
824 | @end
825 |
826 | ///////////////////////////////////////////////////////////////////////////////////////////
827 | #pragma mark - TOCAssetCatalogBackground_AspectsContainer
828 |
829 | @implementation TOCAssetCatalogBackground_AspectsContainer
830 |
831 | - (BOOL)hasAspects {
832 | return self.beforeAspects.count > 0 || self.insteadAspects.count > 0 || self.afterAspects.count > 0;
833 | }
834 |
835 | - (void)addAspect:(TOCAssetCatalogBackground_AspectIdentifier *)aspect withOptions:(AspectOptions)options {
836 | NSParameterAssert(aspect);
837 | NSUInteger position = options&AspectPositionFilter;
838 | switch (position) {
839 | case AspectPositionBefore: self.beforeAspects = [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break;
840 | case AspectPositionInstead: self.insteadAspects = [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break;
841 | case AspectPositionAfter: self.afterAspects = [(self.afterAspects ?:@[]) arrayByAddingObject:aspect]; break;
842 | }
843 | }
844 |
845 | - (BOOL)removeAspect:(id)aspect {
846 | for (NSString *aspectArrayName in @[NSStringFromSelector(@selector(beforeAspects)),
847 | NSStringFromSelector(@selector(insteadAspects)),
848 | NSStringFromSelector(@selector(afterAspects))]) {
849 | NSArray *array = [self valueForKey:aspectArrayName];
850 | NSUInteger index = [array indexOfObjectIdenticalTo:aspect];
851 | if (array && index != NSNotFound) {
852 | NSMutableArray *newArray = [NSMutableArray arrayWithArray:array];
853 | [newArray removeObjectAtIndex:index];
854 | [self setValue:newArray forKey:aspectArrayName];
855 | return YES;
856 | }
857 | }
858 | return NO;
859 | }
860 |
861 | - (NSString *)description {
862 | return [NSString stringWithFormat:@"<%@: %p, before:%@, instead:%@, after:%@>", self.class, self, self.beforeAspects, self.insteadAspects, self.afterAspects];
863 | }
864 |
865 | @end
866 |
867 | ///////////////////////////////////////////////////////////////////////////////////////////
868 | #pragma mark - TOCAssetCatalogBackground_AspectInfo
869 |
870 | @implementation TOCAssetCatalogBackground_AspectInfo
871 |
872 | @synthesize arguments = _arguments;
873 |
874 | - (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation {
875 | NSCParameterAssert(instance);
876 | NSCParameterAssert(invocation);
877 | if (self = [super init]) {
878 | _instance = instance;
879 | _originalInvocation = invocation;
880 | }
881 | return self;
882 | }
883 |
884 | - (NSArray *)arguments {
885 | // Lazily evaluate arguments, boxing is expensive.
886 | if (!_arguments) {
887 | _arguments = self.originalInvocation.tocassetcatalogbackground_aspects_arguments;
888 | }
889 | return _arguments;
890 | }
891 |
892 | @end
893 |
--------------------------------------------------------------------------------