1 // Copyright 2018 Google LLC
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 #import "Private/GULSwizzler.h"
17 #import <objc/runtime.h>
19 #import <GoogleUtilities/GULLogger.h>
20 #import "../Common/GULLoggerCodes.h"
22 #ifdef GUL_UNSWIZZLING_ENABLED
23 #import <GoogleUtilities/GULSwizzlingCache.h>
24 // We need a private method for an assert.
25 #import <GoogleUtilities/GULSwizzlingCache_Private.h>
28 static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilites/MethodSwizzler]";
30 dispatch_queue_t GetGULSwizzlingQueue() {
31 static dispatch_queue_t queue;
32 static dispatch_once_t onceToken;
33 dispatch_once(&onceToken, ^{
34 queue = dispatch_queue_create("com.google.GULSwizzler", DISPATCH_QUEUE_SERIAL);
39 @implementation GULSwizzler
41 + (void)swizzleClass:(Class)aClass
42 selector:(SEL)selector
43 isClassSelector:(BOOL)isClassSelector
44 withBlock:(nullable id)block {
45 dispatch_sync(GetGULSwizzlingQueue(), ^{
46 NSAssert(selector, @"The selector cannot be NULL");
47 NSAssert(aClass, @"The class cannot be Nil");
48 Class resolvedClass = aClass;
50 if (isClassSelector) {
51 method = class_getClassMethod(aClass, selector);
52 resolvedClass = object_getClass(aClass);
54 method = class_getInstanceMethod(aClass, selector);
56 NSAssert(method, @"You're attempting to swizzle a method that doesn't exist. (%@, %@)",
57 NSStringFromClass(resolvedClass), NSStringFromSelector(selector));
58 IMP newImp = imp_implementationWithBlock(block);
60 #ifdef GUL_UNSWIZZLING_ENABLED
61 IMP currentImp = class_getMethodImplementation(resolvedClass, selector);
62 [[GULSwizzlingCache sharedInstance] cacheCurrentIMP:currentImp
64 forClass:resolvedClass
65 withSelector:selector];
68 const char *typeEncoding = method_getTypeEncoding(method);
69 __unused IMP originalImpOfClass =
70 class_replaceMethod(resolvedClass, selector, newImp, typeEncoding);
72 #ifdef GUL_UNSWIZZLING_ENABLED
73 // If !originalImpOfClass, then the IMP came from a superclass.
74 if (originalImpOfClass) {
75 if (originalImpOfClass !=
76 [[GULSwizzlingCache sharedInstance] originalIMPOfCurrentIMP:currentImp]) {
77 GULLogWarning(kGULLoggerSwizzler, NO,
78 [NSString stringWithFormat:@"I-SWZ%06ld",
79 (long)kGULSwizzlerMessageCodeMethodSwizzling000],
80 @"Swizzling class: %@ SEL:%@ after it has been previously been swizzled.",
81 NSStringFromClass(resolvedClass), NSStringFromSelector(selector));
88 + (void)unswizzleClass:(Class)aClass selector:(SEL)selector isClassSelector:(BOOL)isClassSelector {
89 #ifdef GUL_UNSWIZZLING_ENABLED
90 dispatch_sync(GetGULSwizzlingQueue(), ^{
91 NSAssert(aClass != nil && selector != nil, @"You cannot unswizzle a nil class or selector.");
93 Class resolvedClass = aClass;
94 if (isClassSelector) {
95 resolvedClass = object_getClass(aClass);
96 method = class_getClassMethod(aClass, selector);
98 method = class_getInstanceMethod(aClass, selector);
100 NSAssert(method, @"Couldn't find the method you're unswizzling in the runtime.");
102 [[GULSwizzlingCache sharedInstance] cachedIMPForClass:resolvedClass withSelector:selector];
103 NSAssert(originalImp, @"This class/selector combination hasn't been swizzled");
104 IMP currentImp = method_setImplementation(method, originalImp);
105 BOOL didRemoveBlock = imp_removeBlock(currentImp);
106 NSAssert(didRemoveBlock, @"Wasn't able to remove the block of a swizzled IMP.");
107 [[GULSwizzlingCache sharedInstance] clearCacheForSwizzledIMP:currentImp
109 aClass:resolvedClass];
112 NSAssert(NO, @"Unswizzling is disabled.");
116 + (nullable IMP)originalImplementationForClass:(Class)aClass
117 selector:(SEL)selector
118 isClassSelector:(BOOL)isClassSelector {
119 #ifdef GUL_UNSWIZZLING_ENABLED
120 __block IMP originalImp = nil;
121 dispatch_sync(GetGULSwizzlingQueue(), ^{
122 Class resolvedClass = isClassSelector ? object_getClass(aClass) : aClass;
124 [[GULSwizzlingCache sharedInstance] cachedIMPForClass:resolvedClass withSelector:selector];
125 NSAssert(originalImp, @"The IMP for this class/selector combo doesn't exist (%@, %@).",
126 NSStringFromClass(resolvedClass), NSStringFromSelector(selector));
130 NSAssert(NO, @"Unswizzling is disabled and the original IMP is not cached.");
135 + (nullable IMP)currentImplementationForClass:(Class)aClass
136 selector:(SEL)selector
137 isClassSelector:(BOOL)isClassSelector {
138 NSAssert(selector, @"The selector cannot be NULL");
139 NSAssert(aClass, @"The class cannot be Nil");
140 if (selector == NULL || aClass == nil) {
143 __block IMP currentIMP = nil;
144 dispatch_sync(GetGULSwizzlingQueue(), ^{
146 if (isClassSelector) {
147 method = class_getClassMethod(aClass, selector);
149 method = class_getInstanceMethod(aClass, selector);
151 NSAssert(method, @"The Method for this class/selector combo doesn't exist (%@, %@).",
152 NSStringFromClass(aClass), NSStringFromSelector(selector));
156 currentIMP = method_getImplementation(method);
157 NSAssert(currentIMP, @"The IMP for this class/selector combo doesn't exist (%@, %@).",
158 NSStringFromClass(aClass), NSStringFromSelector(selector));
163 + (BOOL)selector:(SEL)selector existsInClass:(Class)aClass isClassSelector:(BOOL)isClassSelector {
164 Method method = isClassSelector ? class_getClassMethod(aClass, selector)
165 : class_getInstanceMethod(aClass, selector);
166 return method != nil;
169 + (NSArray<id> *)ivarObjectsForObject:(id)object {
170 NSMutableArray *array = [NSMutableArray array];
172 Ivar *vars = class_copyIvarList([object class], &count);
173 for (NSUInteger i = 0; i < count; i++) {
174 const char *typeEncoding = ivar_getTypeEncoding(vars[i]);
175 // Check to see if the ivar is an object.
176 if (strncmp(typeEncoding, "@", 1) == 0) {
177 id ivarObject = object_getIvar(object, vars[i]);
178 [array addObject:ivarObject];