GULObjectSwizzlerTest.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  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 <XCTest/XCTest.h>
  15. #import <objc/runtime.h>
  16. #import <GoogleUtilities/GULObjectSwizzler.h>
  17. #import <GoogleUtilities/GULProxy.h>
  18. #import <GoogleUtilities/GULSwizzledObject.h>
  19. @interface GULObjectSwizzlerTest : XCTestCase
  20. @end
  21. @implementation GULObjectSwizzlerTest
  22. /** Used as a donor method to add a method that doesn't exist on the superclass. */
  23. - (NSString *)donorDescription {
  24. return @"SwizzledDonorDescription";
  25. }
  26. /** Used as a donor method to add a method that exists on the superclass. */
  27. - (NSString *)description {
  28. return @"SwizzledDescription";
  29. }
  30. /** Exists just as a donor method. */
  31. - (void)donorMethod {
  32. }
  33. - (void)testRetainedAssociatedObjects {
  34. NSObject *object = [[NSObject alloc] init];
  35. NSObject *associatedObject = [[NSObject alloc] init];
  36. size_t addressOfAssociatedObject = (size_t)&associatedObject;
  37. [GULObjectSwizzler setAssociatedObject:object
  38. key:@"test"
  39. value:associatedObject
  40. association:GUL_ASSOCIATION_RETAIN];
  41. associatedObject = nil;
  42. associatedObject = [GULObjectSwizzler getAssociatedObject:object key:@"test"];
  43. XCTAssertEqual((size_t)&associatedObject, addressOfAssociatedObject);
  44. XCTAssertNotNil(associatedObject);
  45. }
  46. /** Tests that creating an object swizzler works. */
  47. - (void)testObjectSwizzlerInit {
  48. NSObject *object = [[NSObject alloc] init];
  49. GULObjectSwizzler *objectSwizzler = [[GULObjectSwizzler alloc] initWithObject:object];
  50. XCTAssertNotNil(objectSwizzler);
  51. }
  52. /** Tests that you're able to swizzle an object. */
  53. - (void)testSwizzle {
  54. NSObject *object = [[NSObject alloc] init];
  55. GULObjectSwizzler *objectSwizzler = [[GULObjectSwizzler alloc] initWithObject:object];
  56. XCTAssertEqual([object class], [NSObject class]);
  57. [objectSwizzler swizzle];
  58. XCTAssertNotEqual([object class], [NSObject class]);
  59. XCTAssertTrue([[object class] isSubclassOfClass:[NSObject class]]);
  60. XCTAssertTrue([object respondsToSelector:@selector(gul_class)]);
  61. }
  62. /** Tests that swizzling a nil object fails. */
  63. - (void)testSwizzleNil {
  64. NSObject *object = [[NSObject alloc] init];
  65. GULObjectSwizzler *objectSwizzler = [[GULObjectSwizzler alloc] initWithObject:object];
  66. XCTAssertEqual([object class], [NSObject class]);
  67. object = nil;
  68. XCTAssertThrows([objectSwizzler swizzle]);
  69. }
  70. /** Tests the ability to copy a selector from one class to the swizzled object's generated class. */
  71. - (void)testCopySelectorFromClassIsClassSelectorAndSwizzle {
  72. NSObject *object = [[NSObject alloc] init];
  73. GULObjectSwizzler *objectSwizzler = [[GULObjectSwizzler alloc] initWithObject:object];
  74. [objectSwizzler copySelector:@selector(donorMethod) fromClass:[self class] isClassSelector:NO];
  75. XCTAssertFalse([object respondsToSelector:@selector(donorMethod)]);
  76. XCTAssertFalse([[object class] instancesRespondToSelector:@selector(donorMethod)]);
  77. [objectSwizzler swizzle];
  78. XCTAssertTrue([object respondsToSelector:@selector(donorMethod)]);
  79. // [object class] should return the original class, not the swizzled class.
  80. XCTAssertTrue(
  81. [[(GULSwizzledObject *)object gul_class] instancesRespondToSelector:@selector(donorMethod)]);
  82. }
  83. /** Tests that some helper methods are always added to swizzled objects. */
  84. - (void)testCommonSelectorsAddedUponSwizzling {
  85. NSObject *object = [[NSObject alloc] init];
  86. GULObjectSwizzler *objectSwizzler = [[GULObjectSwizzler alloc] initWithObject:object];
  87. XCTAssertFalse([object respondsToSelector:@selector(gul_class)]);
  88. [objectSwizzler swizzle];
  89. XCTAssertTrue([object respondsToSelector:@selector(gul_class)]);
  90. }
  91. /** Tests that there's no retain cycle and that -dealloc causes unswizzling. */
  92. - (void)testRetainCycleDoesntExistAndDeallocCausesUnswizzling {
  93. NSObject *object = [[NSObject alloc] init];
  94. GULObjectSwizzler *objectSwizzler = [[GULObjectSwizzler alloc] initWithObject:object];
  95. [objectSwizzler copySelector:@selector(donorMethod) fromClass:[self class] isClassSelector:NO];
  96. [objectSwizzler swizzle];
  97. // If objectSwizzler were used, the strong reference would make it live to the end of this test.
  98. // We want to make sure it dies when the object dies, hence the weak reference.
  99. __weak GULObjectSwizzler *weakObjectSwizzler = objectSwizzler;
  100. objectSwizzler = nil;
  101. XCTAssertNotNil(weakObjectSwizzler);
  102. object = nil;
  103. XCTAssertNil(weakObjectSwizzler);
  104. }
  105. /** Tests the class get/set associated object methods. */
  106. - (void)testClassSetAssociatedObjectCopy {
  107. NSObject *object = [[NSObject alloc] init];
  108. NSDictionary *objectToBeAssociated = [[NSDictionary alloc] init];
  109. [GULObjectSwizzler setAssociatedObject:object
  110. key:@"fir_key"
  111. value:objectToBeAssociated
  112. association:GUL_ASSOCIATION_COPY];
  113. NSDictionary *returnedObject = [GULObjectSwizzler getAssociatedObject:object key:@"fir_key"];
  114. XCTAssertEqualObjects(returnedObject, objectToBeAssociated);
  115. }
  116. /** Tests the class get/set associated object methods. */
  117. - (void)testClassSetAssociatedObjectAssign {
  118. NSObject *object = [[NSObject alloc] init];
  119. NSDictionary *objectToBeAssociated = [[NSDictionary alloc] init];
  120. [GULObjectSwizzler setAssociatedObject:object
  121. key:@"fir_key"
  122. value:objectToBeAssociated
  123. association:GUL_ASSOCIATION_ASSIGN];
  124. NSDictionary *returnedObject = [GULObjectSwizzler getAssociatedObject:object key:@"fir_key"];
  125. XCTAssertEqualObjects(returnedObject, objectToBeAssociated);
  126. }
  127. /** Tests the class get/set associated object methods. */
  128. - (void)testClassSetAssociatedObjectRetain {
  129. NSObject *object = [[NSObject alloc] init];
  130. NSDictionary *objectToBeAssociated = [[NSDictionary alloc] init];
  131. [GULObjectSwizzler setAssociatedObject:object
  132. key:@"fir_key"
  133. value:objectToBeAssociated
  134. association:GUL_ASSOCIATION_RETAIN];
  135. NSDictionary *returnedObject = [GULObjectSwizzler getAssociatedObject:object key:@"fir_key"];
  136. XCTAssertEqualObjects(returnedObject, objectToBeAssociated);
  137. }
  138. /** Tests the class get/set associated object methods. */
  139. - (void)testClassSetAssociatedObjectCopyNonatomic {
  140. NSObject *object = [[NSObject alloc] init];
  141. NSDictionary *objectToBeAssociated = [[NSDictionary alloc] init];
  142. [GULObjectSwizzler setAssociatedObject:object
  143. key:@"fir_key"
  144. value:objectToBeAssociated
  145. association:GUL_ASSOCIATION_COPY_NONATOMIC];
  146. NSDictionary *returnedObject = [GULObjectSwizzler getAssociatedObject:object key:@"fir_key"];
  147. XCTAssertEqualObjects(returnedObject, objectToBeAssociated);
  148. }
  149. /** Tests the class get/set associated object methods. */
  150. - (void)testClassSetAssociatedObjectRetainNonatomic {
  151. NSObject *object = [[NSObject alloc] init];
  152. NSDictionary *objectToBeAssociated = [[NSDictionary alloc] init];
  153. [GULObjectSwizzler setAssociatedObject:object
  154. key:@"fir_key"
  155. value:objectToBeAssociated
  156. association:GUL_ASSOCIATION_RETAIN_NONATOMIC];
  157. NSDictionary *returnedObject = [GULObjectSwizzler getAssociatedObject:object key:@"fir_key"];
  158. XCTAssertEqualObjects(returnedObject, objectToBeAssociated);
  159. }
  160. /** Tests the swizzler get/set associated object methods. */
  161. - (void)testSetGetAssociatedObjectCopy {
  162. NSObject *object = [[NSObject alloc] init];
  163. NSDictionary *associatedObject = [[NSDictionary alloc] init];
  164. GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object];
  165. [swizzler setAssociatedObjectWithKey:@"key"
  166. value:associatedObject
  167. association:GUL_ASSOCIATION_COPY];
  168. NSDictionary *returnedObject = [swizzler getAssociatedObjectForKey:@"key"];
  169. XCTAssertEqualObjects(returnedObject, associatedObject);
  170. }
  171. /** Tests the swizzler get/set associated object methods. */
  172. - (void)testSetGetAssociatedObjectAssign {
  173. NSObject *object = [[NSObject alloc] init];
  174. NSDictionary *associatedObject = [[NSDictionary alloc] init];
  175. GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object];
  176. [swizzler setAssociatedObjectWithKey:@"key"
  177. value:associatedObject
  178. association:GUL_ASSOCIATION_ASSIGN];
  179. NSDictionary *returnedObject = [swizzler getAssociatedObjectForKey:@"key"];
  180. XCTAssertEqualObjects(returnedObject, associatedObject);
  181. }
  182. /** Tests the swizzler get/set associated object methods. */
  183. - (void)testSetGetAssociatedObjectRetain {
  184. NSObject *object = [[NSObject alloc] init];
  185. NSDictionary *associatedObject = [[NSDictionary alloc] init];
  186. GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object];
  187. [swizzler setAssociatedObjectWithKey:@"key"
  188. value:associatedObject
  189. association:GUL_ASSOCIATION_RETAIN];
  190. NSDictionary *returnedObject = [swizzler getAssociatedObjectForKey:@"key"];
  191. XCTAssertEqualObjects(returnedObject, associatedObject);
  192. }
  193. /** Tests the swizzler get/set associated object methods. */
  194. - (void)testSetGetAssociatedObjectCopyNonatomic {
  195. NSObject *object = [[NSObject alloc] init];
  196. NSDictionary *associatedObject = [[NSDictionary alloc] init];
  197. GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object];
  198. [swizzler setAssociatedObjectWithKey:@"key"
  199. value:associatedObject
  200. association:GUL_ASSOCIATION_COPY_NONATOMIC];
  201. NSDictionary *returnedObject = [swizzler getAssociatedObjectForKey:@"key"];
  202. XCTAssertEqualObjects(returnedObject, associatedObject);
  203. }
  204. /** Tests the swizzler get/set associated object methods. */
  205. - (void)testSetGetAssociatedObjectRetainNonatomic {
  206. NSObject *object = [[NSObject alloc] init];
  207. NSDictionary *associatedObject = [[NSDictionary alloc] init];
  208. GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object];
  209. [swizzler setAssociatedObjectWithKey:@"key"
  210. value:associatedObject
  211. association:GUL_ASSOCIATION_RETAIN_NONATOMIC];
  212. NSDictionary *returnedObject = [swizzler getAssociatedObjectForKey:@"key"];
  213. XCTAssertEqualObjects(returnedObject, associatedObject);
  214. }
  215. /** Tests getting and setting an associated object with an invalid association type. */
  216. - (void)testSetGetAssociatedObjectWithoutProperAssociation {
  217. NSObject *object = [[NSObject alloc] init];
  218. NSDictionary *associatedObject = [[NSDictionary alloc] init];
  219. GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object];
  220. [swizzler setAssociatedObjectWithKey:@"key" value:associatedObject association:1337];
  221. NSDictionary *returnedObject = [swizzler getAssociatedObjectForKey:@"key"];
  222. XCTAssertEqualObjects(returnedObject, associatedObject);
  223. }
  224. /** Tests using the GULObjectSwizzler to swizzle an object wrapped in an NSProxy. */
  225. - (void)testSwizzleProxiedObject {
  226. NSObject *object = [[NSObject alloc] init];
  227. GULProxy *proxyObject = [GULProxy proxyWithDelegate:object];
  228. GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:proxyObject];
  229. XCTAssertNoThrow([swizzler swizzle]);
  230. XCTAssertNotEqual(object_getClass(proxyObject), [GULProxy class]);
  231. XCTAssertTrue([object_getClass(proxyObject) isSubclassOfClass:[GULProxy class]]);
  232. XCTAssertTrue([proxyObject respondsToSelector:@selector(gul_objectSwizzler)]);
  233. XCTAssertNoThrow([proxyObject performSelector:@selector(gul_objectSwizzler)]);
  234. XCTAssertTrue([proxyObject respondsToSelector:@selector(gul_class)]);
  235. XCTAssertNoThrow([proxyObject performSelector:@selector(gul_class)]);
  236. }
  237. /** Tests overriding a method that already exists on a proxied object works as expected. */
  238. - (void)testSwizzleProxiedObjectInvokesInjectedMethodWhenOverridingMethod {
  239. NSObject *object = [[NSObject alloc] init];
  240. GULProxy *proxyObject = [GULProxy proxyWithDelegate:object];
  241. GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:proxyObject];
  242. [swizzler copySelector:@selector(description)
  243. fromClass:[GULObjectSwizzlerTest class]
  244. isClassSelector:NO];
  245. [swizzler swizzle];
  246. XCTAssertEqual([proxyObject performSelector:@selector(description)], @"SwizzledDescription");
  247. }
  248. /** Tests adding a method that doesn't exist on a proxied object works as expected. */
  249. - (void)testSwizzleProxiedObjectInvokesInjectedMethodWhenAddingMethod {
  250. NSObject *object = [[NSObject alloc] init];
  251. GULProxy *proxyObject = [GULProxy proxyWithDelegate:object];
  252. GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:proxyObject];
  253. [swizzler copySelector:@selector(donorDescription)
  254. fromClass:[GULObjectSwizzlerTest class]
  255. isClassSelector:NO];
  256. [swizzler swizzle];
  257. XCTAssertEqual([proxyObject performSelector:@selector(donorDescription)],
  258. @"SwizzledDonorDescription");
  259. }
  260. /** Tests KVOing a proxy object that we've ISA Swizzled works as expected. */
  261. - (void)testRespondsToSelectorWorksEvenIfSwizzledProxyIsKVOd {
  262. NSObject *object = [[NSObject alloc] init];
  263. GULProxy *proxyObject = [GULProxy proxyWithDelegate:object];
  264. GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:proxyObject];
  265. [swizzler copySelector:@selector(donorDescription)
  266. fromClass:[GULObjectSwizzlerTest class]
  267. isClassSelector:NO];
  268. [swizzler swizzle];
  269. [(NSObject *)proxyObject addObserver:self
  270. forKeyPath:NSStringFromSelector(@selector(description))
  271. options:0
  272. context:NULL];
  273. XCTAssertTrue([proxyObject respondsToSelector:@selector(donorDescription)]);
  274. XCTAssertEqual([proxyObject performSelector:@selector(donorDescription)],
  275. @"SwizzledDonorDescription");
  276. [(NSObject *)proxyObject removeObserver:self
  277. forKeyPath:NSStringFromSelector(@selector(description))];
  278. }
  279. /** Tests that -[NSObjectProtocol resopondsToSelector:] works as expected after someone else ISA
  280. * swizzles a proxy object that we've also ISA Swizzled.
  281. */
  282. - (void)testRespondsToSelectorWorksEvenIfSwizzledProxyISASwizzledBySomeoneElse {
  283. Class generatedClass = nil;
  284. @autoreleasepool {
  285. NSObject *object = [[NSObject alloc] init];
  286. GULProxy *proxyObject = [GULProxy proxyWithDelegate:object];
  287. GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:proxyObject];
  288. [swizzler copySelector:@selector(donorDescription)
  289. fromClass:[GULObjectSwizzlerTest class]
  290. isClassSelector:NO];
  291. [swizzler swizzle];
  292. // Someone else ISA Swizzles the same object after GULObjectSwizzler.
  293. Class originalClass = object_getClass(proxyObject);
  294. NSString *newClassName = [NSString
  295. stringWithFormat:@"gul_test_%p_%@", proxyObject, NSStringFromClass(originalClass)];
  296. generatedClass = objc_allocateClassPair(originalClass, newClassName.UTF8String, 0);
  297. objc_registerClassPair(generatedClass);
  298. object_setClass(proxyObject, generatedClass);
  299. XCTAssertTrue([proxyObject respondsToSelector:@selector(donorDescription)]);
  300. XCTAssertEqual([proxyObject performSelector:@selector(donorDescription)],
  301. @"SwizzledDonorDescription");
  302. }
  303. // A class generated by GULObjectSwizzler must not be disposed if there is its subclass.
  304. XCTAssertNoThrow([generatedClass description]);
  305. // Clean up.
  306. objc_disposeClassPair(generatedClass);
  307. }
  308. - (void)testMultiSwizzling {
  309. NSObject *object = [[NSObject alloc] init];
  310. NSInteger swizzleCount = 10;
  311. for (NSInteger i = 0; i < swizzleCount; i++) {
  312. GULObjectSwizzler *swizzler = [[GULObjectSwizzler alloc] initWithObject:object];
  313. [swizzler copySelector:@selector(donorDescription)
  314. fromClass:[GULObjectSwizzlerTest class]
  315. isClassSelector:NO];
  316. [swizzler swizzle];
  317. }
  318. XCTAssertNoThrow([object performSelector:@selector(donorDescription)]);
  319. object = nil;
  320. }
  321. @end