├── Icon.icns
├── Preview.png
├── WriteReceipt.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── README.md
├── LICENSE
├── WriteReceipt
├── Info.plist
├── WriteReceipt.m
└── ZKSwizzle
│ ├── ZKSwizzle.h
│ └── ZKSwizzle.m
└── .gitignore
/Icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shishkabibal/WriteReceipt/HEAD/Icon.icns
--------------------------------------------------------------------------------
/Preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shishkabibal/WriteReceipt/HEAD/Preview.png
--------------------------------------------------------------------------------
/WriteReceipt.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/WriteReceipt.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/WriteReceipt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WriteReceipt
2 |
3 | 
4 |
5 | # Information:
6 |
7 | - WriteReceipt is a SIMBL plugin for Messages that leaves chats unread (and read receipts unsent) until you begin typing a reply (or press enter)
8 | - Tested on macOS 10.13.4 (Messages 11.0)
9 | - Author: [shishkabibal](https://github.com/shishkabibal)
10 |
11 | # Installation:
12 |
13 | 1. Download [mySIMBL](https://github.com/w0lfschild/mySIMBL/)
14 | 2. Download [WriteReceipt](https://github.com/shishkabibal/WriteReceipt/releases/latest)
15 | 3. Unzip both downloads
16 | 4. Open `WriteReceipt.bundle` with `mySIMBL.app`
17 | 5. Restart Messages to load WriteReceipt
18 |
19 | ### Appeal:
20 | Repurpose the code any way you like, but please don't just repackage it with your name. Remember to be nice to other people. If you make any changes and want to contribute, feel free to make a pull request.
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Brian "Shishkabibal"
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.
--------------------------------------------------------------------------------
/WriteReceipt/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 | Icon
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | BNDL
19 | CFBundleShortVersionString
20 | 1.1
21 | CFBundleSignature
22 | ????
23 | NSHumanReadableCopyright
24 | Copyright © 2018 Brian "Shishkabibal". All rights reserved.
25 | NSPrincipalClass
26 | WriteReceipt
27 | SIMBLTargetApplications
28 |
29 |
30 | BundleIdentifier
31 | com.apple.iChat
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | # CocoaPods
32 | #
33 | # We recommend against adding the Pods directory to your .gitignore. However
34 | # you should judge for yourself, the pros and cons are mentioned at:
35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
36 | #
37 | # Pods/
38 |
39 | # Carthage
40 | #
41 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
42 | # Carthage/Checkouts
43 |
44 | Carthage/Build
45 |
46 | # fastlane
47 | #
48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
49 | # screenshots whenever they are needed.
50 | # For more information about the recommended setup visit:
51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
52 |
53 | fastlane/report.xml
54 | fastlane/Preview.html
55 | fastlane/screenshots
56 | fastlane/test_output
57 |
58 | # Code Injection
59 | #
60 | # After new code Injection tools there's a generated folder /iOSInjectionProject
61 | # https://github.com/johnno1962/injectionforxcode
62 |
63 | iOSInjectionProject/
64 |
--------------------------------------------------------------------------------
/WriteReceipt/WriteReceipt.m:
--------------------------------------------------------------------------------
1 | //
2 | // WriteReceipt.m
3 | // WriteReceipt
4 | //
5 | // Created by Brian "Shishkabibal" on 4/26/18.
6 | // Copyright (c) 2018 Brian "Shishkabibal". All rights reserved.
7 | //
8 |
9 | // Imports
10 |
11 | @import AppKit;
12 | #import "ZKSwizzle.h"
13 |
14 | // Interfaces
15 |
16 | @interface WriteReceipt : NSObject
17 | @end
18 |
19 | @interface UnifiedChatListViewController : NSViewController
20 | - (void)markAsRead:(id)arg1;
21 | @end
22 |
23 | @interface NSUserNotificationCenter (Private)
24 | - (void)_removeDisplayedNotification:(id)arg1;
25 | @end
26 |
27 | // Variables
28 |
29 | WriteReceipt* plugin;
30 | UnifiedChatListViewController* unifiedChatListViewController;
31 | BOOL keyPressed;
32 |
33 | // WriteReceipt
34 |
35 | @implementation WriteReceipt
36 |
37 | + (WriteReceipt*) sharedInstance {
38 | static WriteReceipt* plugin = nil;
39 |
40 | if (plugin == nil)
41 | plugin = [[WriteReceipt alloc] init];
42 |
43 | return plugin;
44 | }
45 |
46 | +(void)load {
47 | NSLog(@"WriteReceipt: Plugin Loaded");
48 |
49 | plugin = [WriteReceipt sharedInstance];
50 | }
51 |
52 | @end
53 |
54 | // ZKSwizzleInterfaces
55 |
56 | ZKSwizzleInterface(BSUnifiedChatWindowController, UnifiedChatWindowController, NSWindowController)
57 | @implementation BSUnifiedChatWindowController
58 |
59 | // Required because `[ChatController markAllMessagesAsRead]` used to handle this
60 | - (void)userNotificationCenter:(id)arg1 didActivateNotification:(id)arg2 {
61 | [arg1 _removeDisplayedNotification:arg2];
62 | ZKOrig(void, arg1, arg2);
63 | }
64 |
65 | @end
66 |
67 | ZKSwizzleInterface(BSUnifiedChatListViewController, UnifiedChatListViewController, NSViewController)
68 | @implementation BSUnifiedChatListViewController
69 |
70 | // Reqired to call `[UnifiedChatListViewController markAsRead:arg1]` from `SOInputLine`
71 | - (void)viewDidAppear {
72 | NSLog(@"WriteReceipt: Hooked UnifiedChatListViewController");
73 |
74 | unifiedChatListViewController = (UnifiedChatListViewController*)self;
75 |
76 | ZKOrig(void);
77 | }
78 |
79 | @end
80 |
81 | ZKSwizzleInterface(BSChatController, ChatController, NSObject)
82 | @implementation BSChatController
83 |
84 | // Block `[ChatController markAllMessagesAsRead]` unless `keyPressed`
85 | - (void)markAllMessagesAsRead {
86 | if (keyPressed) {
87 | NSLog(@"WriteReceipt: Modified markAllMessagesAsRead");
88 |
89 | keyPressed = NO;
90 |
91 | ZKOrig(void);
92 | } else {
93 | NSLog(@"WriteReceipt: Blocked markAllMessagesAsRead");
94 | }
95 | }
96 |
97 | @end
98 |
99 | ZKSwizzleInterface(BSSOInputLine, SOInputLine, NSTextView)
100 | @implementation BSSOInputLine
101 |
102 | // Initiate `[unifiedChatListViewController markAsRead:arg1]` with `keyPressed`
103 | - (void)keyDown:(id)arg1 {
104 | NSLog(@"WriteReceipt: Modified keyDown");
105 |
106 | keyPressed = YES;
107 | [unifiedChatListViewController markAsRead:arg1];
108 |
109 | ZKOrig(void, arg1);
110 | }
111 |
112 | @end
113 |
--------------------------------------------------------------------------------
/WriteReceipt/ZKSwizzle/ZKSwizzle.h:
--------------------------------------------------------------------------------
1 | //
2 | // ZKSwizzle.h
3 | // ZKSwizzle
4 | //
5 | // Created by Alexander S Zielenski on 7/24/14.
6 | // Copyright (c) 2014 Alexander S Zielenski. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 | #import
12 |
13 | // This is a class for streamlining swizzling. Simply create a new class of any name you want and
14 | // Example:
15 | /*
16 | @interface ZKHookClass : NSObject
17 | - (NSString *)description; // hooks -description on NSObject
18 | - (void)addedMethod; // all subclasses of NSObject now respond to -addedMethod
19 | @end
20 |
21 | @implementation ZKHookClass
22 | ...
23 | @end
24 |
25 | [ZKSwizzle swizzleClass:ZKClass(ZKHookClass) forClass:ZKClass(destination)];
26 | */
27 |
28 | #ifndef ZKSWIZZLE_DEFS
29 | #define ZKSWIZZLE_DEFS
30 |
31 | // CRAZY MACROS FOR DYNAMIC PROTOTYPE CREATION
32 | #define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(0, ## __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5 ,4 ,3 ,2, 1, 0)
33 | #define VA_NUM_ARGS_IMPL(_0, _1,_2,_3,_4,_5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20 ,N,...) N
34 |
35 | #define WRAP0()
36 | #define WRAP1(VARIABLE) , typeof ( VARIABLE )
37 | #define WRAP2(VARIABLE, ...) WRAP1(VARIABLE) WRAP1(__VA_ARGS__)
38 | #define WRAP3(VARIABLE, ...) WRAP1(VARIABLE) WRAP2(__VA_ARGS__)
39 | #define WRAP4(VARIABLE, ...) WRAP1(VARIABLE) WRAP3(__VA_ARGS__)
40 | #define WRAP5(VARIABLE, ...) WRAP1(VARIABLE) WRAP4(__VA_ARGS__)
41 | #define WRAP6(VARIABLE, ...) WRAP1(VARIABLE) WRAP5(__VA_ARGS__)
42 | #define WRAP7(VARIABLE, ...) WRAP1(VARIABLE) WRAP6(__VA_ARGS__)
43 | #define WRAP8(VARIABLE, ...) WRAP1(VARIABLE) WRAP7(__VA_ARGS__)
44 | #define WRAP9(VARIABLE, ...) WRAP1(VARIABLE) WRAP8(__VA_ARGS__)
45 | #define WRAP10(VARIABLE, ...) WRAP1(VARIABLE) WRAP9(__VA_ARGS__)
46 | #define WRAP11(VARIABLE, ...) WRAP1(VARIABLE) WRAP10(__VA_ARGS__)
47 | #define WRAP12(VARIABLE, ...) WRAP1(VARIABLE) WRAP11(__VA_ARGS__)
48 | #define WRAP13(VARIABLE, ...) WRAP1(VARIABLE) WRAP12(__VA_ARGS__)
49 | #define WRAP14(VARIABLE, ...) WRAP1(VARIABLE) WRAP13(__VA_ARGS__)
50 | #define WRAP15(VARIABLE, ...) WRAP1(VARIABLE) WRAP14(__VA_ARGS__)
51 | #define WRAP16(VARIABLE, ...) WRAP1(VARIABLE) WRAP15(__VA_ARGS__)
52 | #define WRAP17(VARIABLE, ...) WRAP1(VARIABLE) WRAP16(__VA_ARGS__)
53 | #define WRAP18(VARIABLE, ...) WRAP1(VARIABLE) WRAP17(__VA_ARGS__)
54 | #define WRAP19(VARIABLE, ...) WRAP1(VARIABLE) WRAP18(__VA_ARGS__)
55 | #define WRAP20(VARIABLE, ...) WRAP1(VARIABLE) WRAP19(__VA_ARGS__)
56 |
57 | #define CAT(A, B) A ## B
58 | #define INVOKE(MACRO, NUMBER, ...) CAT(MACRO, NUMBER)(__VA_ARGS__)
59 | #define WRAP_LIST(...) INVOKE(WRAP, VA_NUM_ARGS(__VA_ARGS__), __VA_ARGS__)
60 |
61 | // Gets the a class with the name CLASS
62 | #define ZKClass(CLASS) objc_getClass(#CLASS)
63 |
64 | // returns the value of an instance variable.
65 | #if !__has_feature(objc_arc)
66 | #define ZKHookIvar(OBJECT, TYPE, NAME) (*(TYPE *)ZKIvarPointer(OBJECT, NAME))
67 | #else
68 | #define ZKHookIvar(OBJECT, TYPE, NAME) \
69 | _Pragma("clang diagnostic push") \
70 | _Pragma("clang diagnostic ignored \"-Wignored-attributes\"") \
71 | (*(__unsafe_unretained TYPE *)ZKIvarPointer(OBJECT, NAME)) \
72 | _Pragma("clang diagnostic pop")
73 | #endif
74 | // returns the original implementation of the swizzled function or null or not found
75 | #define ZKOrig(TYPE, ...) ((TYPE (*)(id, SEL WRAP_LIST(__VA_ARGS__)))(ZKOriginalImplementation(self, _cmd, __PRETTY_FUNCTION__)))(self, _cmd, ##__VA_ARGS__)
76 |
77 | // returns the original implementation of the superclass of the object swizzled
78 | #define ZKSuper(TYPE, ...) ((TYPE (*)(id, SEL WRAP_LIST(__VA_ARGS__)))(ZKSuperImplementation(self, _cmd, __PRETTY_FUNCTION__)))(self, _cmd, ##__VA_ARGS__)
79 |
80 | #define _ZKSwizzleInterfaceConditionally(CLASS_NAME, TARGET_CLASS, SUPERCLASS, GROUP, IMMEDIATELY) \
81 | @interface _$ ## CLASS_NAME : SUPERCLASS @end \
82 | @implementation _$ ## CLASS_NAME \
83 | + (void)initialize {} \
84 | @end \
85 | @interface CLASS_NAME : _$ ## CLASS_NAME @end \
86 | @implementation CLASS_NAME (ZKSWIZZLE) \
87 | + (void)load { \
88 | _$ZKRegisterInterface(self, #GROUP);\
89 | if (IMMEDIATELY) { \
90 | [self _ZK_unconditionallySwizzle]; \
91 | } \
92 | } \
93 | + (void)_ZK_unconditionallySwizzle { \
94 | ZKSwizzle(CLASS_NAME, TARGET_CLASS); \
95 | } \
96 | @end
97 |
98 | // Bootstraps your swizzling class so that it requires no setup
99 | // outside of this macro call
100 | // If you override +load you must call ZKSwizzle(CLASS_NAME, TARGET_CLASS)
101 | // yourself, otherwise the swizzling would not take place
102 | #define ZKSwizzleInterface(CLASS_NAME, TARGET_CLASS, SUPERCLASS) \
103 | _ZKSwizzleInterfaceConditionally(CLASS_NAME, TARGET_CLASS, SUPERCLASS, ZK_UNGROUPED, YES)
104 |
105 | // Same as ZKSwizzleInterface, except
106 | #define ZKSwizzleInterfaceGroup(CLASS_NAME, TARGET_CLASS, SUPER_CLASS, GROUP) \
107 | _ZKSwizzleInterfaceConditionally(CLASS_NAME, TARGET_CLASS, SUPER_CLASS, GROUP, NO)
108 |
109 | __BEGIN_DECLS
110 |
111 | // Make sure to cast this before you use it
112 | typedef id (*ZKIMP)(id, SEL, ...);
113 |
114 | // returns a pointer to the instance variable "name" on the object
115 | void *ZKIvarPointer(id self, const char *name);
116 | // returns the original implementation of a method with selector "sel" of an object hooked by the methods below
117 | ZKIMP ZKOriginalImplementation(id self, SEL sel, const char *info);
118 | // returns the implementation of a method with selector "sel" of the superclass of object
119 | ZKIMP ZKSuperImplementation(id object, SEL sel, const char *info);
120 |
121 | // hooks all the implemented methods of source with destination
122 | // adds any methods that arent implemented on destination to destination that are implemented in source
123 | #define ZKSwizzle(src, dst) _ZKSwizzle(ZKClass(src), ZKClass(dst))
124 | BOOL _ZKSwizzle(Class src, Class dest);
125 |
126 | #define ZKSwizzleGroup(NAME) _ZKSwizzleGroup(#NAME)
127 | void _$ZKRegisterInterface(Class cls, const char *groupName);
128 | BOOL _ZKSwizzleGroup(const char *groupName);
129 |
130 | // Calls above method with the superclass of source for desination
131 | #define ZKSwizzleClass(src) _ZKSwizzleClass(ZKClass(src))
132 | BOOL _ZKSwizzleClass(Class cls);
133 |
134 | __END_DECLS
135 | #endif
136 |
137 |
--------------------------------------------------------------------------------
/WriteReceipt.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | D388E75D2093868300441C31 /* ZKSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = D388E7592093868300441C31 /* ZKSwizzle.m */; };
11 | D388E75E2093868300441C31 /* WriteReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = D388E75B2093868300441C31 /* WriteReceipt.m */; };
12 | D3E945B7209E016D00B67560 /* Icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = D3E945B6209E016D00B67560 /* Icon.icns */; };
13 | /* End PBXBuildFile section */
14 |
15 | /* Begin PBXFileReference section */
16 | D388E74F2093866B00441C31 /* WriteReceipt.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WriteReceipt.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
17 | D388E7522093866B00441C31 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
18 | D388E7592093868300441C31 /* ZKSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZKSwizzle.m; sourceTree = ""; };
19 | D388E75A2093868300441C31 /* ZKSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZKSwizzle.h; sourceTree = ""; };
20 | D388E75B2093868300441C31 /* WriteReceipt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WriteReceipt.m; sourceTree = ""; };
21 | D3E945B6209E016D00B67560 /* Icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Icon.icns; sourceTree = SOURCE_ROOT; };
22 | /* End PBXFileReference section */
23 |
24 | /* Begin PBXFrameworksBuildPhase section */
25 | D388E74C2093866B00441C31 /* Frameworks */ = {
26 | isa = PBXFrameworksBuildPhase;
27 | buildActionMask = 2147483647;
28 | files = (
29 | );
30 | runOnlyForDeploymentPostprocessing = 0;
31 | };
32 | /* End PBXFrameworksBuildPhase section */
33 |
34 | /* Begin PBXGroup section */
35 | D388E7462093866B00441C31 = {
36 | isa = PBXGroup;
37 | children = (
38 | D388E7512093866B00441C31 /* WriteReceipt */,
39 | D388E7502093866B00441C31 /* Products */,
40 | );
41 | sourceTree = "";
42 | };
43 | D388E7502093866B00441C31 /* Products */ = {
44 | isa = PBXGroup;
45 | children = (
46 | D388E74F2093866B00441C31 /* WriteReceipt.bundle */,
47 | );
48 | name = Products;
49 | sourceTree = "";
50 | };
51 | D388E7512093866B00441C31 /* WriteReceipt */ = {
52 | isa = PBXGroup;
53 | children = (
54 | D388E7582093868300441C31 /* ZKSwizzle */,
55 | D388E75B2093868300441C31 /* WriteReceipt.m */,
56 | D3E945B6209E016D00B67560 /* Icon.icns */,
57 | D388E7522093866B00441C31 /* Info.plist */,
58 | );
59 | path = WriteReceipt;
60 | sourceTree = "";
61 | };
62 | D388E7582093868300441C31 /* ZKSwizzle */ = {
63 | isa = PBXGroup;
64 | children = (
65 | D388E75A2093868300441C31 /* ZKSwizzle.h */,
66 | D388E7592093868300441C31 /* ZKSwizzle.m */,
67 | );
68 | path = ZKSwizzle;
69 | sourceTree = "";
70 | };
71 | /* End PBXGroup section */
72 |
73 | /* Begin PBXNativeTarget section */
74 | D388E74E2093866B00441C31 /* WriteReceipt */ = {
75 | isa = PBXNativeTarget;
76 | buildConfigurationList = D388E7552093866B00441C31 /* Build configuration list for PBXNativeTarget "WriteReceipt" */;
77 | buildPhases = (
78 | D388E74B2093866B00441C31 /* Sources */,
79 | D388E74C2093866B00441C31 /* Frameworks */,
80 | D388E74D2093866B00441C31 /* Resources */,
81 | );
82 | buildRules = (
83 | );
84 | dependencies = (
85 | );
86 | name = WriteReceipt;
87 | productName = WriteReceipt;
88 | productReference = D388E74F2093866B00441C31 /* WriteReceipt.bundle */;
89 | productType = "com.apple.product-type.bundle";
90 | };
91 | /* End PBXNativeTarget section */
92 |
93 | /* Begin PBXProject section */
94 | D388E7472093866B00441C31 /* Project object */ = {
95 | isa = PBXProject;
96 | attributes = {
97 | LastUpgradeCheck = 0930;
98 | ORGANIZATIONNAME = Shishkabibal;
99 | TargetAttributes = {
100 | D388E74E2093866B00441C31 = {
101 | CreatedOnToolsVersion = 9.3;
102 | };
103 | };
104 | };
105 | buildConfigurationList = D388E74A2093866B00441C31 /* Build configuration list for PBXProject "WriteReceipt" */;
106 | compatibilityVersion = "Xcode 9.3";
107 | developmentRegion = en;
108 | hasScannedForEncodings = 0;
109 | knownRegions = (
110 | en,
111 | );
112 | mainGroup = D388E7462093866B00441C31;
113 | productRefGroup = D388E7502093866B00441C31 /* Products */;
114 | projectDirPath = "";
115 | projectRoot = "";
116 | targets = (
117 | D388E74E2093866B00441C31 /* WriteReceipt */,
118 | );
119 | };
120 | /* End PBXProject section */
121 |
122 | /* Begin PBXResourcesBuildPhase section */
123 | D388E74D2093866B00441C31 /* Resources */ = {
124 | isa = PBXResourcesBuildPhase;
125 | buildActionMask = 2147483647;
126 | files = (
127 | D3E945B7209E016D00B67560 /* Icon.icns in Resources */,
128 | );
129 | runOnlyForDeploymentPostprocessing = 0;
130 | };
131 | /* End PBXResourcesBuildPhase section */
132 |
133 | /* Begin PBXSourcesBuildPhase section */
134 | D388E74B2093866B00441C31 /* Sources */ = {
135 | isa = PBXSourcesBuildPhase;
136 | buildActionMask = 2147483647;
137 | files = (
138 | D388E75E2093868300441C31 /* WriteReceipt.m in Sources */,
139 | D388E75D2093868300441C31 /* ZKSwizzle.m in Sources */,
140 | );
141 | runOnlyForDeploymentPostprocessing = 0;
142 | };
143 | /* End PBXSourcesBuildPhase section */
144 |
145 | /* Begin XCBuildConfiguration section */
146 | D388E7532093866B00441C31 /* Debug */ = {
147 | isa = XCBuildConfiguration;
148 | buildSettings = {
149 | ALWAYS_SEARCH_USER_PATHS = NO;
150 | CLANG_ANALYZER_NONNULL = YES;
151 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
152 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
153 | CLANG_CXX_LIBRARY = "libc++";
154 | CLANG_ENABLE_MODULES = YES;
155 | CLANG_ENABLE_OBJC_ARC = YES;
156 | CLANG_ENABLE_OBJC_WEAK = YES;
157 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
158 | CLANG_WARN_BOOL_CONVERSION = YES;
159 | CLANG_WARN_COMMA = YES;
160 | CLANG_WARN_CONSTANT_CONVERSION = YES;
161 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
162 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
163 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
164 | CLANG_WARN_EMPTY_BODY = YES;
165 | CLANG_WARN_ENUM_CONVERSION = YES;
166 | CLANG_WARN_INFINITE_RECURSION = YES;
167 | CLANG_WARN_INT_CONVERSION = YES;
168 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
169 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
170 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
171 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
172 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
173 | CLANG_WARN_STRICT_PROTOTYPES = YES;
174 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
175 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
176 | CLANG_WARN_UNREACHABLE_CODE = YES;
177 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
178 | CODE_SIGN_IDENTITY = "-";
179 | COPY_PHASE_STRIP = NO;
180 | DEBUG_INFORMATION_FORMAT = dwarf;
181 | DEPLOYMENT_POSTPROCESSING = NO;
182 | ENABLE_STRICT_OBJC_MSGSEND = YES;
183 | ENABLE_TESTABILITY = YES;
184 | GCC_C_LANGUAGE_STANDARD = gnu11;
185 | GCC_DYNAMIC_NO_PIC = NO;
186 | GCC_NO_COMMON_BLOCKS = YES;
187 | GCC_OPTIMIZATION_LEVEL = 0;
188 | GCC_PREPROCESSOR_DEFINITIONS = (
189 | "DEBUG=1",
190 | "$(inherited)",
191 | );
192 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
193 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
194 | GCC_WARN_UNDECLARED_SELECTOR = YES;
195 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
196 | GCC_WARN_UNUSED_FUNCTION = YES;
197 | GCC_WARN_UNUSED_VARIABLE = YES;
198 | MACOSX_DEPLOYMENT_TARGET = 10.13;
199 | MTL_ENABLE_DEBUG_INFO = YES;
200 | ONLY_ACTIVE_ARCH = YES;
201 | SDKROOT = macosx;
202 | };
203 | name = Debug;
204 | };
205 | D388E7542093866B00441C31 /* Release */ = {
206 | isa = XCBuildConfiguration;
207 | buildSettings = {
208 | ALWAYS_SEARCH_USER_PATHS = NO;
209 | CLANG_ANALYZER_NONNULL = YES;
210 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
211 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
212 | CLANG_CXX_LIBRARY = "libc++";
213 | CLANG_ENABLE_MODULES = YES;
214 | CLANG_ENABLE_OBJC_ARC = YES;
215 | CLANG_ENABLE_OBJC_WEAK = YES;
216 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
217 | CLANG_WARN_BOOL_CONVERSION = YES;
218 | CLANG_WARN_COMMA = YES;
219 | CLANG_WARN_CONSTANT_CONVERSION = YES;
220 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
221 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
222 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
223 | CLANG_WARN_EMPTY_BODY = YES;
224 | CLANG_WARN_ENUM_CONVERSION = YES;
225 | CLANG_WARN_INFINITE_RECURSION = YES;
226 | CLANG_WARN_INT_CONVERSION = YES;
227 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
228 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
229 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
230 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
231 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
232 | CLANG_WARN_STRICT_PROTOTYPES = YES;
233 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
234 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
235 | CLANG_WARN_UNREACHABLE_CODE = YES;
236 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
237 | CODE_SIGN_IDENTITY = "-";
238 | COPY_PHASE_STRIP = NO;
239 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
240 | DEPLOYMENT_POSTPROCESSING = NO;
241 | ENABLE_NS_ASSERTIONS = NO;
242 | ENABLE_STRICT_OBJC_MSGSEND = YES;
243 | GCC_C_LANGUAGE_STANDARD = gnu11;
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 | MACOSX_DEPLOYMENT_TARGET = 10.13;
252 | MTL_ENABLE_DEBUG_INFO = NO;
253 | SDKROOT = macosx;
254 | };
255 | name = Release;
256 | };
257 | D388E7562093866B00441C31 /* Debug */ = {
258 | isa = XCBuildConfiguration;
259 | buildSettings = {
260 | CODE_SIGN_STYLE = Automatic;
261 | COMBINE_HIDPI_IMAGES = YES;
262 | DEPLOYMENT_POSTPROCESSING = NO;
263 | INFOPLIST_FILE = WriteReceipt/Info.plist;
264 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Application Support/SIMBL/Plugins";
265 | PRODUCT_BUNDLE_IDENTIFIER = com.shishkabibal.WriteReceipt;
266 | PRODUCT_NAME = "$(TARGET_NAME)";
267 | SKIP_INSTALL = YES;
268 | WRAPPER_EXTENSION = bundle;
269 | };
270 | name = Debug;
271 | };
272 | D388E7572093866B00441C31 /* Release */ = {
273 | isa = XCBuildConfiguration;
274 | buildSettings = {
275 | CODE_SIGN_STYLE = Automatic;
276 | COMBINE_HIDPI_IMAGES = YES;
277 | DEPLOYMENT_POSTPROCESSING = NO;
278 | INFOPLIST_FILE = WriteReceipt/Info.plist;
279 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Application Support/SIMBL/Plugins";
280 | PRODUCT_BUNDLE_IDENTIFIER = com.shishkabibal.WriteReceipt;
281 | PRODUCT_NAME = "$(TARGET_NAME)";
282 | SKIP_INSTALL = YES;
283 | WRAPPER_EXTENSION = bundle;
284 | };
285 | name = Release;
286 | };
287 | /* End XCBuildConfiguration section */
288 |
289 | /* Begin XCConfigurationList section */
290 | D388E74A2093866B00441C31 /* Build configuration list for PBXProject "WriteReceipt" */ = {
291 | isa = XCConfigurationList;
292 | buildConfigurations = (
293 | D388E7532093866B00441C31 /* Debug */,
294 | D388E7542093866B00441C31 /* Release */,
295 | );
296 | defaultConfigurationIsVisible = 0;
297 | defaultConfigurationName = Release;
298 | };
299 | D388E7552093866B00441C31 /* Build configuration list for PBXNativeTarget "WriteReceipt" */ = {
300 | isa = XCConfigurationList;
301 | buildConfigurations = (
302 | D388E7562093866B00441C31 /* Debug */,
303 | D388E7572093866B00441C31 /* Release */,
304 | );
305 | defaultConfigurationIsVisible = 0;
306 | defaultConfigurationName = Release;
307 | };
308 | /* End XCConfigurationList section */
309 | };
310 | rootObject = D388E7472093866B00441C31 /* Project object */;
311 | }
312 |
--------------------------------------------------------------------------------
/WriteReceipt/ZKSwizzle/ZKSwizzle.m:
--------------------------------------------------------------------------------
1 | //
2 | // ZKSwizzle.m
3 | // ZKSwizzle
4 | //
5 | // Created by Alexander S Zielenski on 7/24/14.
6 | // Copyright (c) 2014 Alexander S Zielenski. All rights reserved.
7 | //
8 |
9 | #import "ZKSwizzle.h"
10 | static NSMutableDictionary *classTable;
11 |
12 | @interface NSObject (ZKSwizzle)
13 | + (void)_ZK_unconditionallySwizzle;
14 | @end
15 |
16 | void *ZKIvarPointer(id self, const char *name) {
17 | Ivar ivar = class_getInstanceVariable(object_getClass(self), name);
18 | return ivar == NULL ? NULL : (__bridge void *)self + ivar_getOffset(ivar);
19 | }
20 |
21 | static SEL destinationSelectorForSelector(SEL cmd, Class dst) {
22 | return NSSelectorFromString([@"_ZK_old_" stringByAppendingFormat:@"%s_%@", class_getName(dst), NSStringFromSelector(cmd)]);
23 | }
24 |
25 | static Class classFromInfo(const char *info) {
26 | NSUInteger bracket_index = -1;
27 | for (NSUInteger i = 0; i < strlen(info); i++) {
28 | if (info[i] == '[') {
29 | bracket_index = i;
30 | }
31 | }
32 | bracket_index++;
33 |
34 | if (bracket_index == -1) {
35 | [NSException raise:@"Failed to parse info" format:@"Couldn't find swizzle class for info: %s", info];
36 | return NULL;
37 | }
38 |
39 | char after_bracket[255];
40 | memcpy(after_bracket, &info[bracket_index], strlen(info) - bracket_index - 1);
41 |
42 | for (NSUInteger i = 0; i < strlen(info); i++) {
43 | if (after_bracket[i] == ' ') {
44 | after_bracket[i] = '\0';
45 | }
46 | }
47 |
48 | return objc_getClass(after_bracket);
49 | }
50 |
51 | // takes __PRETTY_FUNCTION__ for info which gives the name of the swizzle source class
52 | /*
53 |
54 | We add the original implementation onto the swizzle class
55 | On ZKOrig, we use __PRETTY_FUNCTION__ to get the name of the swizzle class
56 | Then we get the implementation of that selector on the swizzle class
57 | Then we call it directly, passing in the correct selector and self
58 |
59 | */
60 | ZKIMP ZKOriginalImplementation(id self, SEL sel, const char *info) {
61 | if (sel == NULL || self == NULL || info == NULL) {
62 | [NSException raise:@"Invalid Arguments" format:@"One of self: %@, self: %@, or info: %s is NULL", self, NSStringFromSelector(sel), info];
63 | return NULL;
64 | }
65 |
66 | Class cls = classFromInfo(info);
67 | Class dest = object_getClass(self);
68 |
69 | if (cls == NULL || dest == NULL) {
70 | [NSException raise:@"Failed obtain class pair" format:@"src: %@ | dst: %@ | sel: %@", NSStringFromClass(cls), NSStringFromClass(dest), NSStringFromSelector(sel)];
71 | return NULL;
72 | }
73 |
74 | SEL destSel = destinationSelectorForSelector(sel, cls);
75 |
76 | Method method = class_getInstanceMethod(dest, destSel);
77 |
78 | if (method == NULL) {
79 | [NSException raise:@"Failed to retrieve method" format:@"Got null for the source class %@ with selector %@ (%@)", NSStringFromClass(cls), NSStringFromSelector(sel), NSStringFromSelector(destSel)];
80 | return NULL;
81 | }
82 |
83 | ZKIMP implementation = (ZKIMP)method_getImplementation(method);
84 | if (implementation == NULL) {
85 | [NSException raise:@"Failed to get implementation" format:@"The objective-c runtime could not get the implementation for %@ on the class %@. There is no fix for this", NSStringFromClass(cls), NSStringFromSelector(sel)];
86 | }
87 |
88 | return implementation;
89 | }
90 |
91 | ZKIMP ZKSuperImplementation(id object, SEL sel, const char *info) {
92 | if (sel == NULL || object == NULL) {
93 | [NSException raise:@"Invalid Arguments" format:@"One of self: %@, self: %@ is NULL", object, NSStringFromSelector(sel)];
94 | return NULL;
95 | }
96 |
97 | Class cls = object_getClass(object);
98 | if (cls == NULL) {
99 | [NSException raise:@"Invalid Argument" format:@"Could not obtain class for the passed object"];
100 | return NULL;
101 | }
102 |
103 | // Two scenarios:
104 | // 1.) The superclass was not swizzled, no problem
105 | // 2.) The superclass was swizzled, problem
106 |
107 | // We want to return the swizzled class's superclass implementation
108 | // If this is a subclass of such a class, we want two behaviors:
109 | // a.) If this imp was also swizzled, no problem, return the superclass's swizzled imp
110 | // b.) This imp was not swizzled, return the class that was originally swizzled's superclass's imp
111 | Class sourceClass = classFromInfo(info);
112 | if (sourceClass != NULL) {
113 | BOOL isClassMethod = class_isMetaClass(cls);
114 | // This was called from a swizzled method, get the class it was swizzled with
115 | NSString *className = classTable[NSStringFromClass(sourceClass)];
116 | if (className != NULL) {
117 | cls = NSClassFromString(className);
118 | // make sure we get a class method if we asked for one
119 | if (isClassMethod) {
120 | cls = object_getClass(cls);
121 | }
122 | }
123 | }
124 |
125 | cls = class_getSuperclass(cls);
126 |
127 | // This is a root class, it has no super class
128 | if (cls == NULL) {
129 | [NSException raise:@"Invalid Argument" format:@"Could not obtain superclass for the passed object"];
130 | return NULL;
131 | }
132 |
133 | Method method = class_getInstanceMethod(cls, sel);
134 | if (method == NULL) {
135 | [NSException raise:@"Failed to retrieve method" format:@"We could not find the super implementation for the class %@ and selector %@, are you sure it exists?", NSStringFromClass(cls), NSStringFromSelector(sel)];
136 | return NULL;
137 | }
138 |
139 | ZKIMP implementation = (ZKIMP)method_getImplementation(method);
140 | if (implementation == NULL) {
141 | [NSException raise:@"Failed to get implementation" format:@"The objective-c runtime could not get the implementation for %@ on the class %@. There is no fix for this", NSStringFromClass(cls), NSStringFromSelector(sel)];
142 | }
143 |
144 | return implementation;
145 | }
146 |
147 | static BOOL enumerateMethods(Class, Class);
148 | BOOL _ZKSwizzle(Class src, Class dest) {
149 | if (dest == NULL)
150 | return NO;
151 |
152 | NSString *destName = NSStringFromClass(dest);
153 | if (!destName) {
154 | return NO;
155 | }
156 |
157 | if (!classTable) {
158 | classTable = [[NSMutableDictionary alloc] init];
159 | }
160 |
161 | if ([classTable objectForKey:NSStringFromClass(src)]) {
162 | [NSException raise:@"Invalid Argument"
163 | format:@"This source class (%@) was already swizzled with another, (%@)", NSStringFromClass(src), classTable[NSStringFromClass(src)]];
164 | return NO;
165 | }
166 |
167 | BOOL success = enumerateMethods(dest, src);
168 | // The above method only gets instance methods. Do the same method for the metaclass of the class
169 | success &= enumerateMethods(object_getClass(dest), object_getClass(src));
170 |
171 | [classTable setObject:destName forKey:NSStringFromClass(src)];
172 | return success;
173 | }
174 |
175 | BOOL _ZKSwizzleClass(Class cls) {
176 | return _ZKSwizzle(cls, [cls superclass]);
177 | }
178 |
179 | static BOOL enumerateMethods(Class destination, Class source) {
180 | #if OBJC_API_VERSION < 2
181 | [NSException raise:@"Unsupported feature" format:@"ZKSwizzle is only available in objc 2.0"];
182 | return NO;
183 |
184 | #else
185 |
186 | unsigned int methodCount;
187 | Method *methodList = class_copyMethodList(source, &methodCount);
188 | BOOL success = YES;
189 | for (int i = 0; i < methodCount; i++) {
190 | Method method = methodList[i];
191 | SEL selector = method_getName(method);
192 | NSString *methodName = NSStringFromSelector(selector);
193 |
194 | // Don't do anything with the unconditional swizzle
195 | if (sel_isEqual(selector, @selector(_ZK_unconditionallySwizzle))) {
196 | continue;
197 | }
198 |
199 | // We only swizzle methods that are implemented
200 | if (class_respondsToSelector(destination, selector)) {
201 | Method originalMethod = class_getInstanceMethod(destination, selector);
202 |
203 | const char *originalType = method_getTypeEncoding(originalMethod);
204 | const char *newType = method_getTypeEncoding(method);
205 | if (strcmp(originalType, newType) != 0) {
206 | NSLog(@"ZKSwizzle: incompatible type encoding for %@. (expected %s, got %s)", methodName, originalType, newType);
207 | // Incompatible type encoding
208 | success = NO;
209 | continue;
210 | }
211 |
212 | // We are re-adding the destination selector because it could be on a superclass and not on the class itself. This method could fail
213 | class_addMethod(destination, selector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
214 |
215 | SEL destSel = destinationSelectorForSelector(selector, source);
216 | if (!class_addMethod(destination, destSel, method_getImplementation(method), method_getTypeEncoding(originalMethod))) {
217 | NSLog(@"ZKSwizzle: failed to add method %@ onto class %@ with selector %@", NSStringFromSelector(selector), NSStringFromClass(source), NSStringFromSelector(destSel));
218 | success = NO;
219 | continue;
220 | }
221 |
222 | method_exchangeImplementations(class_getInstanceMethod(destination, selector), class_getInstanceMethod(destination, destSel));
223 | } else {
224 | // Add any extra methods to the class but don't swizzle them
225 | success &= class_addMethod(destination, selector, method_getImplementation(method), method_getTypeEncoding(method));
226 | }
227 | }
228 |
229 | unsigned int propertyCount;
230 | objc_property_t *propertyList = class_copyPropertyList(source, &propertyCount);
231 | for (int i = 0; i < propertyCount; i++) {
232 | objc_property_t property = propertyList[i];
233 | const char *name = property_getName(property);
234 | unsigned int attributeCount;
235 | objc_property_attribute_t *attributes = property_copyAttributeList(property, &attributeCount);
236 |
237 | if (class_getProperty(destination, name) == NULL) {
238 | class_addProperty(destination, name, attributes, attributeCount);
239 | } else {
240 | class_replaceProperty(destination, name, attributes, attributeCount);
241 | }
242 |
243 | free(attributes);
244 | }
245 |
246 | free(propertyList);
247 | free(methodList);
248 | return success;
249 | #endif
250 | }
251 |
252 | // Options were to use a group class and traverse its subclasses
253 | // or to create a groups dictionary
254 | // This works because +load on NSObject is called before attribute((constructor))
255 | static NSMutableDictionary *groups = nil;
256 | void _$ZKRegisterInterface(Class cls, const char *groupName) {
257 | if (!groups)
258 | groups = [NSMutableDictionary dictionary];
259 |
260 | NSString *groupString = @(groupName);
261 | NSMutableArray *groupList = groups[groupString];
262 | if (!groupList) {
263 | groupList = [NSMutableArray array];
264 | groups[groupString] = groupList;
265 | }
266 |
267 | [groupList addObject:NSStringFromClass(cls)];
268 | }
269 |
270 | BOOL _ZKSwizzleGroup(const char *groupName) {
271 | NSArray *groupList = groups[@(groupName)];
272 | if (!groupList) {
273 | [NSException raise:@"Invalid Argument" format:@"ZKSwizzle: There is no group by the name of %s", groupName];
274 | return NO;
275 | }
276 |
277 | BOOL success = YES;
278 | for (NSString *className in groupList) {
279 | Class cls = NSClassFromString(className);
280 | if (cls == NULL)
281 | continue;
282 |
283 | if (class_respondsToSelector(object_getClass(cls), @selector(_ZK_unconditionallySwizzle))) {
284 | [cls _ZK_unconditionallySwizzle];
285 | } else {
286 | success = NO;
287 | }
288 | }
289 |
290 | return success;
291 | }
292 |
--------------------------------------------------------------------------------