GULSwizzlerInheritedMethodsSwizzlingTest.m 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  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. /*
  15. * GULSwizzlerInheritedMethodsSwizzlingTest.h
  16. *
  17. * This test tests the behavior when swizzling and unswizzling methods that are inherited. After the
  18. * execution of these tests, the runtime for these classes is polluted - or to be more specific
  19. * the subclasses of PollutedTestObject no longer inherit (description:) from their superclass, but
  20. * now have their own implementation (which incidentally is identical to their superclass'
  21. * implementation). For this reason, we are using a separate hierarchy of classes from the tests in
  22. * GULSwizzlerTest.m so as to not interfere with other tests as well as to prevent other tests from
  23. * interfering with this test.
  24. */
  25. #import <XCTest/XCTest.h>
  26. #import <GoogleUtilities/GULSwizzler+Unswizzle.h>
  27. #import <GoogleUtilities/GULSwizzler.h>
  28. /** This class hierarchy exists exclusively for tests that test swizzling and unswizzling methods
  29. * declared in an inheritance chain 3 levels deep. After completion of the tests the runtime for
  30. * these classes ends up being polluted and would very likely result in these classes not being
  31. * useful for any other tests, especially those involving inherited methods.
  32. */
  33. @interface PollutedTestObject : NSObject
  34. @end
  35. @implementation PollutedTestObject
  36. // This implementation of description is used to test swizzling a method declared several
  37. // inheritance chains up.
  38. - (NSString *)description {
  39. return [NSString stringWithFormat:@"Method implemented in PollutedTestObject, invoked on: %@",
  40. NSStringFromClass([self class])];
  41. }
  42. @end
  43. /** Subclass of PollutedTestObject. It inherits all of its methods from its superclass. */
  44. @interface PollutedTestObjectSubclass : PollutedTestObject
  45. @end
  46. @implementation PollutedTestObjectSubclass
  47. @end
  48. /** Subclass of PollutedTestObjectSubclass. It inherits all of its methods from its superclass two
  49. * levels up (PollutedTestObject).
  50. */
  51. @interface PollutedTestObjectSubclassSubclass : PollutedTestObjectSubclass
  52. @end
  53. @implementation PollutedTestObjectSubclassSubclass
  54. @end
  55. @interface GULSwizzlerInheritedMethodsSwizzlingTest : XCTestCase
  56. @end
  57. @implementation GULSwizzlerInheritedMethodsSwizzlingTest
  58. /** Tests swizzling and unswizzling inherited instance methods works as expected. Specifically, it
  59. * tests how unswizzling works in the case when we swizzle a method declared in the superclass,
  60. * consequently swizzle the same method in its subclass - which results in adding the swizzled IMP
  61. * to the subclass - and then unswizzle the method in the subclass, but not in the superclass.
  62. * We also test this with 3 layers of inheritance to ensure that we restore the correct original
  63. * IMP in the case of a subclass, even when the superclass method remains swizzled.
  64. */
  65. - (void)testSwizzlingAndUnswizzlingInheritedInstanceMethodsForSuperclassesWorksAsExpected {
  66. PollutedTestObject *pollutedTestObject = [[PollutedTestObject alloc] init];
  67. PollutedTestObjectSubclass *pollutedTestObjectSubclass =
  68. [[PollutedTestObjectSubclass alloc] init];
  69. PollutedTestObjectSubclassSubclass *pollutedTestObjectSubclassSubclass =
  70. [[PollutedTestObjectSubclassSubclass alloc] init];
  71. NSString *originalPollutedTestObjectDescription = [pollutedTestObject description];
  72. NSString *originalPollutedTestObjectSubclassDescription =
  73. [pollutedTestObjectSubclass description];
  74. NSString *originalPollutedTestObjectSubclassSubclassDescription =
  75. [pollutedTestObjectSubclassSubclass description];
  76. // @selector(description:) is declared by the superclass (PollutedTestObject) but its result
  77. // varies based on which class it is invoked on. This detail needs to be true for this test to be
  78. // valid, which is what we're asserting.
  79. XCTAssertNotEqualObjects(originalPollutedTestObjectDescription,
  80. originalPollutedTestObjectSubclassDescription);
  81. XCTAssertNotEqualObjects(originalPollutedTestObjectDescription,
  82. originalPollutedTestObjectSubclassSubclassDescription);
  83. XCTAssertNotEqualObjects(originalPollutedTestObjectSubclassDescription,
  84. originalPollutedTestObjectSubclassSubclassDescription);
  85. NSString *swizzledPollutedTestObjectDescription =
  86. [originalPollutedTestObjectDescription stringByAppendingString:@"SWIZZLED!"];
  87. NSString *swizzledPollutedTestObjectSubclassDescription =
  88. [originalPollutedTestObjectSubclassDescription stringByAppendingString:@"SWIZZLED!"];
  89. NSString *swizzledPollutedTestObjectSubclassSubclassDescription =
  90. [originalPollutedTestObjectSubclassSubclassDescription stringByAppendingString:@"SWIZZLED!"];
  91. NSString * (^newImplementationPollutedTestObject)(NSString *) = ^NSString *(id _self) {
  92. return swizzledPollutedTestObjectDescription;
  93. };
  94. NSString * (^newImplementationPollutedTestObjectSubclass)(NSString *) = ^NSString *(id _self) {
  95. return swizzledPollutedTestObjectSubclassDescription;
  96. };
  97. NSString * (^newImplementationPollutedTestObjectSubclassSubclass)(NSString *) =
  98. ^NSString *(id _self) {
  99. return swizzledPollutedTestObjectSubclassSubclassDescription;
  100. };
  101. SEL swizzledSelector = @selector(description);
  102. // Observe that swizzling the superclass IMP propogates to its subclasses when we haven't yet
  103. // swizzled and unsiwzzled the subclasses.
  104. [GULSwizzler swizzleClass:[PollutedTestObject class]
  105. selector:swizzledSelector
  106. isClassSelector:NO
  107. withBlock:newImplementationPollutedTestObject];
  108. XCTAssertEqualObjects([pollutedTestObject description], swizzledPollutedTestObjectDescription);
  109. XCTAssertEqualObjects([pollutedTestObjectSubclass description],
  110. swizzledPollutedTestObjectDescription);
  111. XCTAssertEqualObjects([pollutedTestObjectSubclassSubclass description],
  112. swizzledPollutedTestObjectDescription);
  113. [GULSwizzler swizzleClass:[PollutedTestObjectSubclass class]
  114. selector:swizzledSelector
  115. isClassSelector:NO
  116. withBlock:newImplementationPollutedTestObjectSubclass];
  117. XCTAssertEqualObjects([pollutedTestObjectSubclass description],
  118. swizzledPollutedTestObjectSubclassDescription);
  119. XCTAssertEqualObjects([pollutedTestObjectSubclassSubclass description],
  120. swizzledPollutedTestObjectSubclassDescription);
  121. [GULSwizzler swizzleClass:[PollutedTestObjectSubclassSubclass class]
  122. selector:swizzledSelector
  123. isClassSelector:NO
  124. withBlock:newImplementationPollutedTestObjectSubclassSubclass];
  125. XCTAssertEqualObjects([pollutedTestObjectSubclassSubclass description],
  126. swizzledPollutedTestObjectSubclassSubclassDescription);
  127. // Unswizzling a subclass invokes the original IMP declared in the super class (which is several
  128. // inheritance chains up), and mimics the behavior as if we - i.e. GULSwizzler, had never swizzled
  129. // the specific class in the first place.
  130. [GULSwizzler unswizzleClass:[PollutedTestObjectSubclassSubclass class]
  131. selector:swizzledSelector
  132. isClassSelector:NO];
  133. XCTAssertEqualObjects([pollutedTestObjectSubclassSubclass description],
  134. originalPollutedTestObjectSubclassSubclassDescription);
  135. XCTAssertEqualObjects([pollutedTestObjectSubclass description],
  136. swizzledPollutedTestObjectSubclassDescription);
  137. XCTAssertEqualObjects([pollutedTestObject description], swizzledPollutedTestObjectDescription);
  138. [GULSwizzler unswizzleClass:[PollutedTestObjectSubclass class]
  139. selector:swizzledSelector
  140. isClassSelector:NO];
  141. XCTAssertEqualObjects([pollutedTestObjectSubclass description],
  142. originalPollutedTestObjectSubclassDescription);
  143. XCTAssertEqualObjects([pollutedTestObject description], swizzledPollutedTestObjectDescription);
  144. [GULSwizzler unswizzleClass:[PollutedTestObject class]
  145. selector:swizzledSelector
  146. isClassSelector:NO];
  147. XCTAssertEqualObjects([pollutedTestObject description], originalPollutedTestObjectDescription);
  148. // This part of the test shows how 'unswizzling' still maintains a polluted runtime and
  149. // demonstrates the limitations of unswizzling due to the absence of class_removeMethod in ObjC.
  150. // Because we swizzled methods that didn't exist on the specific class (it came from the super
  151. // class), we ended up adding them. Swizzling again no longer respects the same inheritance chain
  152. // we'd observed after we'd first swizzled [PollutedTestObject description].
  153. [GULSwizzler swizzleClass:[PollutedTestObject class]
  154. selector:swizzledSelector
  155. isClassSelector:NO
  156. withBlock:newImplementationPollutedTestObject];
  157. XCTAssertEqualObjects([pollutedTestObject description], swizzledPollutedTestObjectDescription);
  158. XCTAssertEqualObjects([pollutedTestObjectSubclass description],
  159. originalPollutedTestObjectSubclassDescription);
  160. XCTAssertEqualObjects([pollutedTestObjectSubclassSubclass description],
  161. originalPollutedTestObjectSubclassSubclassDescription);
  162. [GULSwizzler unswizzleClass:[PollutedTestObject class]
  163. selector:swizzledSelector
  164. isClassSelector:NO];
  165. }
  166. @end