FPRObjectSwizzlerTest.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  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 "FirebasePerformance/Sources/ISASwizzler/FPRObjectSwizzler+Internal.h"
  17. #import "FirebasePerformance/Sources/ISASwizzler/FPRSwizzledObject.h"
  18. #import "FirebasePerformance/Tests/Unit/ISASwizzler/FPRProxy.h"
  19. @interface FPRObjectSwizzlerTest : XCTestCase
  20. @end
  21. @implementation FPRObjectSwizzlerTest
  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. [FPRObjectSwizzler setAssociatedObject:object
  38. key:@"test"
  39. value:associatedObject
  40. association:GUL_ASSOCIATION_RETAIN];
  41. associatedObject = nil;
  42. associatedObject = [FPRObjectSwizzler 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. FPRObjectSwizzler *objectSwizzler = [[FPRObjectSwizzler 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. FPRObjectSwizzler *objectSwizzler = [[FPRObjectSwizzler 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. FPRObjectSwizzler *objectSwizzler = [[FPRObjectSwizzler 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. FPRObjectSwizzler *objectSwizzler = [[FPRObjectSwizzler 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. [[(FPRSwizzledObject *)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. FPRObjectSwizzler *objectSwizzler = [[FPRObjectSwizzler 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. FPRObjectSwizzler *objectSwizzler = [[FPRObjectSwizzler 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 FPRObjectSwizzler *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. [FPRObjectSwizzler setAssociatedObject:object
  110. key:@"fir_key"
  111. value:objectToBeAssociated
  112. association:GUL_ASSOCIATION_COPY];
  113. NSDictionary *returnedObject = [FPRObjectSwizzler 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. [FPRObjectSwizzler setAssociatedObject:object
  121. key:@"fir_key"
  122. value:objectToBeAssociated
  123. association:GUL_ASSOCIATION_ASSIGN];
  124. NSDictionary *returnedObject = [FPRObjectSwizzler 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. [FPRObjectSwizzler setAssociatedObject:object
  132. key:@"fir_key"
  133. value:objectToBeAssociated
  134. association:GUL_ASSOCIATION_RETAIN];
  135. NSDictionary *returnedObject = [FPRObjectSwizzler 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. [FPRObjectSwizzler setAssociatedObject:object
  143. key:@"fir_key"
  144. value:objectToBeAssociated
  145. association:GUL_ASSOCIATION_COPY_NONATOMIC];
  146. NSDictionary *returnedObject = [FPRObjectSwizzler 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. [FPRObjectSwizzler setAssociatedObject:object
  154. key:@"fir_key"
  155. value:objectToBeAssociated
  156. association:GUL_ASSOCIATION_RETAIN_NONATOMIC];
  157. NSDictionary *returnedObject = [FPRObjectSwizzler 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. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler 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. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler 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. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler 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. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler 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. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler 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. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler 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 FPRObjectSwizzler to swizzle an object wrapped in an NSProxy. */
  225. - (void)testSwizzleProxiedObject {
  226. NSObject *object = [[NSObject alloc] init];
  227. FPRProxy *proxyObject = [FPRProxy proxyWithDelegate:object];
  228. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler alloc] initWithObject:proxyObject];
  229. XCTAssertNoThrow([swizzler swizzle]);
  230. XCTAssertNotEqual(object_getClass(proxyObject), [FPRProxy class]);
  231. XCTAssertTrue([object_getClass(proxyObject) isSubclassOfClass:[FPRProxy 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. FPRProxy *proxyObject = [FPRProxy proxyWithDelegate:object];
  241. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler alloc] initWithObject:proxyObject];
  242. [swizzler copySelector:@selector(description)
  243. fromClass:[FPRObjectSwizzlerTest 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. FPRProxy *proxyObject = [FPRProxy proxyWithDelegate:object];
  252. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler alloc] initWithObject:proxyObject];
  253. [swizzler copySelector:@selector(donorDescription)
  254. fromClass:[FPRObjectSwizzlerTest 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. FPRProxy *proxyObject = [FPRProxy proxyWithDelegate:object];
  264. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler alloc] initWithObject:proxyObject];
  265. [swizzler copySelector:@selector(donorDescription)
  266. fromClass:[FPRObjectSwizzlerTest 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. // TODO: Investigate why this test fails in Swift PM build.
  280. /** Tests that -[NSObjectProtocol respondsToSelector:] works as expected after someone else ISA
  281. * swizzles a proxy object that we've also ISA Swizzled.
  282. */
  283. - (void)testRespondsToSelectorWorksEvenIfSwizzledProxyISASwizzledBySomeoneElse {
  284. Class generatedClass = nil;
  285. __weak FPRObjectSwizzler *weakSwizzler;
  286. @autoreleasepool {
  287. NSObject *object = [[NSObject alloc] init];
  288. FPRProxy *proxyObject = [FPRProxy proxyWithDelegate:object];
  289. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler alloc] initWithObject:proxyObject];
  290. weakSwizzler = swizzler;
  291. [swizzler copySelector:@selector(donorDescription)
  292. fromClass:[FPRObjectSwizzlerTest class]
  293. isClassSelector:NO];
  294. [swizzler swizzle];
  295. // Someone else ISA Swizzles the same object after FPRObjectSwizzler.
  296. Class originalClass = object_getClass(proxyObject);
  297. NSString *newClassName = [NSString
  298. stringWithFormat:@"gul_test_%p_%@", proxyObject, NSStringFromClass(originalClass)];
  299. generatedClass = objc_allocateClassPair(originalClass, newClassName.UTF8String, 0);
  300. objc_registerClassPair(generatedClass);
  301. object_setClass(proxyObject, generatedClass);
  302. XCTAssertTrue([proxyObject respondsToSelector:@selector(donorDescription)]);
  303. XCTAssertEqual([proxyObject performSelector:@selector(donorDescription)],
  304. @"SwizzledDonorDescription");
  305. // Release FPRObjectSwizzler
  306. [FPRObjectSwizzler setAssociatedObject:proxyObject
  307. key:&kGULSwizzlerAssociatedObjectKey
  308. value:nil
  309. association:GUL_ASSOCIATION_RETAIN];
  310. }
  311. XCTAssertNil(weakSwizzler);
  312. // Clean up.
  313. objc_disposeClassPair(generatedClass);
  314. }
  315. #if !TARGET_OS_MACCATALYST
  316. // Test fails on Catalyst due to an interaction with GULSceneDelegateSwizzlerTests.
  317. - (void)testSwizzlerDoesntDisposeGeneratedClassWhenObjectIsISASwizzledBySomeoneElse {
  318. Class generatedClass = nil;
  319. __weak FPRObjectSwizzler *weakSwizzler;
  320. XCTestExpectation *swizzlerDeallocatedExpectation =
  321. [self expectationWithDescription:@"swizzlerDeallocatedExpectation"];
  322. @autoreleasepool {
  323. NSObject *object = [[NSObject alloc] init];
  324. @autoreleasepool {
  325. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler alloc] initWithObject:object];
  326. weakSwizzler = swizzler;
  327. [swizzler copySelector:@selector(donorDescription)
  328. fromClass:[FPRObjectSwizzlerTest class]
  329. isClassSelector:NO];
  330. [swizzler swizzle];
  331. }
  332. // Someone else ISA Swizzles the same object after FPRObjectSwizzler.
  333. Class originalClass = object_getClass(object);
  334. NSString *newClassName =
  335. [NSString stringWithFormat:@"gul_test_%p_%@", object, NSStringFromClass(originalClass)];
  336. generatedClass = objc_allocateClassPair(originalClass, newClassName.UTF8String, 0);
  337. objc_registerClassPair(generatedClass);
  338. object_setClass(object, generatedClass);
  339. // Release FPRObjectSwizzler
  340. [FPRObjectSwizzler setAssociatedObject:object
  341. key:&kGULSwizzlerAssociatedObjectKey
  342. value:nil
  343. association:GUL_ASSOCIATION_RETAIN];
  344. // Wait for a while
  345. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)),
  346. dispatch_get_main_queue(), ^{
  347. [swizzlerDeallocatedExpectation fulfill];
  348. });
  349. [self waitForExpectations:@[ swizzlerDeallocatedExpectation ] timeout:2];
  350. XCTAssertNil(weakSwizzler);
  351. // A class generated by FPRObjectSwizzler must not be disposed if there is its subclass.
  352. XCTAssertNoThrow([generatedClass description]);
  353. }
  354. // Clean up.
  355. objc_disposeClassPair(generatedClass);
  356. }
  357. #endif
  358. // The test is disabled because in the case of success it should crash with SIGABRT, so it is not
  359. // suitable for CI.
  360. - (void)disabledForCI_testSwizzlerDisposesGeneratedClass {
  361. __weak FPRObjectSwizzler *weakSwizzler;
  362. XCTestExpectation *swizzlerDeallocatedExpectation =
  363. [self expectationWithDescription:@"swizzlerDeallocatedExpectation"];
  364. @autoreleasepool {
  365. NSObject *object = [[NSObject alloc] init];
  366. @autoreleasepool {
  367. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler alloc] initWithObject:object];
  368. weakSwizzler = swizzler;
  369. [swizzler copySelector:@selector(donorDescription)
  370. fromClass:[FPRObjectSwizzlerTest class]
  371. isClassSelector:NO];
  372. [swizzler swizzle];
  373. }
  374. // Release FPRObjectSwizzler
  375. [FPRObjectSwizzler setAssociatedObject:object
  376. key:&kGULSwizzlerAssociatedObjectKey
  377. value:nil
  378. association:GUL_ASSOCIATION_RETAIN];
  379. // Wait for a while until FPRObjectSwizzler has disposed the generated class.
  380. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)),
  381. dispatch_get_main_queue(), ^{
  382. [swizzlerDeallocatedExpectation fulfill];
  383. });
  384. [self waitForExpectations:@[ swizzlerDeallocatedExpectation ] timeout:2];
  385. XCTAssertNil(weakSwizzler);
  386. // Must crash here with SIGABRT.
  387. XCTAssertThrows([object description]);
  388. XCTFail(@"The test must have crashed on the previous line.");
  389. }
  390. }
  391. - (void)testMultiSwizzling {
  392. NSObject *object = [[NSObject alloc] init];
  393. __weak FPRObjectSwizzler *existingSwizzler;
  394. // Use @autoreleasepool to make the memory management in the test more deterministic.
  395. @autoreleasepool {
  396. NSInteger swizzleCount = 10;
  397. for (NSInteger i = 0; i < swizzleCount; i++) {
  398. FPRObjectSwizzler *swizzler = [[FPRObjectSwizzler alloc] initWithObject:object];
  399. if (i > 0) {
  400. XCTAssertEqualObjects(swizzler, existingSwizzler,
  401. @"There must be a single swizzler per object.");
  402. } else {
  403. existingSwizzler = swizzler;
  404. }
  405. [swizzler copySelector:@selector(donorDescription)
  406. fromClass:[FPRObjectSwizzlerTest class]
  407. isClassSelector:NO];
  408. [swizzler swizzle];
  409. }
  410. XCTAssertNoThrow([object performSelector:@selector(donorDescription)]);
  411. object = nil;
  412. }
  413. XCTAssertNil(existingSwizzler,
  414. @"FPRObjectSwizzler must be deallocated after the object deallocation.");
  415. }
  416. @end