GULObjectSwizzler.m 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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/GULObjectSwizzler.h>
  15. #import <objc/runtime.h>
  16. #import "GoogleUtilities/ISASwizzler/Private/GULSwizzledObject.h"
  17. @implementation GULObjectSwizzler {
  18. // The swizzled object.
  19. __weak id _swizzledObject;
  20. // The original class of the object.
  21. Class _originalClass;
  22. // The dynamically generated subclass of _originalClass.
  23. Class _generatedClass;
  24. }
  25. #pragma mark - Class methods
  26. + (void)setAssociatedObject:(id)object
  27. key:(NSString *)key
  28. value:(nullable id)value
  29. association:(GUL_ASSOCIATION)association {
  30. objc_AssociationPolicy resolvedAssociation;
  31. switch (association) {
  32. case GUL_ASSOCIATION_ASSIGN:
  33. resolvedAssociation = OBJC_ASSOCIATION_ASSIGN;
  34. break;
  35. case GUL_ASSOCIATION_RETAIN_NONATOMIC:
  36. resolvedAssociation = OBJC_ASSOCIATION_RETAIN_NONATOMIC;
  37. break;
  38. case GUL_ASSOCIATION_COPY_NONATOMIC:
  39. resolvedAssociation = OBJC_ASSOCIATION_COPY_NONATOMIC;
  40. break;
  41. case GUL_ASSOCIATION_RETAIN:
  42. resolvedAssociation = OBJC_ASSOCIATION_RETAIN;
  43. break;
  44. case GUL_ASSOCIATION_COPY:
  45. resolvedAssociation = OBJC_ASSOCIATION_COPY;
  46. break;
  47. default:
  48. break;
  49. }
  50. objc_setAssociatedObject(object, key.UTF8String, value, resolvedAssociation);
  51. }
  52. + (nullable id)getAssociatedObject:(id)object key:(NSString *)key {
  53. return objc_getAssociatedObject(object, key.UTF8String);
  54. }
  55. #pragma mark - Instance methods
  56. /** Instantiates an instance of this class.
  57. *
  58. * @param object The object to swizzle.
  59. * @return An instance of this class.
  60. */
  61. - (instancetype)initWithObject:(id)object {
  62. self = [super init];
  63. if (self) {
  64. __strong id swizzledObject = object;
  65. if (swizzledObject) {
  66. _swizzledObject = swizzledObject;
  67. _originalClass = object_getClass(object);
  68. NSString *newClassName = [NSString stringWithFormat:@"fir_%@_%@", [[NSUUID UUID] UUIDString],
  69. NSStringFromClass(_originalClass)];
  70. _generatedClass = objc_allocateClassPair(_originalClass, newClassName.UTF8String, 0);
  71. NSAssert(_generatedClass, @"Wasn't able to allocate the class pair.");
  72. } else {
  73. return nil;
  74. }
  75. }
  76. return self;
  77. }
  78. - (void)copySelector:(SEL)selector fromClass:(Class)aClass isClassSelector:(BOOL)isClassSelector {
  79. NSAssert(_generatedClass, @"This object has already been unswizzled.");
  80. Method method = isClassSelector ? class_getClassMethod(aClass, selector)
  81. : class_getInstanceMethod(aClass, selector);
  82. Class targetClass = isClassSelector ? object_getClass(_generatedClass) : _generatedClass;
  83. IMP implementation = method_getImplementation(method);
  84. const char *typeEncoding = method_getTypeEncoding(method);
  85. BOOL success __unused = class_addMethod(targetClass, selector, implementation, typeEncoding);
  86. NSAssert(success, @"Unable to add selector %@ to class %@", NSStringFromSelector(selector),
  87. NSStringFromClass(targetClass));
  88. }
  89. - (void)setAssociatedObjectWithKey:(NSString *)key
  90. value:(id)value
  91. association:(GUL_ASSOCIATION)association {
  92. __strong id swizzledObject = _swizzledObject;
  93. if (swizzledObject) {
  94. [[self class] setAssociatedObject:swizzledObject key:key value:value association:association];
  95. }
  96. }
  97. - (nullable id)getAssociatedObjectForKey:(NSString *)key {
  98. __strong id swizzledObject = _swizzledObject;
  99. if (swizzledObject) {
  100. return [[self class] getAssociatedObject:swizzledObject key:key];
  101. }
  102. return nil;
  103. }
  104. - (void)swizzle {
  105. __strong id swizzledObject = _swizzledObject;
  106. if (swizzledObject) {
  107. [GULObjectSwizzler setAssociatedObject:swizzledObject
  108. key:kSwizzlerAssociatedObjectKey
  109. value:self
  110. association:GUL_ASSOCIATION_RETAIN_NONATOMIC];
  111. [GULSwizzledObject copyDonorSelectorsUsingObjectSwizzler:self];
  112. NSAssert(_originalClass == object_getClass(swizzledObject),
  113. @"The original class is not the reported class now.");
  114. NSAssert(class_getInstanceSize(_originalClass) == class_getInstanceSize(_generatedClass),
  115. @"The instance size of the generated class must be equal to the original class.");
  116. objc_registerClassPair(_generatedClass);
  117. Class doubleCheckOriginalClass __unused = object_setClass(_swizzledObject, _generatedClass);
  118. NSAssert(_originalClass == doubleCheckOriginalClass,
  119. @"The original class must be the same as the class returned by object_setClass");
  120. } else {
  121. NSAssert(NO, @"You can't swizzle a nil object");
  122. }
  123. }
  124. - (void)swizzledObjectHasBeenDeallocatedWithGeneratedSubclass:(BOOL)isInstanceOfGeneratedSubclass {
  125. // If the swizzled object had a different class, it most likely indicates that the object was
  126. // ISA swizzled one more time. In this case it is not safe to dispose the generated class. We
  127. // will have to keep it to prevent a crash.
  128. // TODO: Consider adding a flag that can be set by the host application to dispose the class pair
  129. // unconditionally. It may be used by apps that use ISA Swizzling themself and are confident in
  130. // disposing their subclasses.
  131. if (isInstanceOfGeneratedSubclass) {
  132. objc_disposeClassPair(_generatedClass);
  133. }
  134. }
  135. - (BOOL)isSwizzlingProxyObject {
  136. return [_swizzledObject isProxy];
  137. }
  138. @end