├── FixCode.jpg ├── FixIssueButton.jpg ├── FixIssueButton2.jpg ├── FixCode ├── NSObject_Extension.h ├── FixCode.h ├── NSObject_Extension.m ├── Info.plist └── FixCode.m ├── .gitignore ├── LICENSE ├── FixCode.xcodeproj ├── xcshareddata │ └── xcschemes │ │ └── FixCode.xcscheme └── project.pbxproj ├── README.md └── vendor ├── Aspects.h └── Aspects.m /FixCode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FixIssue/FixCode/HEAD/FixCode.jpg -------------------------------------------------------------------------------- /FixIssueButton.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FixIssue/FixCode/HEAD/FixIssueButton.jpg -------------------------------------------------------------------------------- /FixIssueButton2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FixIssue/FixCode/HEAD/FixIssueButton2.jpg -------------------------------------------------------------------------------- /FixCode/NSObject_Extension.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject_Extension.h 3 | // FixCode 4 | // 5 | // Created by Boris Bügling on 31/10/15. 6 | // Copyright (c) 2015 Fastlane. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSObject (Xcode_Plugin_Template_Extension) 12 | 13 | + (void)pluginDidLoad:(NSBundle *)plugin; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /FixCode/FixCode.h: -------------------------------------------------------------------------------- 1 | // 2 | // FixCode.h 3 | // FixCode 4 | // 5 | // Created by Boris Bügling on 31/10/15. 6 | // Copyright (c) 2015 Fastlane. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FixCode; 12 | 13 | static FixCode *sharedPlugin; 14 | 15 | @interface FixCode : NSObject 16 | 17 | + (instancetype)sharedPlugin; 18 | - (id)initWithBundle:(NSBundle *)plugin; 19 | 20 | @property (nonatomic, strong, readonly) NSBundle* bundle; 21 | 22 | @end -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | Makefile.local 3 | *.swp 4 | *tags 5 | 6 | # OS X 7 | #################### 8 | .Spotlight-V100/ 9 | .Trashes/ 10 | ._* 11 | .AppleDouble 12 | .DS_Store 13 | .LSOverride 14 | Icon? 15 | 16 | # Xcode 17 | #################### 18 | *.mode1v3 19 | *.mode2v3 20 | *.pbxuser 21 | *.perspectivev3 22 | *.user 23 | *.xcuserstate 24 | *.moved-aside 25 | *~.nib 26 | .idea/ 27 | DerivedData/ 28 | project.xcworkspace/ 29 | xcuserdata/ 30 | profile 31 | !default.pbxuser 32 | !default.mode1v3 33 | !default.mode2v3 34 | !default.perspectivev3 35 | 36 | # CocoaPods 37 | #################### 38 | Pods 39 | *.xcworkspace 40 | 41 | -------------------------------------------------------------------------------- /FixCode/NSObject_Extension.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject_Extension.m 3 | // FixCode 4 | // 5 | // Created by Boris Bügling on 31/10/15. 6 | // Copyright (c) 2015 Fastlane. All rights reserved. 7 | // 8 | 9 | 10 | #import "NSObject_Extension.h" 11 | #import "FixCode.h" 12 | 13 | @implementation NSObject (Xcode_Plugin_Template_Extension) 14 | 15 | + (void)pluginDidLoad:(NSBundle *)plugin 16 | { 17 | static dispatch_once_t onceToken; 18 | NSString *currentApplicationName = [[NSBundle mainBundle] infoDictionary][@"CFBundleName"]; 19 | if ([currentApplicationName isEqual:@"Xcode"]) { 20 | dispatch_once(&onceToken, ^{ 21 | sharedPlugin = [[FixCode alloc] initWithBundle:plugin]; 22 | }); 23 | } 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 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 | 23 | -------------------------------------------------------------------------------- /FixCode/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 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 | C4A681B0-4A26-480E-93EC-1218098B9AA0 28 | AD68E85B-441B-4301-B564-A45E4919A6AD 29 | A16FF353-8441-459E-A50C-B071F53F51B7 30 | 9F75337B-21B4-4ADC-B558-F9CADF7073A7 31 | E969541F-E6F9-4D25-8158-72DC3545A6C6 32 | 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90 33 | 7265231C-39B4-402C-89E1-16167C4CC990 34 | 9AFF134A-08DC-4096-8CEE-62A4BB123046 35 | F41BD31E-2683-44B8-AE7F-5F09E919790E 36 | E71C2CFE-BFD8-4044-8F06-00AE685A406C 37 | ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C 38 | 0420B86A-AA43-4792-9ED0-6FE0F2B16A13 39 | 40 | LSMinimumSystemVersion 41 | $(MACOSX_DEPLOYMENT_TARGET) 42 | NSPrincipalClass 43 | FixCode 44 | XC4Compatible 45 | 46 | XCPluginHasUI 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /FixCode.xcodeproj/xcshareddata/xcschemes/FixCode.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 16 | 22 | 23 | 24 | 31 | 37 | 38 | 39 | 40 | 41 | 46 | 47 | 48 | 49 | 50 | 51 | 63 | 66 | 67 | 68 | 74 | 75 | 76 | 77 | 78 | 79 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 2 | 3 | **Xcode 8 does not load unsigned code anymore** - see , therefore this project is deprecated and only available for historical purposes. 4 | 5 |

6 | FixCode Logo 7 |

8 | 9 |

10 | FixCode 11 |

12 | 13 |

14 | Fixing the Fix Issue button 15 |

16 | 17 | This Xcode plugin disables the `Fix Issue` functionality in Xcode. This way, none of your team members can click this button by mistake and might end up revoking existing certificates and provisioning profiles. 18 | 19 | 20 | | | Fix Issue 21 | ----------------------|---------------------------------- 22 | :lock: | Avoid breaking existing profiles when clicking the `Fix Issue` button 23 | :rocket: | Saves you **hours** of dealing with code signing 24 | :sparkles: | Super easy installation 25 | :computer: | Support for iOS, OS X, watchOS and tvOS projects 26 | 27 | Fix Issue 28 | 29 | # Installation 30 | 31 | ### Using fastlane 32 | 33 | Add the `install_xcode_plugin` to your `Fastfile`. This way, the plugin gets installed for the whole team, so that no one can revoke your certificate by mistake. 34 | 35 | ```ruby 36 | lane :xcode do 37 | install_xcode_plugin(github: 'https://github.com/FixIssue/FixCode') 38 | end 39 | ``` 40 | 41 | ``` 42 | fastlane xcode 43 | ``` 44 | 45 | ### Alcatraz 46 | You can install `FixCode` using [Alcatraz](http://alcatraz.io/). 47 | 48 | First, install [Alcatraz](http://alcatraz.io/) using 49 | 50 | ``` 51 | curl -fsSL https://raw.githubusercontent.com/supermarin/Alcatraz/deploy/Scripts/install.sh | sh 52 | ``` 53 | 54 | - Restart Xcode 55 | - Click on `Window` 56 | - Select `Package Manager` 57 | - Search and Install `FixCode` 58 | - Restart Xcode 59 | 60 | # What does this do? 61 | 62 | The primary goal of this plugin is to **disable** the original `Fix Issue` button, as it has side effects, like sometimes [revoking your certificates and with it all its provisioning profiles](https://raw.githubusercontent.com/fastlane/sigh/master/assets/SignErrors.png). 63 | 64 | This is especially a big problem when working in a bigger team: Someone clicks the `Fix Issue` button and revokes the other profiles. This includes Enterprise profiles, resulting in breaking the app on all devices it's installed on. 65 | 66 | This Xcode plugin will deactivate the button and replace it with emojis. The button will open the [official code signing guide](https://github.com/fastlane/fastlane/blob/master/docs/CodeSigning.md). 67 | 68 | You might ask yourself, why the button doesn't just run [sigh](https://github.com/fastlane/sigh) or [cert](https://github.com/fastlane/cert): Check out the [blog post about developer tools by Felix Krause](https://krausefx.com/blog/ios-tools) for more information about how developer tools should be transparent and show you what they do. 69 | 70 | Fix Issue 71 | 72 | # Thanks 73 | 74 | - The code was implemented by [@neonacho](https://twitter.com/neonacho) 75 | - [@KrauseFx](https://twitter.com/KrauseFx) was also there, providing drinks while [@neonacho](https://twitter.com/neonacho) did the actual work. 76 | 77 | # Need help? 78 | Please submit an issue on GitHub and provide information about your setup 79 | 80 | # License 81 | This project is licensed under the terms of the MIT license. See the LICENSE file. 82 | 83 | > This project are in no way affiliated with Apple Inc. This project is open source under the MIT license, which means you have full access to the source code and can modify it to fit your own needs. This tool run on your own computer or server, so your credentials or other sensitive information will never leave your own computer. You are responsible for how you use this tool. 84 | 85 | -------------------------------------------------------------------------------- /vendor/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 AspectToken 20 | 21 | /// Deregisters an aspect. 22 | /// @return YES if deregistration is successful, otherwise NO. 23 | - (BOOL)remove; 24 | 25 | @end 26 | 27 | /// The AspectInfo protocol is the first parameter of our block syntax. 28 | @protocol 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)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)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 AspectErrorDomain; 84 | -------------------------------------------------------------------------------- /FixCode/FixCode.m: -------------------------------------------------------------------------------- 1 | // 2 | // FixCode.m 3 | // FixCode 4 | // 5 | // Created by Boris Bügling on 31/10/15. 6 | // Copyright (c) 2015 Fastlane. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "Aspects.h" 12 | #import "FixCode.h" 13 | 14 | @interface IDEEnhancedProvisioningSigningIdentity : NSObject 15 | 16 | @property NSUInteger state; // 0: current, 2: online-only 17 | 18 | @end 19 | 20 | @interface IDESigningIdentityActionCellViewContents : NSObject 21 | 22 | @property IDEEnhancedProvisioningSigningIdentity *signingIdentity; 23 | 24 | @end 25 | 26 | @interface NSObject (Shutup) 27 | 28 | -(void)_autoLayoutViewViewFrameDidChange:(id)arg0; 29 | 30 | @end 31 | 32 | #pragma mark - 33 | 34 | @interface NSView (Debug) 35 | 36 | -(NSString*)_subtreeDescription; 37 | 38 | @end 39 | 40 | #pragma mark - 41 | 42 | @interface FixCode() 43 | 44 | @property (nonatomic, strong, readwrite) NSBundle *bundle; 45 | @property (nonatomic, strong, readwrite) NSWindowController *currentCodeSigningWindowController; 46 | 47 | @end 48 | 49 | #pragma mark - 50 | 51 | @implementation FixCode 52 | 53 | + (instancetype)sharedPlugin 54 | { 55 | return sharedPlugin; 56 | } 57 | 58 | - (id)initWithBundle:(NSBundle *)plugin 59 | { 60 | if (self = [super init]) { 61 | self.bundle = plugin; 62 | [[NSNotificationCenter defaultCenter] addObserver:self 63 | selector:@selector(didApplicationFinishLaunchingNotification:) 64 | name:NSApplicationDidFinishLaunchingNotification 65 | object:nil]; 66 | } 67 | return self; 68 | } 69 | 70 | - (void)didApplicationFinishLaunchingNotification:(NSNotification*)noti 71 | { 72 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidFinishLaunchingNotification object:nil]; 73 | 74 | [self swizzleCodeSigningResolution]; 75 | } 76 | 77 | - (void)dealloc 78 | { 79 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 80 | } 81 | 82 | #pragma mark - 83 | 84 | - (void)doItRight { 85 | [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://codesigning.guide"]]; 86 | 87 | [self.currentCodeSigningWindowController.window close]; 88 | [self.currentCodeSigningWindowController close]; 89 | } 90 | 91 | - (void)findAndReplaceFixIssueButtonInView:(NSView*)view { 92 | NSButton* fixIssueButton = [self findFirstButtonRecursive:view]; 93 | fixIssueButton.title = @"Do it right ✨🚀✨"; 94 | 95 | fixIssueButton.action = @selector(doItRight); 96 | fixIssueButton.target = self; 97 | 98 | [fixIssueButton sizeToFit]; 99 | } 100 | 101 | - (NSButton*)findFirstButtonRecursive:(NSView*)view { 102 | if ([view isKindOfClass:NSButton.class]) { 103 | return (NSButton*)view; 104 | } 105 | 106 | for (NSView* subview in view.subviews) { 107 | NSButton* result = [self findFirstButtonRecursive:subview]; 108 | if (result) { 109 | return result; 110 | } 111 | } 112 | 113 | return nil; 114 | } 115 | 116 | - (NSTextField*)findLastTextFieldRecursive:(NSView*)view { 117 | NSTextField* textField = nil; 118 | 119 | for (NSView* subview in view.subviews) { 120 | if ([subview isKindOfClass:NSTextField.class]) { 121 | textField = (NSTextField*)subview; 122 | } 123 | 124 | NSTextField* result = [self findLastTextFieldRecursive:subview]; 125 | if (result) { 126 | return result; 127 | } 128 | } 129 | 130 | return textField; 131 | } 132 | 133 | - (void)swizzleCodeSigningResolution 134 | { 135 | NSError* error = nil; 136 | 137 | [objc_getClass("IDECodesignIssueResolutionWindowController") aspect_hookSelector:@selector(windowDidLoad) withOptions:AspectPositionAfter usingBlock:^(id info) { 138 | self.currentCodeSigningWindowController = info.instance; 139 | NSView* contentView = [[self.currentCodeSigningWindowController window] contentView]; 140 | 141 | [self findAndReplaceFixIssueButtonInView:contentView]; 142 | 143 | NSTextField* field = [self findLastTextFieldRecursive:contentView]; 144 | [field setStringValue:[field.stringValue componentsSeparatedByString:@"\n"][0]]; 145 | } error:&error]; 146 | 147 | if (error) { 148 | NSLog(@"Error: %@", error); 149 | } 150 | 151 | [objc_getClass("DVTStackView_ML") aspect_hookSelector:@selector(_autoLayoutViewViewFrameDidChange:) withOptions:AspectPositionAfter usingBlock:^(id info) { 152 | NSView* view = info.instance; 153 | 154 | if([[view.superview nextResponder] isKindOfClass:objc_getClass("Xcode3CodesignTroubleshootingViewController")]) { 155 | [self findAndReplaceFixIssueButtonInView:view.superview]; 156 | } 157 | } error:&error]; 158 | 159 | if (error) { 160 | NSLog(@"Error: %@", error); 161 | } 162 | 163 | [objc_getClass("IDEFlightCheckListView") aspect_hookSelector:@selector(_shouldShowResolveButton) withOptions:AspectPositionAfter usingBlock:^(id info) { 164 | BOOL result = NO; 165 | [info.originalInvocation getReturnValue:&result]; 166 | 167 | if (!result) { 168 | return; 169 | } 170 | 171 | NSView* view = info.instance; 172 | [self findAndReplaceFixIssueButtonInView:view]; 173 | } error:&error]; 174 | 175 | if (error) { 176 | NSLog(@"Error: %@", error); 177 | } 178 | 179 | [objc_getClass("IDESigningIdentityActionCellView") aspect_hookSelector:@selector(setObjectValue:) withOptions:AspectPositionAfter usingBlock:^(id info, IDESigningIdentityActionCellViewContents *cellContents) { 180 | if ([[cellContents signingIdentity] state] == 2) { 181 | [self findAndReplaceFixIssueButtonInView:info.instance]; 182 | } 183 | } error:&error]; 184 | 185 | if (error) { 186 | NSLog(@"Error: %@", error); 187 | } 188 | } 189 | 190 | @end 191 | -------------------------------------------------------------------------------- /FixCode.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A13D4D421BE59B72005ADEBA /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A13D4D411BE59B72005ADEBA /* AppKit.framework */; }; 11 | A13D4D441BE59B72005ADEBA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A13D4D431BE59B72005ADEBA /* Foundation.framework */; }; 12 | A13D4D491BE59B72005ADEBA /* FixCode.xcscheme in Resources */ = {isa = PBXBuildFile; fileRef = A13D4D481BE59B72005ADEBA /* FixCode.xcscheme */; }; 13 | A13D4D4C1BE59B72005ADEBA /* FixCode.m in Sources */ = {isa = PBXBuildFile; fileRef = A13D4D4B1BE59B72005ADEBA /* FixCode.m */; }; 14 | A13D4D4F1BE59B72005ADEBA /* NSObject_Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = A13D4D4E1BE59B72005ADEBA /* NSObject_Extension.m */; }; 15 | A13D4D571BE59C96005ADEBA /* Aspects.m in Sources */ = {isa = PBXBuildFile; fileRef = A13D4D561BE59C96005ADEBA /* Aspects.m */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | A13D4D3E1BE59B72005ADEBA /* FixCode.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FixCode.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | A13D4D411BE59B72005ADEBA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 21 | A13D4D431BE59B72005ADEBA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 22 | A13D4D471BE59B72005ADEBA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 23 | A13D4D481BE59B72005ADEBA /* FixCode.xcscheme */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = FixCode.xcscheme; path = FixCode.xcodeproj/xcshareddata/xcschemes/FixCode.xcscheme; sourceTree = SOURCE_ROOT; }; 24 | A13D4D4A1BE59B72005ADEBA /* FixCode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FixCode.h; sourceTree = ""; }; 25 | A13D4D4B1BE59B72005ADEBA /* FixCode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FixCode.m; sourceTree = ""; }; 26 | A13D4D4D1BE59B72005ADEBA /* NSObject_Extension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSObject_Extension.h; sourceTree = ""; }; 27 | A13D4D4E1BE59B72005ADEBA /* NSObject_Extension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSObject_Extension.m; sourceTree = ""; }; 28 | A13D4D551BE59C96005ADEBA /* Aspects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Aspects.h; path = vendor/Aspects.h; sourceTree = SOURCE_ROOT; }; 29 | A13D4D561BE59C96005ADEBA /* Aspects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Aspects.m; path = vendor/Aspects.m; sourceTree = SOURCE_ROOT; }; 30 | /* End PBXFileReference section */ 31 | 32 | /* Begin PBXFrameworksBuildPhase section */ 33 | A13D4D3C1BE59B72005ADEBA /* Frameworks */ = { 34 | isa = PBXFrameworksBuildPhase; 35 | buildActionMask = 2147483647; 36 | files = ( 37 | A13D4D421BE59B72005ADEBA /* AppKit.framework in Frameworks */, 38 | A13D4D441BE59B72005ADEBA /* Foundation.framework in Frameworks */, 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | A13D4D351BE59B72005ADEBA = { 46 | isa = PBXGroup; 47 | children = ( 48 | A13D4D451BE59B72005ADEBA /* Code */, 49 | A13D4D401BE59B72005ADEBA /* Frameworks */, 50 | A13D4D3F1BE59B72005ADEBA /* Products */, 51 | A13D4D461BE59B72005ADEBA /* Supporting Files */, 52 | ); 53 | sourceTree = ""; 54 | }; 55 | A13D4D3F1BE59B72005ADEBA /* Products */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | A13D4D3E1BE59B72005ADEBA /* FixCode.xcplugin */, 59 | ); 60 | name = Products; 61 | sourceTree = ""; 62 | }; 63 | A13D4D401BE59B72005ADEBA /* Frameworks */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | A13D4D411BE59B72005ADEBA /* AppKit.framework */, 67 | A13D4D431BE59B72005ADEBA /* Foundation.framework */, 68 | ); 69 | name = Frameworks; 70 | sourceTree = ""; 71 | }; 72 | A13D4D451BE59B72005ADEBA /* Code */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | A13D4D551BE59C96005ADEBA /* Aspects.h */, 76 | A13D4D561BE59C96005ADEBA /* Aspects.m */, 77 | A13D4D4A1BE59B72005ADEBA /* FixCode.h */, 78 | A13D4D4B1BE59B72005ADEBA /* FixCode.m */, 79 | A13D4D4D1BE59B72005ADEBA /* NSObject_Extension.h */, 80 | A13D4D4E1BE59B72005ADEBA /* NSObject_Extension.m */, 81 | ); 82 | name = Code; 83 | path = FixCode; 84 | sourceTree = ""; 85 | }; 86 | A13D4D461BE59B72005ADEBA /* Supporting Files */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | A13D4D471BE59B72005ADEBA /* Info.plist */, 90 | A13D4D481BE59B72005ADEBA /* FixCode.xcscheme */, 91 | ); 92 | name = "Supporting Files"; 93 | path = FixCode; 94 | sourceTree = ""; 95 | }; 96 | /* End PBXGroup section */ 97 | 98 | /* Begin PBXNativeTarget section */ 99 | A13D4D3D1BE59B72005ADEBA /* FixCode */ = { 100 | isa = PBXNativeTarget; 101 | buildConfigurationList = A13D4D521BE59B72005ADEBA /* Build configuration list for PBXNativeTarget "FixCode" */; 102 | buildPhases = ( 103 | A13D4D3A1BE59B72005ADEBA /* Sources */, 104 | A13D4D3B1BE59B72005ADEBA /* Resources */, 105 | A13D4D3C1BE59B72005ADEBA /* Frameworks */, 106 | ); 107 | buildRules = ( 108 | ); 109 | dependencies = ( 110 | ); 111 | name = FixCode; 112 | productName = FixCode; 113 | productReference = A13D4D3E1BE59B72005ADEBA /* FixCode.xcplugin */; 114 | productType = "com.apple.product-type.bundle"; 115 | }; 116 | /* End PBXNativeTarget section */ 117 | 118 | /* Begin PBXProject section */ 119 | A13D4D361BE59B72005ADEBA /* Project object */ = { 120 | isa = PBXProject; 121 | attributes = { 122 | LastUpgradeCheck = 0730; 123 | ORGANIZATIONNAME = Fastlane; 124 | TargetAttributes = { 125 | A13D4D3D1BE59B72005ADEBA = { 126 | CreatedOnToolsVersion = 6.4; 127 | }; 128 | }; 129 | }; 130 | buildConfigurationList = A13D4D391BE59B72005ADEBA /* Build configuration list for PBXProject "FixCode" */; 131 | compatibilityVersion = "Xcode 3.2"; 132 | developmentRegion = English; 133 | hasScannedForEncodings = 0; 134 | knownRegions = ( 135 | en, 136 | ); 137 | mainGroup = A13D4D351BE59B72005ADEBA; 138 | productRefGroup = A13D4D3F1BE59B72005ADEBA /* Products */; 139 | projectDirPath = ""; 140 | projectRoot = ""; 141 | targets = ( 142 | A13D4D3D1BE59B72005ADEBA /* FixCode */, 143 | ); 144 | }; 145 | /* End PBXProject section */ 146 | 147 | /* Begin PBXResourcesBuildPhase section */ 148 | A13D4D3B1BE59B72005ADEBA /* Resources */ = { 149 | isa = PBXResourcesBuildPhase; 150 | buildActionMask = 2147483647; 151 | files = ( 152 | A13D4D491BE59B72005ADEBA /* FixCode.xcscheme in Resources */, 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | /* End PBXResourcesBuildPhase section */ 157 | 158 | /* Begin PBXSourcesBuildPhase section */ 159 | A13D4D3A1BE59B72005ADEBA /* Sources */ = { 160 | isa = PBXSourcesBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | A13D4D4C1BE59B72005ADEBA /* FixCode.m in Sources */, 164 | A13D4D4F1BE59B72005ADEBA /* NSObject_Extension.m in Sources */, 165 | A13D4D571BE59C96005ADEBA /* Aspects.m in Sources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXSourcesBuildPhase section */ 170 | 171 | /* Begin XCBuildConfiguration section */ 172 | A13D4D501BE59B72005ADEBA /* Debug */ = { 173 | isa = XCBuildConfiguration; 174 | buildSettings = { 175 | ALWAYS_SEARCH_USER_PATHS = NO; 176 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 177 | CLANG_CXX_LIBRARY = "libc++"; 178 | CLANG_ENABLE_MODULES = YES; 179 | CLANG_ENABLE_OBJC_ARC = YES; 180 | CLANG_WARN_BOOL_CONVERSION = YES; 181 | CLANG_WARN_CONSTANT_CONVERSION = YES; 182 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 183 | CLANG_WARN_EMPTY_BODY = YES; 184 | CLANG_WARN_ENUM_CONVERSION = YES; 185 | CLANG_WARN_INT_CONVERSION = YES; 186 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 187 | CLANG_WARN_UNREACHABLE_CODE = YES; 188 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 189 | COPY_PHASE_STRIP = NO; 190 | ENABLE_STRICT_OBJC_MSGSEND = YES; 191 | ENABLE_TESTABILITY = YES; 192 | GCC_C_LANGUAGE_STANDARD = gnu99; 193 | GCC_DYNAMIC_NO_PIC = NO; 194 | GCC_NO_COMMON_BLOCKS = YES; 195 | GCC_OPTIMIZATION_LEVEL = 0; 196 | GCC_PREPROCESSOR_DEFINITIONS = ( 197 | "DEBUG=1", 198 | "$(inherited)", 199 | ); 200 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 201 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 202 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 203 | GCC_WARN_UNDECLARED_SELECTOR = YES; 204 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 205 | GCC_WARN_UNUSED_FUNCTION = YES; 206 | GCC_WARN_UNUSED_VARIABLE = YES; 207 | MTL_ENABLE_DEBUG_INFO = YES; 208 | ONLY_ACTIVE_ARCH = YES; 209 | }; 210 | name = Debug; 211 | }; 212 | A13D4D511BE59B72005ADEBA /* Release */ = { 213 | isa = XCBuildConfiguration; 214 | buildSettings = { 215 | ALWAYS_SEARCH_USER_PATHS = NO; 216 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 217 | CLANG_CXX_LIBRARY = "libc++"; 218 | CLANG_ENABLE_MODULES = YES; 219 | CLANG_ENABLE_OBJC_ARC = YES; 220 | CLANG_WARN_BOOL_CONVERSION = YES; 221 | CLANG_WARN_CONSTANT_CONVERSION = YES; 222 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 223 | CLANG_WARN_EMPTY_BODY = YES; 224 | CLANG_WARN_ENUM_CONVERSION = YES; 225 | CLANG_WARN_INT_CONVERSION = YES; 226 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 227 | CLANG_WARN_UNREACHABLE_CODE = YES; 228 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 229 | COPY_PHASE_STRIP = NO; 230 | ENABLE_NS_ASSERTIONS = NO; 231 | ENABLE_STRICT_OBJC_MSGSEND = YES; 232 | GCC_C_LANGUAGE_STANDARD = gnu99; 233 | GCC_NO_COMMON_BLOCKS = YES; 234 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 235 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 236 | GCC_WARN_UNDECLARED_SELECTOR = YES; 237 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 238 | GCC_WARN_UNUSED_FUNCTION = YES; 239 | GCC_WARN_UNUSED_VARIABLE = YES; 240 | MTL_ENABLE_DEBUG_INFO = NO; 241 | }; 242 | name = Release; 243 | }; 244 | A13D4D531BE59B72005ADEBA /* Debug */ = { 245 | isa = XCBuildConfiguration; 246 | buildSettings = { 247 | COMBINE_HIDPI_IMAGES = YES; 248 | DEPLOYMENT_LOCATION = YES; 249 | DSTROOT = "$(HOME)"; 250 | INFOPLIST_FILE = FixCode/Info.plist; 251 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 252 | MACOSX_DEPLOYMENT_TARGET = 10.10; 253 | PRODUCT_BUNDLE_IDENTIFIER = "tools.fastlane.$(PRODUCT_NAME:rfc1034identifier)"; 254 | PRODUCT_NAME = "$(TARGET_NAME)"; 255 | WRAPPER_EXTENSION = xcplugin; 256 | }; 257 | name = Debug; 258 | }; 259 | A13D4D541BE59B72005ADEBA /* Release */ = { 260 | isa = XCBuildConfiguration; 261 | buildSettings = { 262 | COMBINE_HIDPI_IMAGES = YES; 263 | DEPLOYMENT_LOCATION = YES; 264 | DSTROOT = "$(HOME)"; 265 | INFOPLIST_FILE = FixCode/Info.plist; 266 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 267 | MACOSX_DEPLOYMENT_TARGET = 10.10; 268 | PRODUCT_BUNDLE_IDENTIFIER = "tools.fastlane.$(PRODUCT_NAME:rfc1034identifier)"; 269 | PRODUCT_NAME = "$(TARGET_NAME)"; 270 | WRAPPER_EXTENSION = xcplugin; 271 | }; 272 | name = Release; 273 | }; 274 | /* End XCBuildConfiguration section */ 275 | 276 | /* Begin XCConfigurationList section */ 277 | A13D4D391BE59B72005ADEBA /* Build configuration list for PBXProject "FixCode" */ = { 278 | isa = XCConfigurationList; 279 | buildConfigurations = ( 280 | A13D4D501BE59B72005ADEBA /* Debug */, 281 | A13D4D511BE59B72005ADEBA /* Release */, 282 | ); 283 | defaultConfigurationIsVisible = 0; 284 | defaultConfigurationName = Release; 285 | }; 286 | A13D4D521BE59B72005ADEBA /* Build configuration list for PBXNativeTarget "FixCode" */ = { 287 | isa = XCConfigurationList; 288 | buildConfigurations = ( 289 | A13D4D531BE59B72005ADEBA /* Debug */, 290 | A13D4D541BE59B72005ADEBA /* Release */, 291 | ); 292 | defaultConfigurationIsVisible = 0; 293 | defaultConfigurationName = Release; 294 | }; 295 | /* End XCConfigurationList section */ 296 | }; 297 | rootObject = A13D4D361BE59B72005ADEBA /* Project object */; 298 | } 299 | -------------------------------------------------------------------------------- /vendor/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 _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 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 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 AspectsContainer : NSObject 60 | - (void)addAspect:(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 AspectTracker : 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:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName; 75 | - (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName; 76 | - (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName; 77 | - (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName; 78 | @end 79 | 80 | @interface NSInvocation (Aspects) 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:AspectErrorDomain code:errorCode userInfo:@{NSLocalizedDescriptionKey: errorDescription}]; }}while(0) 89 | 90 | NSString *const AspectErrorDomain = @"AspectErrorDomain"; 91 | static NSString *const AspectsSubclassSuffix = @"_Aspects_"; 92 | static NSString *const AspectsMessagePrefix = @"aspects_"; 93 | 94 | @implementation NSObject (Aspects) 95 | 96 | /////////////////////////////////////////////////////////////////////////////////////////// 97 | #pragma mark - Public Aspects API 98 | 99 | + (id)aspect_hookSelector:(SEL)selector 100 | withOptions:(AspectOptions)options 101 | usingBlock:(id)block 102 | error:(NSError **)error { 103 | return aspect_add((id)self, selector, options, block, error); 104 | } 105 | 106 | /// @return A token which allows to later deregister the aspect. 107 | - (id)aspect_hookSelector:(SEL)selector 108 | withOptions:(AspectOptions)options 109 | usingBlock:(id)block 110 | error:(NSError **)error { 111 | return aspect_add(self, selector, options, block, error); 112 | } 113 | 114 | /////////////////////////////////////////////////////////////////////////////////////////// 115 | #pragma mark - Private Helper 116 | 117 | static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) { 118 | NSCParameterAssert(self); 119 | NSCParameterAssert(selector); 120 | NSCParameterAssert(block); 121 | 122 | __block AspectIdentifier *identifier = nil; 123 | aspect_performLocked(^{ 124 | if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) { 125 | AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector); 126 | identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error]; 127 | if (identifier) { 128 | [aspectContainer addAspect:identifier withOptions:options]; 129 | 130 | // Modify the class to allow message interception. 131 | aspect_prepareClassAndHookSelector(self, selector, error); 132 | } 133 | } 134 | }); 135 | return identifier; 136 | } 137 | 138 | static BOOL aspect_remove(AspectIdentifier *aspect, NSError **error) { 139 | NSCAssert([aspect isKindOfClass:AspectIdentifier.class], @"Must have correct type."); 140 | 141 | __block BOOL success = NO; 142 | aspect_performLocked(^{ 143 | id self = aspect.object; // strongify 144 | if (self) { 145 | AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector); 146 | success = [aspectContainer removeAspect:aspect]; 147 | 148 | aspect_cleanupHookedClassAndSelector(self, aspect.selector); 149 | // destroy token 150 | aspect.object = nil; 151 | aspect.block = nil; 152 | aspect.selector = NULL; 153 | }else { 154 | NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", aspect]; 155 | AspectError(AspectErrorRemoveObjectAlreadyDeallocated, errrorDesc); 156 | } 157 | }); 158 | return success; 159 | } 160 | 161 | static void aspect_performLocked(dispatch_block_t block) { 162 | static OSSpinLock aspect_lock = OS_SPINLOCK_INIT; 163 | OSSpinLockLock(&aspect_lock); 164 | block(); 165 | OSSpinLockUnlock(&aspect_lock); 166 | } 167 | 168 | static SEL aspect_aliasForSelector(SEL selector) { 169 | NSCParameterAssert(selector); 170 | return NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]); 171 | } 172 | 173 | static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) { 174 | AspectBlockRef layout = (__bridge void *)block; 175 | if (!(layout->flags & AspectBlockFlagsHasSignature)) { 176 | NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block]; 177 | AspectError(AspectErrorMissingBlockSignature, description); 178 | return nil; 179 | } 180 | void *desc = layout->descriptor; 181 | desc += 2 * sizeof(unsigned long int); 182 | if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) { 183 | desc += 2 * sizeof(void *); 184 | } 185 | if (!desc) { 186 | NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block]; 187 | AspectError(AspectErrorMissingBlockSignature, description); 188 | return nil; 189 | } 190 | const char *signature = (*(const char **)desc); 191 | return [NSMethodSignature signatureWithObjCTypes:signature]; 192 | } 193 | 194 | static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) { 195 | NSCParameterAssert(blockSignature); 196 | NSCParameterAssert(object); 197 | NSCParameterAssert(selector); 198 | 199 | BOOL signaturesMatch = YES; 200 | NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector]; 201 | if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) { 202 | signaturesMatch = NO; 203 | }else { 204 | if (blockSignature.numberOfArguments > 1) { 205 | const char *blockType = [blockSignature getArgumentTypeAtIndex:1]; 206 | if (blockType[0] != '@') { 207 | signaturesMatch = NO; 208 | } 209 | } 210 | // Argument 0 is self/block, argument 1 is SEL or id. We start comparing at argument 2. 211 | // The block can have less arguments than the method, that's ok. 212 | if (signaturesMatch) { 213 | for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) { 214 | const char *methodType = [methodSignature getArgumentTypeAtIndex:idx]; 215 | const char *blockType = [blockSignature getArgumentTypeAtIndex:idx]; 216 | // Only compare parameter, not the optional type data. 217 | if (!methodType || !blockType || methodType[0] != blockType[0]) { 218 | signaturesMatch = NO; break; 219 | } 220 | } 221 | } 222 | } 223 | 224 | if (!signaturesMatch) { 225 | NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature]; 226 | AspectError(AspectErrorIncompatibleBlockSignature, description); 227 | return NO; 228 | } 229 | return YES; 230 | } 231 | 232 | /////////////////////////////////////////////////////////////////////////////////////////// 233 | #pragma mark - Class + Selector Preparation 234 | 235 | static BOOL aspect_isMsgForwardIMP(IMP impl) { 236 | return impl == _objc_msgForward 237 | #if !defined(__arm64__) 238 | || impl == (IMP)_objc_msgForward_stret 239 | #endif 240 | ; 241 | } 242 | 243 | static IMP aspect_getMsgForwardIMP(NSObject *self, SEL selector) { 244 | IMP msgForwardIMP = _objc_msgForward; 245 | #if !defined(__arm64__) 246 | // 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. 247 | // https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html 248 | // https://github.com/ReactiveCocoa/ReactiveCocoa/issues/783 249 | // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf (Section 5.4) 250 | Method method = class_getInstanceMethod(self.class, selector); 251 | const char *encoding = method_getTypeEncoding(method); 252 | BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B; 253 | if (methodReturnsStructValue) { 254 | @try { 255 | NSUInteger valueSize = 0; 256 | NSGetSizeAndAlignment(encoding, &valueSize, NULL); 257 | 258 | if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) { 259 | methodReturnsStructValue = NO; 260 | } 261 | } @catch (__unused NSException *e) {} 262 | } 263 | if (methodReturnsStructValue) { 264 | msgForwardIMP = (IMP)_objc_msgForward_stret; 265 | } 266 | #endif 267 | return msgForwardIMP; 268 | } 269 | 270 | static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) { 271 | NSCParameterAssert(selector); 272 | Class klass = aspect_hookClass(self, error); 273 | Method targetMethod = class_getInstanceMethod(klass, selector); 274 | IMP targetMethodIMP = method_getImplementation(targetMethod); 275 | if (!aspect_isMsgForwardIMP(targetMethodIMP)) { 276 | // Make a method alias for the existing method implementation, it not already copied. 277 | const char *typeEncoding = method_getTypeEncoding(targetMethod); 278 | SEL aliasSelector = aspect_aliasForSelector(selector); 279 | if (![klass instancesRespondToSelector:aliasSelector]) { 280 | __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding); 281 | NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass); 282 | } 283 | 284 | // We use forwardInvocation to hook in. 285 | class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding); 286 | AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector)); 287 | } 288 | } 289 | 290 | // Will undo the runtime changes made. 291 | static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) { 292 | NSCParameterAssert(self); 293 | NSCParameterAssert(selector); 294 | 295 | Class klass = object_getClass(self); 296 | BOOL isMetaClass = class_isMetaClass(klass); 297 | if (isMetaClass) { 298 | klass = (Class)self; 299 | } 300 | 301 | // Check if the method is marked as forwarded and undo that. 302 | Method targetMethod = class_getInstanceMethod(klass, selector); 303 | IMP targetMethodIMP = method_getImplementation(targetMethod); 304 | if (aspect_isMsgForwardIMP(targetMethodIMP)) { 305 | // Restore the original method implementation. 306 | const char *typeEncoding = method_getTypeEncoding(targetMethod); 307 | SEL aliasSelector = aspect_aliasForSelector(selector); 308 | Method originalMethod = class_getInstanceMethod(klass, aliasSelector); 309 | IMP originalIMP = method_getImplementation(originalMethod); 310 | NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass); 311 | 312 | class_replaceMethod(klass, selector, originalIMP, typeEncoding); 313 | AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector)); 314 | } 315 | 316 | // Deregister global tracked selector 317 | aspect_deregisterTrackedSelector(self, selector); 318 | 319 | // Get the aspect container and check if there are any hooks remaining. Clean up if there are not. 320 | AspectsContainer *container = aspect_getContainerForObject(self, selector); 321 | if (!container.hasAspects) { 322 | // Destroy the container 323 | aspect_destroyContainerForObject(self, selector); 324 | 325 | // Figure out how the class was modified to undo the changes. 326 | NSString *className = NSStringFromClass(klass); 327 | if ([className hasSuffix:AspectsSubclassSuffix]) { 328 | Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]); 329 | NSCAssert(originalClass != nil, @"Original class must exist"); 330 | object_setClass(self, originalClass); 331 | AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass)); 332 | 333 | // We can only dispose the class pair if we can ensure that no instances exist using our subclass. 334 | // Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around. 335 | //objc_disposeClassPair(object.class); 336 | }else { 337 | // Class is most likely swizzled in place. Undo that. 338 | if (isMetaClass) { 339 | aspect_undoSwizzleClassInPlace((Class)self); 340 | }else if (self.class != klass) { 341 | aspect_undoSwizzleClassInPlace(klass); 342 | } 343 | } 344 | } 345 | } 346 | 347 | /////////////////////////////////////////////////////////////////////////////////////////// 348 | #pragma mark - Hook Class 349 | 350 | static Class aspect_hookClass(NSObject *self, NSError **error) { 351 | NSCParameterAssert(self); 352 | Class statedClass = self.class; 353 | Class baseClass = object_getClass(self); 354 | NSString *className = NSStringFromClass(baseClass); 355 | 356 | // Already subclassed 357 | if ([className hasSuffix:AspectsSubclassSuffix]) { 358 | return baseClass; 359 | 360 | // We swizzle a class object, not a single object. 361 | }else if (class_isMetaClass(baseClass)) { 362 | return aspect_swizzleClassInPlace((Class)self); 363 | // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place. 364 | }else if (statedClass != baseClass) { 365 | return aspect_swizzleClassInPlace(baseClass); 366 | } 367 | 368 | // Default case. Create dynamic subclass. 369 | const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String; 370 | Class subclass = objc_getClass(subclassName); 371 | 372 | if (subclass == nil) { 373 | subclass = objc_allocateClassPair(baseClass, subclassName, 0); 374 | if (subclass == nil) { 375 | NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName]; 376 | AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc); 377 | return nil; 378 | } 379 | 380 | aspect_swizzleForwardInvocation(subclass); 381 | aspect_hookedGetClass(subclass, statedClass); 382 | aspect_hookedGetClass(object_getClass(subclass), statedClass); 383 | objc_registerClassPair(subclass); 384 | } 385 | 386 | object_setClass(self, subclass); 387 | return subclass; 388 | } 389 | 390 | static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:"; 391 | static void aspect_swizzleForwardInvocation(Class klass) { 392 | NSCParameterAssert(klass); 393 | // If there is no method, replace will act like class_addMethod. 394 | IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@"); 395 | if (originalImplementation) { 396 | class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@"); 397 | } 398 | AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass)); 399 | } 400 | 401 | static void aspect_undoSwizzleForwardInvocation(Class klass) { 402 | NSCParameterAssert(klass); 403 | Method originalMethod = class_getInstanceMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName)); 404 | Method objectMethod = class_getInstanceMethod(NSObject.class, @selector(forwardInvocation:)); 405 | // There is no class_removeMethod, so the best we can do is to retore the original implementation, or use a dummy. 406 | IMP originalImplementation = method_getImplementation(originalMethod ?: objectMethod); 407 | class_replaceMethod(klass, @selector(forwardInvocation:), originalImplementation, "v@:@"); 408 | 409 | AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(klass)); 410 | } 411 | 412 | static void aspect_hookedGetClass(Class class, Class statedClass) { 413 | NSCParameterAssert(class); 414 | NSCParameterAssert(statedClass); 415 | Method method = class_getInstanceMethod(class, @selector(class)); 416 | IMP newIMP = imp_implementationWithBlock(^(id self) { 417 | return statedClass; 418 | }); 419 | class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method)); 420 | } 421 | 422 | /////////////////////////////////////////////////////////////////////////////////////////// 423 | #pragma mark - Swizzle Class In Place 424 | 425 | static void _aspect_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) { 426 | static NSMutableSet *swizzledClasses; 427 | static dispatch_once_t pred; 428 | dispatch_once(&pred, ^{ 429 | swizzledClasses = [NSMutableSet new]; 430 | }); 431 | @synchronized(swizzledClasses) { 432 | block(swizzledClasses); 433 | } 434 | } 435 | 436 | static Class aspect_swizzleClassInPlace(Class klass) { 437 | NSCParameterAssert(klass); 438 | NSString *className = NSStringFromClass(klass); 439 | 440 | _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) { 441 | if (![swizzledClasses containsObject:className]) { 442 | aspect_swizzleForwardInvocation(klass); 443 | [swizzledClasses addObject:className]; 444 | } 445 | }); 446 | return klass; 447 | } 448 | 449 | static void aspect_undoSwizzleClassInPlace(Class klass) { 450 | NSCParameterAssert(klass); 451 | NSString *className = NSStringFromClass(klass); 452 | 453 | _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) { 454 | if ([swizzledClasses containsObject:className]) { 455 | aspect_undoSwizzleForwardInvocation(klass); 456 | [swizzledClasses removeObject:className]; 457 | } 458 | }); 459 | } 460 | 461 | /////////////////////////////////////////////////////////////////////////////////////////// 462 | #pragma mark - Aspect Invoke Point 463 | 464 | // This is a macro so we get a cleaner stack trace. 465 | #define aspect_invoke(aspects, info) \ 466 | for (AspectIdentifier *aspect in aspects) {\ 467 | [aspect invokeWithInfo:info];\ 468 | if (aspect.options & AspectOptionAutomaticRemoval) { \ 469 | aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \ 470 | } \ 471 | } 472 | 473 | // This is the swizzled forwardInvocation: method. 474 | static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) { 475 | NSCParameterAssert(self); 476 | NSCParameterAssert(invocation); 477 | SEL originalSelector = invocation.selector; 478 | SEL aliasSelector = aspect_aliasForSelector(invocation.selector); 479 | invocation.selector = aliasSelector; 480 | AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector); 481 | AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector); 482 | AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation]; 483 | NSArray *aspectsToRemove = nil; 484 | 485 | // Before hooks. 486 | aspect_invoke(classContainer.beforeAspects, info); 487 | aspect_invoke(objectContainer.beforeAspects, info); 488 | 489 | // Instead hooks. 490 | BOOL respondsToAlias = YES; 491 | if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) { 492 | aspect_invoke(classContainer.insteadAspects, info); 493 | aspect_invoke(objectContainer.insteadAspects, info); 494 | }else { 495 | Class klass = object_getClass(invocation.target); 496 | do { 497 | if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) { 498 | [invocation invoke]; 499 | break; 500 | } 501 | }while (!respondsToAlias && (klass = class_getSuperclass(klass))); 502 | } 503 | 504 | // After hooks. 505 | aspect_invoke(classContainer.afterAspects, info); 506 | aspect_invoke(objectContainer.afterAspects, info); 507 | 508 | // If no hooks are installed, call original implementation (usually to throw an exception) 509 | if (!respondsToAlias) { 510 | invocation.selector = originalSelector; 511 | SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName); 512 | if ([self respondsToSelector:originalForwardInvocationSEL]) { 513 | ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation); 514 | }else { 515 | [self doesNotRecognizeSelector:invocation.selector]; 516 | } 517 | } 518 | 519 | // Remove any hooks that are queued for deregistration. 520 | [aspectsToRemove makeObjectsPerformSelector:@selector(remove)]; 521 | } 522 | #undef aspect_invoke 523 | 524 | /////////////////////////////////////////////////////////////////////////////////////////// 525 | #pragma mark - Aspect Container Management 526 | 527 | // Loads or creates the aspect container. 528 | static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) { 529 | NSCParameterAssert(self); 530 | SEL aliasSelector = aspect_aliasForSelector(selector); 531 | AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector); 532 | if (!aspectContainer) { 533 | aspectContainer = [AspectsContainer new]; 534 | objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN); 535 | } 536 | return aspectContainer; 537 | } 538 | 539 | static AspectsContainer *aspect_getContainerForClass(Class klass, SEL selector) { 540 | NSCParameterAssert(klass); 541 | AspectsContainer *classContainer = nil; 542 | do { 543 | classContainer = objc_getAssociatedObject(klass, selector); 544 | if (classContainer.hasAspects) break; 545 | }while ((klass = class_getSuperclass(klass))); 546 | 547 | return classContainer; 548 | } 549 | 550 | static void aspect_destroyContainerForObject(id self, SEL selector) { 551 | NSCParameterAssert(self); 552 | SEL aliasSelector = aspect_aliasForSelector(selector); 553 | objc_setAssociatedObject(self, aliasSelector, nil, OBJC_ASSOCIATION_RETAIN); 554 | } 555 | 556 | /////////////////////////////////////////////////////////////////////////////////////////// 557 | #pragma mark - Selector Blacklist Checking 558 | 559 | static NSMutableDictionary *aspect_getSwizzledClassesDict() { 560 | static NSMutableDictionary *swizzledClassesDict; 561 | static dispatch_once_t pred; 562 | dispatch_once(&pred, ^{ 563 | swizzledClassesDict = [NSMutableDictionary new]; 564 | }); 565 | return swizzledClassesDict; 566 | } 567 | 568 | static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) { 569 | static NSSet *disallowedSelectorList; 570 | static dispatch_once_t pred; 571 | dispatch_once(&pred, ^{ 572 | disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil]; 573 | }); 574 | 575 | // Check against the blacklist. 576 | NSString *selectorName = NSStringFromSelector(selector); 577 | if ([disallowedSelectorList containsObject:selectorName]) { 578 | NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName]; 579 | AspectError(AspectErrorSelectorBlacklisted, errorDescription); 580 | return NO; 581 | } 582 | 583 | // Additional checks. 584 | AspectOptions position = options&AspectPositionFilter; 585 | if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) { 586 | NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc."; 587 | AspectError(AspectErrorSelectorDeallocPosition, errorDesc); 588 | return NO; 589 | } 590 | 591 | if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) { 592 | NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName]; 593 | AspectError(AspectErrorDoesNotRespondToSelector, errorDesc); 594 | return NO; 595 | } 596 | 597 | // Search for the current class and the class hierarchy IF we are modifying a class object 598 | if (class_isMetaClass(object_getClass(self))) { 599 | Class klass = [self class]; 600 | NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict(); 601 | Class currentClass = [self class]; 602 | 603 | AspectTracker *tracker = swizzledClassesDict[currentClass]; 604 | if ([tracker subclassHasHookedSelectorName:selectorName]) { 605 | NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName]; 606 | NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"]; 607 | NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked subclasses: %@. A method can only be hooked once per class hierarchy.", selectorName, subclassNames]; 608 | AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription); 609 | return NO; 610 | } 611 | 612 | do { 613 | tracker = swizzledClassesDict[currentClass]; 614 | if ([tracker.selectorNames containsObject:selectorName]) { 615 | if (klass == currentClass) { 616 | // Already modified and topmost! 617 | return YES; 618 | } 619 | NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)]; 620 | AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription); 621 | return NO; 622 | } 623 | } while ((currentClass = class_getSuperclass(currentClass))); 624 | 625 | // Add the selector as being modified. 626 | currentClass = klass; 627 | AspectTracker *subclassTracker = nil; 628 | do { 629 | tracker = swizzledClassesDict[currentClass]; 630 | if (!tracker) { 631 | tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass]; 632 | swizzledClassesDict[(id)currentClass] = tracker; 633 | } 634 | if (subclassTracker) { 635 | [tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName]; 636 | } else { 637 | [tracker.selectorNames addObject:selectorName]; 638 | } 639 | 640 | // All superclasses get marked as having a subclass that is modified. 641 | subclassTracker = tracker; 642 | }while ((currentClass = class_getSuperclass(currentClass))); 643 | } else { 644 | return YES; 645 | } 646 | 647 | return YES; 648 | } 649 | 650 | static void aspect_deregisterTrackedSelector(id self, SEL selector) { 651 | if (!class_isMetaClass(object_getClass(self))) return; 652 | 653 | NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict(); 654 | NSString *selectorName = NSStringFromSelector(selector); 655 | Class currentClass = [self class]; 656 | AspectTracker *subclassTracker = nil; 657 | do { 658 | AspectTracker *tracker = swizzledClassesDict[currentClass]; 659 | if (subclassTracker) { 660 | [tracker removeSubclassTracker:subclassTracker hookingSelectorName:selectorName]; 661 | } else { 662 | [tracker.selectorNames removeObject:selectorName]; 663 | } 664 | if (tracker.selectorNames.count == 0 && tracker.selectorNamesToSubclassTrackers) { 665 | [swizzledClassesDict removeObjectForKey:currentClass]; 666 | } 667 | subclassTracker = tracker; 668 | }while ((currentClass = class_getSuperclass(currentClass))); 669 | } 670 | 671 | @end 672 | 673 | @implementation AspectTracker 674 | 675 | - (id)initWithTrackedClass:(Class)trackedClass { 676 | if (self = [super init]) { 677 | _trackedClass = trackedClass; 678 | _selectorNames = [NSMutableSet new]; 679 | _selectorNamesToSubclassTrackers = [NSMutableDictionary new]; 680 | } 681 | return self; 682 | } 683 | 684 | - (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName { 685 | return self.selectorNamesToSubclassTrackers[selectorName] != nil; 686 | } 687 | 688 | - (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName { 689 | NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName]; 690 | if (!trackerSet) { 691 | trackerSet = [NSMutableSet new]; 692 | self.selectorNamesToSubclassTrackers[selectorName] = trackerSet; 693 | } 694 | [trackerSet addObject:subclassTracker]; 695 | } 696 | - (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName { 697 | NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName]; 698 | [trackerSet removeObject:subclassTracker]; 699 | if (trackerSet.count == 0) { 700 | [self.selectorNamesToSubclassTrackers removeObjectForKey:selectorName]; 701 | } 702 | } 703 | - (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName { 704 | NSMutableSet *hookingSubclassTrackers = [NSMutableSet new]; 705 | for (AspectTracker *tracker in self.selectorNamesToSubclassTrackers[selectorName]) { 706 | if ([tracker.selectorNames containsObject:selectorName]) { 707 | [hookingSubclassTrackers addObject:tracker]; 708 | } 709 | [hookingSubclassTrackers unionSet:[tracker subclassTrackersHookingSelectorName:selectorName]]; 710 | } 711 | return hookingSubclassTrackers; 712 | } 713 | - (NSString *)trackedClassName { 714 | return NSStringFromClass(self.trackedClass); 715 | } 716 | 717 | - (NSString *)description { 718 | return [NSString stringWithFormat:@"<%@: %@, trackedClass: %@, selectorNames:%@, subclass selector names: %@>", self.class, self, NSStringFromClass(self.trackedClass), self.selectorNames, self.selectorNamesToSubclassTrackers.allKeys]; 719 | } 720 | 721 | @end 722 | 723 | /////////////////////////////////////////////////////////////////////////////////////////// 724 | #pragma mark - NSInvocation (Aspects) 725 | 726 | @implementation NSInvocation (Aspects) 727 | 728 | // Thanks to the ReactiveCocoa team for providing a generic solution for this. 729 | - (id)aspect_argumentAtIndex:(NSUInteger)index { 730 | const char *argType = [self.methodSignature getArgumentTypeAtIndex:index]; 731 | // Skip const type qualifier. 732 | if (argType[0] == _C_CONST) argType++; 733 | 734 | #define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0) 735 | if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) { 736 | __autoreleasing id returnObj; 737 | [self getArgument:&returnObj atIndex:(NSInteger)index]; 738 | return returnObj; 739 | } else if (strcmp(argType, @encode(SEL)) == 0) { 740 | SEL selector = 0; 741 | [self getArgument:&selector atIndex:(NSInteger)index]; 742 | return NSStringFromSelector(selector); 743 | } else if (strcmp(argType, @encode(Class)) == 0) { 744 | __autoreleasing Class theClass = Nil; 745 | [self getArgument:&theClass atIndex:(NSInteger)index]; 746 | return theClass; 747 | // Using this list will box the number with the appropriate constructor, instead of the generic NSValue. 748 | } else if (strcmp(argType, @encode(char)) == 0) { 749 | WRAP_AND_RETURN(char); 750 | } else if (strcmp(argType, @encode(int)) == 0) { 751 | WRAP_AND_RETURN(int); 752 | } else if (strcmp(argType, @encode(short)) == 0) { 753 | WRAP_AND_RETURN(short); 754 | } else if (strcmp(argType, @encode(long)) == 0) { 755 | WRAP_AND_RETURN(long); 756 | } else if (strcmp(argType, @encode(long long)) == 0) { 757 | WRAP_AND_RETURN(long long); 758 | } else if (strcmp(argType, @encode(unsigned char)) == 0) { 759 | WRAP_AND_RETURN(unsigned char); 760 | } else if (strcmp(argType, @encode(unsigned int)) == 0) { 761 | WRAP_AND_RETURN(unsigned int); 762 | } else if (strcmp(argType, @encode(unsigned short)) == 0) { 763 | WRAP_AND_RETURN(unsigned short); 764 | } else if (strcmp(argType, @encode(unsigned long)) == 0) { 765 | WRAP_AND_RETURN(unsigned long); 766 | } else if (strcmp(argType, @encode(unsigned long long)) == 0) { 767 | WRAP_AND_RETURN(unsigned long long); 768 | } else if (strcmp(argType, @encode(float)) == 0) { 769 | WRAP_AND_RETURN(float); 770 | } else if (strcmp(argType, @encode(double)) == 0) { 771 | WRAP_AND_RETURN(double); 772 | } else if (strcmp(argType, @encode(BOOL)) == 0) { 773 | WRAP_AND_RETURN(BOOL); 774 | } else if (strcmp(argType, @encode(bool)) == 0) { 775 | WRAP_AND_RETURN(BOOL); 776 | } else if (strcmp(argType, @encode(char *)) == 0) { 777 | WRAP_AND_RETURN(const char *); 778 | } else if (strcmp(argType, @encode(void (^)(void))) == 0) { 779 | __unsafe_unretained id block = nil; 780 | [self getArgument:&block atIndex:(NSInteger)index]; 781 | return [block copy]; 782 | } else { 783 | NSUInteger valueSize = 0; 784 | NSGetSizeAndAlignment(argType, &valueSize, NULL); 785 | 786 | unsigned char valueBytes[valueSize]; 787 | [self getArgument:valueBytes atIndex:(NSInteger)index]; 788 | 789 | return [NSValue valueWithBytes:valueBytes objCType:argType]; 790 | } 791 | return nil; 792 | #undef WRAP_AND_RETURN 793 | } 794 | 795 | - (NSArray *)aspects_arguments { 796 | NSMutableArray *argumentsArray = [NSMutableArray array]; 797 | for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) { 798 | [argumentsArray addObject:[self aspect_argumentAtIndex:idx] ?: NSNull.null]; 799 | } 800 | return [argumentsArray copy]; 801 | } 802 | 803 | @end 804 | 805 | /////////////////////////////////////////////////////////////////////////////////////////// 806 | #pragma mark - AspectIdentifier 807 | 808 | @implementation AspectIdentifier 809 | 810 | + (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error { 811 | NSCParameterAssert(block); 812 | NSCParameterAssert(selector); 813 | NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc. 814 | if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) { 815 | return nil; 816 | } 817 | 818 | AspectIdentifier *identifier = nil; 819 | if (blockSignature) { 820 | identifier = [AspectIdentifier new]; 821 | identifier.selector = selector; 822 | identifier.block = block; 823 | identifier.blockSignature = blockSignature; 824 | identifier.options = options; 825 | identifier.object = object; // weak 826 | } 827 | return identifier; 828 | } 829 | 830 | - (BOOL)invokeWithInfo:(id)info { 831 | NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature]; 832 | NSInvocation *originalInvocation = info.originalInvocation; 833 | NSUInteger numberOfArguments = self.blockSignature.numberOfArguments; 834 | 835 | // Be extra paranoid. We already check that on hook registration. 836 | if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) { 837 | AspectLogError(@"Block has too many arguments. Not calling %@", info); 838 | return NO; 839 | } 840 | 841 | // The `self` of the block will be the AspectInfo. Optional. 842 | if (numberOfArguments > 1) { 843 | [blockInvocation setArgument:&info atIndex:1]; 844 | } 845 | 846 | void *argBuf = NULL; 847 | for (NSUInteger idx = 2; idx < numberOfArguments; idx++) { 848 | const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx]; 849 | NSUInteger argSize; 850 | NSGetSizeAndAlignment(type, &argSize, NULL); 851 | 852 | if (!(argBuf = reallocf(argBuf, argSize))) { 853 | AspectLogError(@"Failed to allocate memory for block invocation."); 854 | return NO; 855 | } 856 | 857 | [originalInvocation getArgument:argBuf atIndex:idx]; 858 | [blockInvocation setArgument:argBuf atIndex:idx]; 859 | } 860 | 861 | [blockInvocation invokeWithTarget:self.block]; 862 | 863 | if (argBuf != NULL) { 864 | free(argBuf); 865 | } 866 | return YES; 867 | } 868 | 869 | - (NSString *)description { 870 | 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]; 871 | } 872 | 873 | - (BOOL)remove { 874 | return aspect_remove(self, NULL); 875 | } 876 | 877 | @end 878 | 879 | /////////////////////////////////////////////////////////////////////////////////////////// 880 | #pragma mark - AspectsContainer 881 | 882 | @implementation AspectsContainer 883 | 884 | - (BOOL)hasAspects { 885 | return self.beforeAspects.count > 0 || self.insteadAspects.count > 0 || self.afterAspects.count > 0; 886 | } 887 | 888 | - (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)options { 889 | NSParameterAssert(aspect); 890 | NSUInteger position = options&AspectPositionFilter; 891 | switch (position) { 892 | case AspectPositionBefore: self.beforeAspects = [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break; 893 | case AspectPositionInstead: self.insteadAspects = [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break; 894 | case AspectPositionAfter: self.afterAspects = [(self.afterAspects ?:@[]) arrayByAddingObject:aspect]; break; 895 | } 896 | } 897 | 898 | - (BOOL)removeAspect:(id)aspect { 899 | for (NSString *aspectArrayName in @[NSStringFromSelector(@selector(beforeAspects)), 900 | NSStringFromSelector(@selector(insteadAspects)), 901 | NSStringFromSelector(@selector(afterAspects))]) { 902 | NSArray *array = [self valueForKey:aspectArrayName]; 903 | NSUInteger index = [array indexOfObjectIdenticalTo:aspect]; 904 | if (array && index != NSNotFound) { 905 | NSMutableArray *newArray = [NSMutableArray arrayWithArray:array]; 906 | [newArray removeObjectAtIndex:index]; 907 | [self setValue:newArray forKey:aspectArrayName]; 908 | return YES; 909 | } 910 | } 911 | return NO; 912 | } 913 | 914 | - (NSString *)description { 915 | return [NSString stringWithFormat:@"<%@: %p, before:%@, instead:%@, after:%@>", self.class, self, self.beforeAspects, self.insteadAspects, self.afterAspects]; 916 | } 917 | 918 | @end 919 | 920 | /////////////////////////////////////////////////////////////////////////////////////////// 921 | #pragma mark - AspectInfo 922 | 923 | @implementation AspectInfo 924 | 925 | @synthesize arguments = _arguments; 926 | 927 | - (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation { 928 | NSCParameterAssert(instance); 929 | NSCParameterAssert(invocation); 930 | if (self = [super init]) { 931 | _instance = instance; 932 | _originalInvocation = invocation; 933 | } 934 | return self; 935 | } 936 | 937 | - (NSArray *)arguments { 938 | // Lazily evaluate arguments, boxing is expensive. 939 | if (!_arguments) { 940 | _arguments = self.originalInvocation.aspects_arguments; 941 | } 942 | return _arguments; 943 | } 944 | 945 | @end 946 | --------------------------------------------------------------------------------