GULSwizzlerTest.m 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  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/GULOriginalIMPConvenienceMacros.h>
  17. #import <GoogleUtilities/GULSwizzler+Unswizzle.h>
  18. #import <GoogleUtilities/GULSwizzler.h>
  19. @interface TestObject : NSObject
  20. @end
  21. @implementation TestObject
  22. + (NSString *)description {
  23. return [[super description] stringByAppendingString:@" and here's my addition: BLAH BLAH"];
  24. }
  25. // This method is used to help test swizzling a method that calls super.
  26. - (NSString *)description {
  27. return [NSString stringWithFormat:@"TestObject, superclass: %@", [super description]];
  28. }
  29. /** This method is used to test invoking an original instance IMP with one argument.
  30. * @return A description string.
  31. */
  32. - (NSString *)descriptionThatSays:(NSString *)something {
  33. return [@"instance:" stringByAppendingString:something];
  34. }
  35. /** This method is used to test invoking an original class IMP with one argument.
  36. * @return A description string.
  37. */
  38. + (NSString *)descriptionThatSays:(NSString *)something {
  39. return [@"class:" stringByAppendingString:something];
  40. }
  41. @end
  42. @interface TestObjectSubclass : TestObject
  43. @end
  44. @implementation TestObjectSubclass
  45. @end
  46. @interface GULSwizzlerTest : XCTestCase
  47. @end
  48. @implementation GULSwizzlerTest
  49. /** Tests originalImplementationForClass:selector:isClassSelector: returns the original instance
  50. * IMP.
  51. */
  52. - (void)testOriginalImpInstanceMethod {
  53. Method method = class_getInstanceMethod([NSObject class], @selector(description));
  54. IMP originalImp = method_getImplementation(method);
  55. NSString * (^newImplementation)(void) = ^NSString *() {
  56. return @"nonsense";
  57. };
  58. [GULSwizzler swizzleClass:[NSObject class]
  59. selector:@selector(description)
  60. isClassSelector:NO
  61. withBlock:newImplementation];
  62. IMP returnedImp = [GULSwizzler originalImplementationForClass:[NSObject class]
  63. selector:@selector(description)
  64. isClassSelector:NO];
  65. XCTAssertEqual(returnedImp, originalImp);
  66. [GULSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:NO];
  67. }
  68. /** Tests currentImplementationForClass:selector:isClassSelector: returns different IMPs for class
  69. * and instance methods.
  70. */
  71. - (void)testCurrentImplementationReturnsDifferentIMPsForClassAndInstanceMethod {
  72. Class aClass = [NSObject class];
  73. SEL aSelector = @selector(description);
  74. IMP descriptionClassIMP = [GULSwizzler currentImplementationForClass:aClass
  75. selector:aSelector
  76. isClassSelector:NO];
  77. IMP descriptionInstanceIMP = [GULSwizzler currentImplementationForClass:aClass
  78. selector:aSelector
  79. isClassSelector:YES];
  80. XCTAssertNotEqual(descriptionClassIMP, descriptionInstanceIMP);
  81. }
  82. /** Tests currentImplementationForClass:selector:isClassSelector: returns the same IMP twice when it
  83. * hasn't been swizzled.
  84. */
  85. - (void)testCurrentImplementationReturnsSameIMPsWhenNotSwizzledBetweenInvocations {
  86. Class aClass = [NSObject class];
  87. SEL aSelector = @selector(description);
  88. IMP descriptionClassIMPOne = [GULSwizzler currentImplementationForClass:aClass
  89. selector:aSelector
  90. isClassSelector:NO];
  91. IMP descriptionClassIMPTwo = [GULSwizzler currentImplementationForClass:aClass
  92. selector:aSelector
  93. isClassSelector:NO];
  94. XCTAssertEqual(descriptionClassIMPOne, descriptionClassIMPTwo);
  95. }
  96. /** Tests currentImplementationForClass:selector:isClassSelector: returns a different IMP when it
  97. * has been swizzled.
  98. */
  99. - (void)testCurrentImplementationReturnsDifferentIMPsWhenSwizzledBetweenInvocations {
  100. Class aClass = [NSObject class];
  101. SEL aSelector = @selector(description);
  102. IMP originalIMP = [GULSwizzler currentImplementationForClass:aClass
  103. selector:aSelector
  104. isClassSelector:NO];
  105. NSString * (^newImplementation)(id) = ^NSString *(id _self) {
  106. return @"nonsense";
  107. };
  108. [GULSwizzler swizzleClass:aClass
  109. selector:aSelector
  110. isClassSelector:NO
  111. withBlock:newImplementation];
  112. IMP newIMP = [GULSwizzler currentImplementationForClass:aClass
  113. selector:aSelector
  114. isClassSelector:NO];
  115. XCTAssertNotEqual(newIMP, originalIMP);
  116. [GULSwizzler unswizzleClass:aClass selector:aSelector isClassSelector:NO];
  117. }
  118. /** Tests that invoking an original IMP in a swizzled IMP calls through correctly. */
  119. - (void)testOriginalImpCallThrough {
  120. SEL selector = @selector(description);
  121. Class aClass = [NSObject class];
  122. id newDescription = ^NSString *(id object) {
  123. IMP originalImp = [GULSwizzler originalImplementationForClass:aClass
  124. selector:selector
  125. isClassSelector:NO];
  126. NSString *originalDescription =
  127. GUL_INVOKE_ORIGINAL_IMP0(object, selector, NSString *, originalImp);
  128. return [originalDescription stringByAppendingString:@"SWIZZLED!"];
  129. };
  130. [GULSwizzler swizzleClass:aClass selector:selector isClassSelector:NO withBlock:newDescription];
  131. NSString *result = [[[NSObject alloc] init] description];
  132. XCTAssertGreaterThan([result rangeOfString:@"SWIZZLED!"].location, 0);
  133. [GULSwizzler unswizzleClass:aClass selector:selector isClassSelector:NO];
  134. }
  135. /** Tests originalImplementationForClass:selector:isClassSelector: returns the original class IMP.
  136. */
  137. - (void)testOriginalImpClassMethod {
  138. Method method = class_getInstanceMethod([NSObject class], @selector(description));
  139. IMP originalImp = method_getImplementation(method);
  140. NSString * (^newImplementation)(void) = ^NSString *() {
  141. return @"nonsense";
  142. };
  143. [GULSwizzler swizzleClass:[NSObject class]
  144. selector:@selector(description)
  145. isClassSelector:NO
  146. withBlock:newImplementation];
  147. IMP returnedImp = [GULSwizzler originalImplementationForClass:[NSObject class]
  148. selector:@selector(description)
  149. isClassSelector:NO];
  150. XCTAssertEqual(returnedImp, originalImp);
  151. [GULSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:NO];
  152. }
  153. /** Tests originalImplementationForClass:selector:isClassSelector: returns different IMPs for
  154. * instance methods and class methods of the same name (like -/+ description).
  155. */
  156. - (void)testOriginalImpInstanceAndClassImpsAreDifferent {
  157. Method instanceMethod = class_getInstanceMethod([NSObject class], @selector(description));
  158. Method classMethod = class_getClassMethod([NSObject class], @selector(description));
  159. IMP instanceImp = method_getImplementation(instanceMethod);
  160. IMP classImp = method_getImplementation(classMethod);
  161. NSString * (^newImplementation)(void) = ^NSString *() {
  162. return @"nonsense";
  163. };
  164. [GULSwizzler swizzleClass:[NSObject class]
  165. selector:@selector(description)
  166. isClassSelector:NO
  167. withBlock:newImplementation];
  168. [GULSwizzler swizzleClass:[NSObject class]
  169. selector:@selector(description)
  170. isClassSelector:YES
  171. withBlock:newImplementation];
  172. XCTAssertNotEqual(instanceMethod, classMethod);
  173. IMP returnedInstanceImp = [GULSwizzler originalImplementationForClass:[NSObject class]
  174. selector:@selector(description)
  175. isClassSelector:NO];
  176. IMP returnedClassImp = [GULSwizzler originalImplementationForClass:[NSObject class]
  177. selector:@selector(description)
  178. isClassSelector:YES];
  179. XCTAssertNotEqual(instanceImp, classImp);
  180. XCTAssertNotEqual(returnedInstanceImp, returnedClassImp);
  181. [GULSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:NO];
  182. [GULSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:YES];
  183. }
  184. /** Tests swizzling an instance method. */
  185. - (void)testSwizzleInstanceMethod {
  186. NSString *swizzledDescription = @"Not what you expected!";
  187. NSString * (^newImplementation)(void) = ^NSString *() {
  188. return swizzledDescription;
  189. };
  190. [GULSwizzler swizzleClass:[NSObject class]
  191. selector:@selector(description)
  192. isClassSelector:NO
  193. withBlock:newImplementation];
  194. NSString *returnedDescription = [[[NSObject alloc] init] description];
  195. XCTAssertEqualObjects(returnedDescription, swizzledDescription);
  196. [GULSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:NO];
  197. }
  198. /** Tests swizzling a class method. */
  199. - (void)testSwizzleClassMethod {
  200. NSString *swizzledDescription = @"Swizzled class description";
  201. NSString * (^newImplementation)(void) = ^NSString *() {
  202. return swizzledDescription;
  203. };
  204. [GULSwizzler swizzleClass:[NSObject class]
  205. selector:@selector(description)
  206. isClassSelector:YES
  207. withBlock:newImplementation];
  208. XCTAssertEqualObjects([NSObject description], swizzledDescription);
  209. [GULSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:YES];
  210. }
  211. /** Tests unswizzling an instance method. */
  212. - (void)testUnswizzleInstanceMethod {
  213. NSObject *object = [[NSObject alloc] init];
  214. NSString *originalDescription = [object description];
  215. NSString *swizzledDescription = @"Swizzled description";
  216. NSString * (^newImplementation)(void) = ^NSString *() {
  217. return swizzledDescription;
  218. };
  219. [GULSwizzler swizzleClass:[NSObject class]
  220. selector:@selector(description)
  221. isClassSelector:NO
  222. withBlock:newImplementation];
  223. NSString *returnedDescription = [object description];
  224. XCTAssertEqualObjects(returnedDescription, swizzledDescription);
  225. [GULSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:NO];
  226. returnedDescription = [object description];
  227. XCTAssertEqualObjects(returnedDescription, originalDescription);
  228. }
  229. /** Tests unswizzling a class method. */
  230. - (void)testUnswizzleClassMethod {
  231. NSString *originalDescription = [NSObject description];
  232. NSString *swizzledDescription = @"Swizzled class description";
  233. NSString * (^newImplementation)(void) = ^NSString *() {
  234. return swizzledDescription;
  235. };
  236. [GULSwizzler swizzleClass:[NSObject class]
  237. selector:@selector(description)
  238. isClassSelector:YES
  239. withBlock:newImplementation];
  240. XCTAssertEqualObjects([NSObject description], swizzledDescription);
  241. [GULSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:YES];
  242. XCTAssertEqualObjects([NSObject description], originalDescription);
  243. }
  244. /** Tests swizzling a class method doesn't swizzle an instance method of the same name. */
  245. - (void)testSwizzlingAClassMethodDoesntSwizzleAnInstanceMethod {
  246. NSString *swizzledDescription = @"Swizzled class description";
  247. NSString * (^newImplementation)(void) = ^NSString *() {
  248. return swizzledDescription;
  249. };
  250. [GULSwizzler swizzleClass:[NSObject class]
  251. selector:@selector(description)
  252. isClassSelector:YES
  253. withBlock:newImplementation];
  254. XCTAssertEqualObjects([NSObject description], swizzledDescription);
  255. XCTAssertNotEqualObjects([[[NSObject alloc] init] description], swizzledDescription);
  256. [GULSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:YES];
  257. }
  258. /** Tests swizzling an instance method doesn't swizzle a class method of the same name. */
  259. - (void)testSwizzlingAnInstanceMethodDoesntSwizzleAClassMethod {
  260. NSString *swizzledDescription = @"Not what you expected!";
  261. NSString * (^newImplementation)(void) = ^NSString *() {
  262. return swizzledDescription;
  263. };
  264. [GULSwizzler swizzleClass:[NSObject class]
  265. selector:@selector(description)
  266. isClassSelector:NO
  267. withBlock:newImplementation];
  268. NSString *returnedDescription = [[[NSObject alloc] init] description];
  269. XCTAssertEqual(returnedDescription, swizzledDescription);
  270. XCTAssertNotEqualObjects([NSObject description], swizzledDescription);
  271. [GULSwizzler unswizzleClass:[NSObject class] selector:@selector(description) isClassSelector:NO];
  272. }
  273. /** Tests swizzling a superclass's instance method. */
  274. - (void)testSwizzlingSuperclassInstanceMethod {
  275. NSObject *generalObject = [[NSObject alloc] init];
  276. BOOL generalObjectIsProxyValue = [generalObject isProxy];
  277. BOOL (^newImplementation)(void) = ^BOOL() {
  278. return !generalObjectIsProxyValue;
  279. };
  280. [GULSwizzler swizzleClass:[TestObject class]
  281. selector:@selector(isProxy)
  282. isClassSelector:NO
  283. withBlock:newImplementation];
  284. XCTAssertNotEqual([[[TestObject alloc] init] isProxy], generalObjectIsProxyValue);
  285. [GULSwizzler unswizzleClass:[TestObject class] selector:@selector(isProxy) isClassSelector:NO];
  286. }
  287. /** Tests swizzling a superclass's class method. */
  288. - (void)testSwizzlingSuperclassClassMethod {
  289. NSString *swizzledDescription = @"Swizzled class description";
  290. NSString * (^newImplementation)(void) = ^NSString *() {
  291. return swizzledDescription;
  292. };
  293. [GULSwizzler swizzleClass:[TestObject class]
  294. selector:@selector(description)
  295. isClassSelector:YES
  296. withBlock:newImplementation];
  297. XCTAssertEqualObjects([TestObject description], swizzledDescription);
  298. [GULSwizzler unswizzleClass:[TestObject class]
  299. selector:@selector(description)
  300. isClassSelector:YES];
  301. }
  302. /** Tests swizzling an instance method that calls into the superclass implementation. */
  303. - (void)testSwizzlingInstanceMethodThatCallsSuper {
  304. TestObject *testObject = [[TestObject alloc] init];
  305. NSString *originalDescription = [testObject description];
  306. NSString *swizzledDescription = [originalDescription stringByAppendingString:@"SWIZZLED!"];
  307. NSString * (^newImplementation)(void) = ^NSString *() {
  308. return swizzledDescription;
  309. };
  310. [GULSwizzler swizzleClass:[TestObject class]
  311. selector:@selector(description)
  312. isClassSelector:NO
  313. withBlock:newImplementation];
  314. XCTAssertEqualObjects([testObject description], swizzledDescription);
  315. [GULSwizzler unswizzleClass:[TestObject class]
  316. selector:@selector(description)
  317. isClassSelector:NO];
  318. XCTAssertEqualObjects([testObject description], originalDescription);
  319. }
  320. /** Tests swizzling a method and getting the original IMP of that method. */
  321. - (void)testSwizzleAndGet {
  322. Class testClass = [NSURL class];
  323. SEL testSelector = @selector(description);
  324. IMP baseImp = class_getMethodImplementation(testClass, testSelector);
  325. [GULSwizzler swizzleClass:testClass
  326. selector:testSelector
  327. isClassSelector:NO
  328. withBlock:^{
  329. return @"Swizzled Description";
  330. }];
  331. IMP origImp = [GULSwizzler originalImplementationForClass:testClass
  332. selector:testSelector
  333. isClassSelector:NO];
  334. XCTAssertEqual(origImp, baseImp, @"Original IMP and base IMP are not equal.");
  335. [GULSwizzler unswizzleClass:testClass selector:testSelector isClassSelector:NO];
  336. }
  337. /** Tests swizzling more than a single method at a time. */
  338. - (void)testSwizzleMultiple {
  339. Class testClass = [NSURL class];
  340. SEL testSelector = @selector(description);
  341. [GULSwizzler swizzleClass:testClass
  342. selector:testSelector
  343. isClassSelector:NO
  344. withBlock:^{
  345. return @"Swizzled Description";
  346. }];
  347. IMP origImp = [GULSwizzler originalImplementationForClass:testClass
  348. selector:testSelector
  349. isClassSelector:NO];
  350. Class testClass2 = [NSURLRequest class];
  351. SEL testSelector2 = @selector(debugDescription);
  352. [GULSwizzler swizzleClass:testClass2
  353. selector:testSelector2
  354. isClassSelector:NO
  355. withBlock:^{
  356. return @"Swizzled Debug Description";
  357. }];
  358. IMP origImp2 = [GULSwizzler originalImplementationForClass:testClass2
  359. selector:testSelector2
  360. isClassSelector:NO];
  361. XCTAssertNotEqual(origImp2, NULL, @"Original IMP is NULL after swizzle.");
  362. XCTAssertNotEqual(origImp, origImp2, @"Implementations are the same when they should't be.");
  363. [GULSwizzler unswizzleClass:testClass selector:testSelector isClassSelector:NO];
  364. [GULSwizzler unswizzleClass:testClass2 selector:testSelector2 isClassSelector:NO];
  365. }
  366. /** Tests swizzling multiple instance methods on the same class at the same time */
  367. - (void)testSwizzleMultipleInstanceMethodsOnSameClass {
  368. Class testClass = [TestObject class];
  369. SEL selectorOne = @selector(description);
  370. SEL selectorTwo = @selector(descriptionThatSays:);
  371. NSString * (^newImplementationDescription)(id, SEL) = ^NSString *(id _self, SEL cmd) {
  372. return @"SWIZZLED!";
  373. };
  374. NSString * (^newImplementationDescriptionThatSays)(TestObject *, NSString *) =
  375. ^NSString *(TestObject *_self, NSString *something) {
  376. return [something stringByAppendingString:@"SWIZZLED!"];
  377. };
  378. [GULSwizzler swizzleClass:testClass
  379. selector:selectorOne
  380. isClassSelector:NO
  381. withBlock:newImplementationDescription];
  382. [GULSwizzler swizzleClass:testClass
  383. selector:selectorTwo
  384. isClassSelector:NO
  385. withBlock:newImplementationDescriptionThatSays];
  386. TestObject *sampleObject = [[TestObject alloc] init];
  387. XCTAssertEqualObjects([sampleObject description], @"SWIZZLED!");
  388. XCTAssertEqualObjects([sampleObject descriptionThatSays:@"Name"], @"NameSWIZZLED!");
  389. [GULSwizzler unswizzleClass:testClass selector:selectorOne isClassSelector:NO];
  390. [GULSwizzler unswizzleClass:testClass selector:selectorTwo isClassSelector:NO];
  391. }
  392. /** Tests swizzling multiple class methods on the same class at the same time */
  393. - (void)testSwizzleMultipleClassMethodsOnSameClass {
  394. Class testClass = [TestObject class];
  395. SEL selectorOne = @selector(description);
  396. SEL selectorTwo = @selector(descriptionThatSays:);
  397. NSString * (^newImplementationDescription)(id, SEL) = ^NSString *(id _self, SEL cmd) {
  398. return @"SWIZZLED!";
  399. };
  400. NSString * (^newImplementationDescriptionThatSays)(TestObject *, NSString *) =
  401. ^NSString *(TestObject *_self, NSString *something) {
  402. return [something stringByAppendingString:@"SWIZZLED!"];
  403. };
  404. [GULSwizzler swizzleClass:testClass
  405. selector:selectorOne
  406. isClassSelector:YES
  407. withBlock:newImplementationDescription];
  408. [GULSwizzler swizzleClass:testClass
  409. selector:selectorTwo
  410. isClassSelector:YES
  411. withBlock:newImplementationDescriptionThatSays];
  412. XCTAssertEqualObjects([TestObject description], @"SWIZZLED!");
  413. XCTAssertEqualObjects([TestObject descriptionThatSays:@"Name"], @"NameSWIZZLED!");
  414. [GULSwizzler unswizzleClass:testClass selector:selectorOne isClassSelector:YES];
  415. [GULSwizzler unswizzleClass:testClass selector:selectorTwo isClassSelector:YES];
  416. }
  417. /** Tests swizzling an instance method is correctly swizzled on multiple instances of the same
  418. * class.
  419. */
  420. - (void)testSwizzlingInstanceMethodIsEffectiveOnMultipleInstancesOfSameClass {
  421. Class testClass = [TestObject class];
  422. SEL selector = @selector(description);
  423. NSString * (^newImplementationDescription)(id, SEL) = ^NSString *(id _self, SEL cmd) {
  424. return @"SWIZZLED!";
  425. };
  426. [GULSwizzler swizzleClass:testClass
  427. selector:selector
  428. isClassSelector:NO
  429. withBlock:newImplementationDescription];
  430. TestObject *sampleObjectOne = [[TestObject alloc] init];
  431. TestObject *sampleObjectTwo = [[TestObject alloc] init];
  432. XCTAssertEqualObjects([sampleObjectOne description], @"SWIZZLED!");
  433. XCTAssertEqualObjects([sampleObjectTwo description], @"SWIZZLED!");
  434. [GULSwizzler unswizzleClass:testClass selector:selector isClassSelector:NO];
  435. }
  436. /** Tests swizzling a class method that calls into the superclass implementation. */
  437. - (void)testSwizzlingClassMethodThatCallsSuper {
  438. NSString *originalDescription = [TestObject description];
  439. NSString *swizzledDescription = @"Swizzled class description";
  440. NSString * (^newImplementation)(void) = ^NSString *() {
  441. return swizzledDescription;
  442. };
  443. [GULSwizzler swizzleClass:[TestObject class]
  444. selector:@selector(description)
  445. isClassSelector:YES
  446. withBlock:newImplementation];
  447. XCTAssertEqualObjects([TestObject description], swizzledDescription);
  448. [GULSwizzler unswizzleClass:[TestObject class]
  449. selector:@selector(description)
  450. isClassSelector:YES];
  451. XCTAssertEqualObjects([TestObject description], originalDescription);
  452. }
  453. /** Tests swizzling an inherited instance method doesn't change the implementation of the
  454. * superclass's implementation of that same method.
  455. */
  456. - (void)testSwizzlingAnInheritedInstanceMethodDoesntAffectTheIMPOfItsSuperclass {
  457. NSObject *generalObject = [[NSObject alloc] init];
  458. BOOL originalGeneralObjectValue = [generalObject isProxy];
  459. BOOL (^newImplementation)(void) = ^BOOL(void) {
  460. return !originalGeneralObjectValue;
  461. };
  462. [GULSwizzler swizzleClass:[TestObject class]
  463. selector:@selector(isProxy)
  464. isClassSelector:NO
  465. withBlock:newImplementation];
  466. XCTAssertEqual([generalObject isProxy], originalGeneralObjectValue);
  467. XCTAssertNotEqual([[[TestObject alloc] init] isProxy], originalGeneralObjectValue);
  468. [GULSwizzler unswizzleClass:[TestObject class] selector:@selector(isProxy) isClassSelector:NO];
  469. XCTAssertEqual([[[TestObject alloc] init] isProxy], originalGeneralObjectValue);
  470. }
  471. /** Tests swizzling an inherited instance method from a superclass a couple of links up in the
  472. * chain of superclasses doesn't affect the implementation of the superclass's method.
  473. */
  474. - (void)testSwizzlingADeeperInheritedInstanceMethodDoesntAffectTheIMPOfItsSuperclass {
  475. TestObject *testObject = [[TestObject alloc] init];
  476. BOOL originalTestObjectValue = [testObject isProxy];
  477. BOOL (^newImplementation)(void) = ^BOOL(void) {
  478. return !originalTestObjectValue;
  479. };
  480. [GULSwizzler swizzleClass:[TestObjectSubclass class]
  481. selector:@selector(isProxy)
  482. isClassSelector:NO
  483. withBlock:newImplementation];
  484. XCTAssertEqual([testObject isProxy], originalTestObjectValue);
  485. XCTAssertNotEqual([[[TestObjectSubclass alloc] init] isProxy], originalTestObjectValue);
  486. [GULSwizzler unswizzleClass:[TestObjectSubclass class]
  487. selector:@selector(isProxy)
  488. isClassSelector:NO];
  489. XCTAssertEqual([[[TestObjectSubclass alloc] init] isProxy], originalTestObjectValue);
  490. }
  491. /** Tests swizzling an inherited class method doesn't change the implementation of the
  492. * superclass's implementation of that same method.
  493. */
  494. - (void)testSwizzlingAnInheritedClassMethodDoesntAffectTheIMPOfItsSuperclass {
  495. // Fun fact, this won't work on +new. Swizzling +new causes a retain to not be placed correctly.
  496. NSString *originalDescription = [TestObject description];
  497. NSString *swizzledDescription = [originalDescription stringByAppendingString:@"SWIZZLED!"];
  498. NSString * (^newImplementation)(void) = ^NSString *() {
  499. return swizzledDescription;
  500. };
  501. [GULSwizzler swizzleClass:[TestObject class]
  502. selector:@selector(description)
  503. isClassSelector:YES
  504. withBlock:newImplementation];
  505. XCTAssertEqualObjects([TestObject description], swizzledDescription);
  506. XCTAssertNotEqualObjects([NSObject description], swizzledDescription);
  507. [GULSwizzler unswizzleClass:[TestObject class]
  508. selector:@selector(description)
  509. isClassSelector:YES];
  510. XCTAssertNotEqualObjects([TestObject description], swizzledDescription);
  511. XCTAssertNotEqualObjects([NSObject description], originalDescription);
  512. }
  513. /** Tests swizzling an inherited class method from a superclass a couple of links up in the
  514. * chain of superclasses doesn't affect the implementation of the superclass's method.
  515. */
  516. - (void)testSwizzlingADeeperInheritedClassMethodDoesntAffectTheIMPOfItsSuperclass {
  517. NSString *originalDescription = [TestObjectSubclass description];
  518. NSString *swizzledDescription = [originalDescription stringByAppendingString:@"SWIZZLED!"];
  519. NSString * (^newImplementation)(void) = ^NSString *() {
  520. return swizzledDescription;
  521. };
  522. [GULSwizzler swizzleClass:[TestObjectSubclass class]
  523. selector:@selector(description)
  524. isClassSelector:YES
  525. withBlock:newImplementation];
  526. XCTAssertEqualObjects([TestObjectSubclass description], swizzledDescription);
  527. XCTAssertNotEqualObjects([TestObject description], swizzledDescription);
  528. XCTAssertNotEqualObjects([NSObject description], swizzledDescription);
  529. [GULSwizzler unswizzleClass:[TestObjectSubclass class]
  530. selector:@selector(description)
  531. isClassSelector:YES];
  532. XCTAssertNotEqualObjects([TestObjectSubclass description], swizzledDescription);
  533. XCTAssertNotEqualObjects([TestObject description], originalDescription);
  534. XCTAssertNotEqualObjects([NSObject description], originalDescription);
  535. }
  536. /** Tests invoking an original instance IMP that takes one argument. */
  537. - (void)testInvokingOriginalInstanceIMPWithOneArgument {
  538. TestObject *testObject = [[TestObject alloc] init];
  539. NSString * (^replacingBlock)(TestObject *, NSString *) =
  540. ^NSString *(TestObject *_self, NSString *something) {
  541. return [something stringByAppendingString:@"SWIZZLED!"];
  542. };
  543. SEL swizzledSelector = @selector(descriptionThatSays:);
  544. [GULSwizzler swizzleClass:[TestObject class]
  545. selector:swizzledSelector
  546. isClassSelector:NO
  547. withBlock:replacingBlock];
  548. XCTAssertEqualObjects([testObject descriptionThatSays:@"something"], @"somethingSWIZZLED!");
  549. IMP originalIMP = [GULSwizzler originalImplementationForClass:[TestObject class]
  550. selector:swizzledSelector
  551. isClassSelector:NO];
  552. NSString *originalDescriptionThatSaysSomething =
  553. GUL_INVOKE_ORIGINAL_IMP1(testObject, swizzledSelector, NSString *, originalIMP, @"something");
  554. XCTAssertEqualObjects(originalDescriptionThatSaysSomething, @"instance:something");
  555. [GULSwizzler unswizzleClass:[TestObject class] selector:swizzledSelector isClassSelector:NO];
  556. }
  557. /** Tests invoking an original class IMP that takes one argument. */
  558. - (void)testInvokingOriginalClassIMPWithOneArgument {
  559. NSString * (^replacingBlock)(id, NSString *) = ^NSString *(id _self, NSString *something) {
  560. return [something stringByAppendingString:@"SWIZZLED!"];
  561. };
  562. SEL swizzledSelector = @selector(descriptionThatSays:);
  563. [GULSwizzler swizzleClass:[TestObject class]
  564. selector:swizzledSelector
  565. isClassSelector:YES
  566. withBlock:replacingBlock];
  567. XCTAssertEqualObjects([TestObject descriptionThatSays:@"something"], @"somethingSWIZZLED!");
  568. IMP originalIMP = [GULSwizzler originalImplementationForClass:[TestObject class]
  569. selector:swizzledSelector
  570. isClassSelector:YES];
  571. NSString *originalDescriptionThatSaysSomething = GUL_INVOKE_ORIGINAL_IMP1(
  572. [TestObject class], swizzledSelector, NSString *, originalIMP, @"something");
  573. XCTAssertEqualObjects(originalDescriptionThatSaysSomething, @"class:something");
  574. [GULSwizzler unswizzleClass:[TestObject class] selector:swizzledSelector isClassSelector:YES];
  575. }
  576. /** Tests swizzling the same class SEL pair again works for class methods. */
  577. - (void)testSwizzlingSameClassSELPairOnClassMethodWorksCorrectly {
  578. NSString * (^replacingBlock1)(id, NSString *) = ^NSString *(id _self, NSString *something) {
  579. return [something stringByAppendingString:@"SWIZZLED!"];
  580. };
  581. NSString * (^replacingBlock2)(id, NSString *) = ^NSString *(id _self, NSString *something) {
  582. return [something stringByAppendingString:@"SWIZZLED2!"];
  583. };
  584. SEL swizzledSelector = @selector(descriptionThatSays:);
  585. Class swizzledClass = [TestObject class];
  586. [GULSwizzler swizzleClass:swizzledClass
  587. selector:swizzledSelector
  588. isClassSelector:YES
  589. withBlock:replacingBlock1];
  590. XCTAssertEqualObjects([TestObject descriptionThatSays:@"something"], @"somethingSWIZZLED!");
  591. [GULSwizzler swizzleClass:swizzledClass
  592. selector:swizzledSelector
  593. isClassSelector:YES
  594. withBlock:replacingBlock2];
  595. XCTAssertEqualObjects([TestObject descriptionThatSays:@"something"], @"somethingSWIZZLED2!");
  596. [GULSwizzler unswizzleClass:[TestObject class] selector:swizzledSelector isClassSelector:YES];
  597. }
  598. /** Tests swizzling the same class SEL pair again works for instance methods. */
  599. - (void)testSwizzlingSameClassSELPairOnInstanceMethodWorksCorrectly {
  600. NSString * (^replacingBlock1)(id, NSString *) = ^NSString *(id _self, NSString *something) {
  601. return [something stringByAppendingString:@"SWIZZLED!"];
  602. };
  603. NSString * (^replacingBlock2)(id, NSString *) = ^NSString *(id _self, NSString *something) {
  604. return [something stringByAppendingString:@"SWIZZLED2!"];
  605. };
  606. TestObject *testObject = [[TestObject alloc] init];
  607. SEL swizzledSelector = @selector(descriptionThatSays:);
  608. Class swizzledClass = [TestObject class];
  609. [GULSwizzler swizzleClass:swizzledClass
  610. selector:swizzledSelector
  611. isClassSelector:NO
  612. withBlock:replacingBlock1];
  613. XCTAssertEqualObjects([testObject descriptionThatSays:@"something"], @"somethingSWIZZLED!");
  614. [GULSwizzler swizzleClass:swizzledClass
  615. selector:swizzledSelector
  616. isClassSelector:NO
  617. withBlock:replacingBlock2];
  618. XCTAssertEqualObjects([testObject descriptionThatSays:@"something"], @"somethingSWIZZLED2!");
  619. [GULSwizzler unswizzleClass:[TestObject class] selector:swizzledSelector isClassSelector:NO];
  620. }
  621. /** Tests calling an IMP which was previously put in place by a consumer of GULSwizzler from inside
  622. * a new IMP that a consumer of GULSwizzler is putting in place of that old IMP works correctly
  623. * in case of instance methods.
  624. */
  625. - (void)testWrappingAPreviouslySwizzledClassSELPairWithANewOneWorksOnInstanceMethods {
  626. NSString * (^replacingBlock1)(id, NSString *) = ^NSString *(id _self, NSString *something) {
  627. return [something stringByAppendingString:@"SWIZZLED!"];
  628. };
  629. TestObject *testObject = [[TestObject alloc] init];
  630. SEL swizzledSelector = @selector(descriptionThatSays:);
  631. Class swizzledClass = [TestObject class];
  632. [GULSwizzler swizzleClass:swizzledClass
  633. selector:swizzledSelector
  634. isClassSelector:NO
  635. withBlock:replacingBlock1];
  636. XCTAssertEqualObjects([testObject descriptionThatSays:@"something"], @"somethingSWIZZLED!");
  637. IMP previouslySwizzledIMP = [GULSwizzler currentImplementationForClass:swizzledClass
  638. selector:swizzledSelector
  639. isClassSelector:NO];
  640. NSString * (^replacingBlock2)(id, NSString *) = ^NSString *(id _self, NSString *something) {
  641. NSString *previousResult = GUL_INVOKE_ORIGINAL_IMP1(testObject, swizzledSelector, NSString *,
  642. previouslySwizzledIMP, something);
  643. NSString *currentResult = [something stringByAppendingString:@"SWIZZLED2!"];
  644. return [previousResult stringByAppendingString:currentResult];
  645. };
  646. [GULSwizzler swizzleClass:swizzledClass
  647. selector:swizzledSelector
  648. isClassSelector:NO
  649. withBlock:replacingBlock2];
  650. XCTAssertEqualObjects([testObject descriptionThatSays:@"something"],
  651. @"somethingSWIZZLED!somethingSWIZZLED2!");
  652. XCTAssertEqualObjects([testObject descriptionThatSays:@"anything"],
  653. @"anythingSWIZZLED!anythingSWIZZLED2!");
  654. [GULSwizzler unswizzleClass:[TestObject class] selector:swizzledSelector isClassSelector:NO];
  655. }
  656. /** Tests calling an IMP which was previously put in place by a consumer of GULSwizzler from inside
  657. * a new IMP that a consumer of GULSwizzler is putting in place of that old IMP works correctly
  658. * in case of class methods.
  659. */
  660. - (void)testWrappingAPreviouslySwizzledClassSELPairWithANewOneWorksOnClassMethods {
  661. NSString * (^replacingBlock1)(id, NSString *) = ^NSString *(id _self, NSString *something) {
  662. return [something stringByAppendingString:@"SWIZZLED!"];
  663. };
  664. SEL swizzledSelector = @selector(descriptionThatSays:);
  665. Class swizzledClass = [TestObject class];
  666. [GULSwizzler swizzleClass:swizzledClass
  667. selector:swizzledSelector
  668. isClassSelector:YES
  669. withBlock:replacingBlock1];
  670. XCTAssertEqualObjects([TestObject descriptionThatSays:@"something"], @"somethingSWIZZLED!");
  671. IMP previouslySwizzledIMP = [GULSwizzler currentImplementationForClass:swizzledClass
  672. selector:swizzledSelector
  673. isClassSelector:YES];
  674. NSString * (^replacingBlock2)(id, NSString *) = ^NSString *(id _self, NSString *something) {
  675. NSString *previousResult = GUL_INVOKE_ORIGINAL_IMP1(
  676. [TestObject class], swizzledSelector, NSString *, previouslySwizzledIMP, something);
  677. NSString *currentResult = [something stringByAppendingString:@"SWIZZLED2!"];
  678. return [previousResult stringByAppendingString:currentResult];
  679. };
  680. [GULSwizzler swizzleClass:swizzledClass
  681. selector:swizzledSelector
  682. isClassSelector:YES
  683. withBlock:replacingBlock2];
  684. XCTAssertEqualObjects([TestObject descriptionThatSays:@"something"],
  685. @"somethingSWIZZLED!somethingSWIZZLED2!");
  686. XCTAssertEqualObjects([TestObject descriptionThatSays:@"anything"],
  687. @"anythingSWIZZLED!anythingSWIZZLED2!");
  688. [GULSwizzler unswizzleClass:[TestObject class] selector:swizzledSelector isClassSelector:YES];
  689. }
  690. @end