GULObjectSwizzler.m 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. // Copyright 2018 Google LLC
  2. //
  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
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  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.
  14. #import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULObjectSwizzler.h"
  15. #import <objc/runtime.h>
  16. #import "GoogleUtilities/ISASwizzler/GULObjectSwizzler+Internal.h"
  17. #import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULSwizzledObject.h"
  18. @implementation GULObjectSwizzler {
  19. // The swizzled object.
  20. __weak id _swizzledObject;
  21. // The original class of the object.
  22. Class _originalClass;
  23. // The dynamically generated subclass of _originalClass.
  24. Class _generatedClass;
  25. }
  26. #pragma mark - Class methods
  27. + (void)setAssociatedObject:(id)object
  28. key:(NSString *)key
  29. value:(nullable id)value
  30. association:(GUL_ASSOCIATION)association {
  31. objc_AssociationPolicy resolvedAssociation;
  32. switch (association) {
  33. case GUL_ASSOCIATION_ASSIGN:
  34. resolvedAssociation = OBJC_ASSOCIATION_ASSIGN;
  35. break;
  36. case GUL_ASSOCIATION_RETAIN_NONATOMIC:
  37. resolvedAssociation = OBJC_ASSOCIATION_RETAIN_NONATOMIC;
  38. break;
  39. case GUL_ASSOCIATION_COPY_NONATOMIC:
  40. resolvedAssociation = OBJC_ASSOCIATION_COPY_NONATOMIC;
  41. break;
  42. case GUL_ASSOCIATION_RETAIN:
  43. resolvedAssociation = OBJC_ASSOCIATION_RETAIN;
  44. break;
  45. case GUL_ASSOCIATION_COPY:
  46. resolvedAssociation = OBJC_ASSOCIATION_COPY;
  47. break;
  48. default:
  49. break;
  50. }
  51. objc_setAssociatedObject(object, key.UTF8String, value, resolvedAssociation);
  52. }
  53. + (nullable id)getAssociatedObject:(id)object key:(NSString *)key {
  54. return objc_getAssociatedObject(object, key.UTF8String);
  55. }
  56. #pragma mark - Instance methods
  57. /** Instantiates an instance of this class.
  58. *
  59. * @param object The object to swizzle.
  60. * @return An instance of this class.
  61. */
  62. - (instancetype)initWithObject:(id)object {
  63. if (object == nil) {
  64. return nil;
  65. }
  66. GULObjectSwizzler *existingSwizzler =
  67. [[self class] getAssociatedObject:object key:kGULSwizzlerAssociatedObjectKey];
  68. if ([existingSwizzler isKindOfClass:[GULObjectSwizzler class]]) {
  69. // The object has been swizzled already, no need to swizzle again.
  70. return existingSwizzler;
  71. }
  72. self = [super init];
  73. if (self) {
  74. _swizzledObject = object;
  75. _originalClass = object_getClass(object);
  76. NSString *newClassName = [NSString stringWithFormat:@"fir_%@_%@", [[NSUUID UUID] UUIDString],
  77. NSStringFromClass(_originalClass)];
  78. _generatedClass = objc_allocateClassPair(_originalClass, newClassName.UTF8String, 0);
  79. NSAssert(_generatedClass, @"Wasn't able to allocate the class pair.");
  80. }
  81. return self;
  82. }
  83. - (void)copySelector:(SEL)selector fromClass:(Class)aClass isClassSelector:(BOOL)isClassSelector {
  84. NSAssert(_generatedClass, @"This object has already been unswizzled.");
  85. Method method = isClassSelector ? class_getClassMethod(aClass, selector)
  86. : class_getInstanceMethod(aClass, selector);
  87. Class targetClass = isClassSelector ? object_getClass(_generatedClass) : _generatedClass;
  88. IMP implementation = method_getImplementation(method);
  89. const char *typeEncoding = method_getTypeEncoding(method);
  90. class_replaceMethod(targetClass, selector, implementation, typeEncoding);
  91. }
  92. - (void)setAssociatedObjectWithKey:(NSString *)key
  93. value:(id)value
  94. association:(GUL_ASSOCIATION)association {
  95. __strong id swizzledObject = _swizzledObject;
  96. if (swizzledObject) {
  97. [[self class] setAssociatedObject:swizzledObject key:key value:value association:association];
  98. }
  99. }
  100. - (nullable id)getAssociatedObjectForKey:(NSString *)key {
  101. __strong id swizzledObject = _swizzledObject;
  102. if (swizzledObject) {
  103. return [[self class] getAssociatedObject:swizzledObject key:key];
  104. }
  105. return nil;
  106. }
  107. - (void)swizzle {
  108. __strong id swizzledObject = _swizzledObject;
  109. GULObjectSwizzler *existingSwizzler =
  110. [[self class] getAssociatedObject:swizzledObject key:kGULSwizzlerAssociatedObjectKey];
  111. if (existingSwizzler != nil) {
  112. NSAssert(existingSwizzler == self, @"The swizzled object has a different swizzler.");
  113. // The object has been swizzled already.
  114. return;
  115. }
  116. if (swizzledObject) {
  117. [GULObjectSwizzler setAssociatedObject:swizzledObject
  118. key:kGULSwizzlerAssociatedObjectKey
  119. value:self
  120. association:GUL_ASSOCIATION_RETAIN];
  121. [GULSwizzledObject copyDonorSelectorsUsingObjectSwizzler:self];
  122. NSAssert(_originalClass == object_getClass(swizzledObject),
  123. @"The original class is not the reported class now.");
  124. NSAssert(class_getInstanceSize(_originalClass) == class_getInstanceSize(_generatedClass),
  125. @"The instance size of the generated class must be equal to the original class.");
  126. objc_registerClassPair(_generatedClass);
  127. Class doubleCheckOriginalClass __unused = object_setClass(_swizzledObject, _generatedClass);
  128. NSAssert(_originalClass == doubleCheckOriginalClass,
  129. @"The original class must be the same as the class returned by object_setClass");
  130. } else {
  131. NSAssert(NO, @"You can't swizzle a nil object");
  132. }
  133. }
  134. - (void)dealloc {
  135. // When the Zombies instrument is enabled, a zombie is created for the swizzled object upon
  136. // deallocation. Because this zombie subclasses the generated class, the swizzler should not
  137. // dispose it during the swizzler's deallocation.
  138. //
  139. // There are other special cases where the generated class might be subclassed by a third-party
  140. // generated classes, for example: https://github.com/firebase/firebase-ios-sdk/issues/9083
  141. // To avoid errors in such cases, the environment variable `GULGeneratedClassDisposeDisabled` can
  142. // be set with `YES`.
  143. NSDictionary *environment = [[NSProcessInfo processInfo] environment];
  144. if ([[environment objectForKey:@"NSZombieEnabled"] boolValue] ||
  145. [[environment objectForKey:@"GULGeneratedClassDisposeDisabled"] boolValue]) {
  146. return;
  147. }
  148. if (_generatedClass) {
  149. if (_swizzledObject == nil) {
  150. // The swizzled object has been deallocated already, so the generated class can be disposed
  151. // now.
  152. objc_disposeClassPair(_generatedClass);
  153. return;
  154. }
  155. // GULSwizzledObject is retained by the swizzled object which means that the swizzled object is
  156. // being deallocated now. Let's see if we should schedule the generated class disposal.
  157. // If the swizzled object has a different class, it most likely indicates that the object was
  158. // ISA swizzled one more time. In this case it is not safe to dispose the generated class. We
  159. // will have to keep it to prevent a crash.
  160. // TODO: Consider adding a flag that can be set by the host application to dispose the class
  161. // pair unconditionally. It may be used by apps that use ISA Swizzling themself and are
  162. // confident in disposing their subclasses.
  163. BOOL isSwizzledObjectInstanceOfGeneratedClass =
  164. object_getClass(_swizzledObject) == _generatedClass;
  165. if (isSwizzledObjectInstanceOfGeneratedClass) {
  166. Class generatedClass = _generatedClass;
  167. // Schedule the generated class disposal after the swizzled object has been deallocated.
  168. dispatch_async(dispatch_get_main_queue(), ^{
  169. objc_disposeClassPair(generatedClass);
  170. });
  171. }
  172. }
  173. }
  174. - (BOOL)isSwizzlingProxyObject {
  175. return [_swizzledObject isProxy];
  176. }
  177. @end