GIDEMMErrorHandlerTest.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. // Copyright 2021 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 <TargetConditionals.h>
  15. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  16. #import <UIKit/UIKit.h>
  17. #import <XCTest/XCTest.h>
  18. #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h"
  19. #import "GoogleSignIn/Sources/GIDSignInStrings.h"
  20. #import "GoogleSignIn/Tests/Unit/UIAlertAction+Testing.h"
  21. #ifdef SWIFT_PACKAGE
  22. @import GoogleUtilities_MethodSwizzler;
  23. @import GoogleUtilities_SwizzlerTestHelpers;
  24. @import OCMock;
  25. #else
  26. #import <GoogleUtilities/GULSwizzler.h>
  27. #import <GoogleUtilities/GULSwizzler+Unswizzle.h>
  28. #import <OCMock/OCMock.h>
  29. #endif
  30. NS_ASSUME_NONNULL_BEGIN
  31. // Unit test for GIDEMMErrorHandler.
  32. @interface GIDEMMErrorHandlerTest : XCTestCase
  33. @end
  34. @implementation GIDEMMErrorHandlerTest {
  35. // Whether or not the current device runs on iOS 10.
  36. BOOL _isIOS10;
  37. // Whether key window has been set.
  38. BOOL _keyWindowSet;
  39. // The view controller that has been presented, if any.
  40. UIViewController *_presentedViewController;
  41. }
  42. - (void)setUp {
  43. [super setUp];
  44. _isIOS10 = [UIDevice currentDevice].systemVersion.integerValue == 10;
  45. _keyWindowSet = NO;
  46. _presentedViewController = nil;
  47. UIWindow *fakeKeyWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  48. [GULSwizzler swizzleClass:[GIDEMMErrorHandler class]
  49. selector:@selector(keyWindow)
  50. isClassSelector:NO
  51. withBlock:^() { return fakeKeyWindow; }];
  52. [GULSwizzler swizzleClass:[UIWindow class]
  53. selector:@selector(makeKeyAndVisible)
  54. isClassSelector:NO
  55. withBlock:^() { self->_keyWindowSet = YES; }];
  56. [GULSwizzler swizzleClass:[UIViewController class]
  57. selector:@selector(presentViewController:animated:completion:)
  58. isClassSelector:NO
  59. withBlock:^(id obj, id arg1) { self->_presentedViewController = arg1; }];
  60. [GULSwizzler swizzleClass:[GIDSignInStrings class]
  61. selector:@selector(localizedStringForKey:text:)
  62. isClassSelector:YES
  63. withBlock:^(id obj, NSString *key, NSString *text) { return text; }];
  64. }
  65. - (void)tearDown {
  66. [GULSwizzler unswizzleClass:[GIDEMMErrorHandler class]
  67. selector:@selector(keyWindow)
  68. isClassSelector:NO];
  69. [GULSwizzler unswizzleClass:[UIWindow class]
  70. selector:@selector(makeKeyAndVisible)
  71. isClassSelector:NO];
  72. [GULSwizzler unswizzleClass:[UIViewController class]
  73. selector:@selector(presentViewController:animated:completion:)
  74. isClassSelector:NO];
  75. [GULSwizzler unswizzleClass:[GIDSignInStrings class]
  76. selector:@selector(localizedStringForKey:text:)
  77. isClassSelector:YES];
  78. _presentedViewController = nil;
  79. [super tearDown];
  80. }
  81. // Expects opening a particular URL string in performing an action.
  82. - (void)expectOpenURLString:(NSString *)urlString inAction:(void (^)(void))action {
  83. // Swizzle and mock [UIApplication sharedApplication] since it is unavailable in unit tests.
  84. id mockApplication = OCMStrictClassMock([UIApplication class]);
  85. [GULSwizzler swizzleClass:[UIApplication class]
  86. selector:@selector(sharedApplication)
  87. isClassSelector:YES
  88. withBlock:^() { return mockApplication; }];
  89. [[mockApplication expect] openURL:[NSURL URLWithString:urlString] options:@{} completionHandler:nil];
  90. action();
  91. [mockApplication verify];
  92. [GULSwizzler unswizzleClass:[UIApplication class]
  93. selector:@selector(sharedApplication)
  94. isClassSelector:YES];
  95. }
  96. // Verifies that the handler doesn't handle non-exist error.
  97. - (void)testNoError {
  98. __block BOOL completionCalled = NO;
  99. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:@{ @"abc" : @123 }
  100. completion:^() {
  101. completionCalled = YES;
  102. }];
  103. XCTAssertFalse(result);
  104. XCTAssertTrue(completionCalled);
  105. XCTAssertFalse(_keyWindowSet);
  106. XCTAssertNil(_presentedViewController);
  107. }
  108. // Verifies that the handler doesn't handle non-EMM error.
  109. - (void)testNoEMMError {
  110. __block BOOL completionCalled = NO;
  111. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"invalid_token" };
  112. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  113. completion:^() {
  114. completionCalled = YES;
  115. }];
  116. XCTAssertFalse(result);
  117. XCTAssertTrue(completionCalled);
  118. XCTAssertFalse(_keyWindowSet);
  119. XCTAssertNil(_presentedViewController);
  120. }
  121. // Verifies that the handler handles general EMM error with user tapping 'OK'.
  122. - (void)testGeneralEMMErrorOK {
  123. __block BOOL completionCalled = NO;
  124. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_something_wrong" };
  125. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  126. completion:^() {
  127. completionCalled = YES;
  128. }];
  129. if (![UIAlertController class]) {
  130. XCTAssertFalse(result);
  131. XCTAssertTrue(completionCalled);
  132. XCTAssertFalse(_keyWindowSet);
  133. XCTAssertNil(_presentedViewController);
  134. return;
  135. }
  136. XCTAssertTrue(result);
  137. XCTAssertFalse(completionCalled);
  138. XCTAssertFalse(_keyWindowSet);
  139. XCTAssertNil(_presentedViewController);
  140. // Should handle no more error while the previous one is being handled.
  141. __block BOOL secondCompletionCalled = NO;
  142. BOOL secondResult = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  143. completion:^() {
  144. secondCompletionCalled = YES;
  145. }];
  146. XCTAssertFalse(secondResult);
  147. XCTAssertTrue(secondCompletionCalled);
  148. XCTAssertFalse(_keyWindowSet);
  149. XCTAssertNil(_presentedViewController);
  150. // Wait for the code under test to be executed on the main thread.
  151. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  152. dispatch_async(dispatch_get_main_queue(), ^() {
  153. [expectation fulfill];
  154. });
  155. [self waitForExpectationsWithTimeout:1 handler:nil];
  156. XCTAssertFalse(completionCalled);
  157. XCTAssertTrue(_keyWindowSet);
  158. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  159. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  160. XCTAssertNotNil(alert.title);
  161. XCTAssertNotNil(alert.message);
  162. XCTAssertEqual(alert.actions.count, 1);
  163. // Pretend to touch the "OK" button.
  164. UIAlertAction *action = alert.actions[0];
  165. XCTAssertEqualObjects(action.title, @"OK");
  166. action.actionHandler(action);
  167. XCTAssertTrue(completionCalled);
  168. }
  169. // Verifies that the handler handles EMM screenlock required error with user tapping 'Cancel'.
  170. - (void)testScreenlockRequiredCancel {
  171. if (_isIOS10) {
  172. // The dialog is different on iOS 10.
  173. return;
  174. }
  175. __block BOOL completionCalled = NO;
  176. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_passcode_required" };
  177. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  178. completion:^() {
  179. completionCalled = YES;
  180. }];
  181. if (![UIAlertController class]) {
  182. XCTAssertFalse(result);
  183. XCTAssertTrue(completionCalled);
  184. XCTAssertFalse(_keyWindowSet);
  185. XCTAssertNil(_presentedViewController);
  186. return;
  187. }
  188. XCTAssertTrue(result);
  189. XCTAssertFalse(completionCalled);
  190. XCTAssertFalse(_keyWindowSet);
  191. XCTAssertNil(_presentedViewController);
  192. // Wait for the code under test to be executed on the main thread.
  193. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  194. dispatch_async(dispatch_get_main_queue(), ^() {
  195. [expectation fulfill];
  196. });
  197. [self waitForExpectationsWithTimeout:1 handler:nil];
  198. XCTAssertFalse(completionCalled);
  199. XCTAssertTrue(_keyWindowSet);
  200. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  201. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  202. XCTAssertNotNil(alert.title);
  203. XCTAssertNotNil(alert.message);
  204. XCTAssertEqual(alert.actions.count, 2);
  205. // Pretend to touch the "Cancel" button.
  206. UIAlertAction *action = alert.actions[0];
  207. XCTAssertEqualObjects(action.title, @"Cancel");
  208. action.actionHandler(action);
  209. XCTAssertTrue(completionCalled);
  210. }
  211. // Verifies that the handler handles EMM screenlock required error with user tapping 'Settings'.
  212. - (void)testScreenlockRequiredSettings {
  213. if (_isIOS10) {
  214. // The dialog is different on iOS 10.
  215. return;
  216. }
  217. __block BOOL completionCalled = NO;
  218. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_passcode_required" };
  219. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  220. completion:^() {
  221. completionCalled = YES;
  222. }];
  223. if (![UIAlertController class]) {
  224. XCTAssertFalse(result);
  225. XCTAssertTrue(completionCalled);
  226. XCTAssertFalse(_keyWindowSet);
  227. XCTAssertNil(_presentedViewController);
  228. return;
  229. }
  230. XCTAssertTrue(result);
  231. XCTAssertFalse(completionCalled);
  232. XCTAssertFalse(_keyWindowSet);
  233. XCTAssertNil(_presentedViewController);
  234. // Wait for the code under test to be executed on the main thread.
  235. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  236. dispatch_async(dispatch_get_main_queue(), ^() {
  237. [expectation fulfill];
  238. });
  239. [self waitForExpectationsWithTimeout:1 handler:nil];
  240. XCTAssertFalse(completionCalled);
  241. XCTAssertTrue(_keyWindowSet);
  242. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  243. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  244. XCTAssertNotNil(alert.title);
  245. XCTAssertNotNil(alert.message);
  246. XCTAssertEqual(alert.actions.count, 2);
  247. // Pretend to touch the "Settings" button.
  248. UIAlertAction *action = alert.actions[1];
  249. XCTAssertEqualObjects(action.title, @"Settings");
  250. [self expectOpenURLString:UIApplicationOpenSettingsURLString inAction:^() {
  251. action.actionHandler(action);
  252. }];
  253. XCTAssertTrue(completionCalled);
  254. }
  255. - (void)testScreenlockRequiredOkOnIOS10 {
  256. if (!_isIOS10) {
  257. // A more useful dialog is used for other iOS versions.
  258. return;
  259. }
  260. __block BOOL completionCalled = NO;
  261. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_passcode_required" };
  262. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  263. completion:^() {
  264. completionCalled = YES;
  265. }];
  266. if (![UIAlertController class]) {
  267. XCTAssertFalse(result);
  268. XCTAssertTrue(completionCalled);
  269. XCTAssertFalse(_keyWindowSet);
  270. XCTAssertNil(_presentedViewController);
  271. return;
  272. }
  273. XCTAssertTrue(result);
  274. XCTAssertFalse(completionCalled);
  275. XCTAssertFalse(_keyWindowSet);
  276. XCTAssertNil(_presentedViewController);
  277. // Wait for the code under test to be executed on the main thread.
  278. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  279. dispatch_async(dispatch_get_main_queue(), ^() {
  280. [expectation fulfill];
  281. });
  282. [self waitForExpectationsWithTimeout:1 handler:nil];
  283. XCTAssertFalse(completionCalled);
  284. XCTAssertTrue(_keyWindowSet);
  285. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  286. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  287. XCTAssertNotNil(alert.title);
  288. XCTAssertNotNil(alert.message);
  289. XCTAssertEqual(alert.actions.count, 1);
  290. // Pretend to touch the "OK" button.
  291. UIAlertAction *action = alert.actions[0];
  292. XCTAssertEqualObjects(action.title, @"OK");
  293. action.actionHandler(action);
  294. XCTAssertTrue(completionCalled);
  295. }
  296. // Verifies that the handler handles EMM app verification required error without a URL.
  297. - (void)testAppVerificationNoURL {
  298. __block BOOL completionCalled = NO;
  299. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_app_verification_required" };
  300. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  301. completion:^() {
  302. completionCalled = YES;
  303. }];
  304. if (![UIAlertController class]) {
  305. XCTAssertFalse(result);
  306. XCTAssertTrue(completionCalled);
  307. XCTAssertFalse(_keyWindowSet);
  308. XCTAssertNil(_presentedViewController);
  309. return;
  310. }
  311. XCTAssertTrue(result);
  312. XCTAssertFalse(completionCalled);
  313. XCTAssertFalse(_keyWindowSet);
  314. XCTAssertNil(_presentedViewController);
  315. // Wait for the code under test to be executed on the main thread.
  316. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  317. dispatch_async(dispatch_get_main_queue(), ^() {
  318. [expectation fulfill];
  319. });
  320. [self waitForExpectationsWithTimeout:1 handler:nil];
  321. XCTAssertFalse(completionCalled);
  322. XCTAssertTrue(_keyWindowSet);
  323. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  324. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  325. XCTAssertNotNil(alert.title);
  326. XCTAssertNotNil(alert.message);
  327. XCTAssertEqual(alert.actions.count, 1);
  328. // Pretend to touch the "OK" button.
  329. UIAlertAction *action = alert.actions[0];
  330. XCTAssertEqualObjects(action.title, @"OK");
  331. action.actionHandler(action);
  332. XCTAssertTrue(completionCalled);
  333. }
  334. // Verifies that the handler handles EMM app verification required error user tapping 'Cancel'.
  335. - (void)testAppVerificationCancel {
  336. __block BOOL completionCalled = NO;
  337. NSDictionary<NSString *, NSString *> *response =
  338. @{ @"error" : @"emm_app_verification_required: https://host.domain/path" };
  339. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  340. completion:^() {
  341. completionCalled = YES;
  342. }];
  343. if (![UIAlertController class]) {
  344. XCTAssertFalse(result);
  345. XCTAssertTrue(completionCalled);
  346. XCTAssertFalse(_keyWindowSet);
  347. XCTAssertNil(_presentedViewController);
  348. return;
  349. }
  350. XCTAssertTrue(result);
  351. XCTAssertFalse(completionCalled);
  352. XCTAssertFalse(_keyWindowSet);
  353. XCTAssertNil(_presentedViewController);
  354. // Wait for the code under test to be executed on the main thread.
  355. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  356. dispatch_async(dispatch_get_main_queue(), ^() {
  357. [expectation fulfill];
  358. });
  359. [self waitForExpectationsWithTimeout:1 handler:nil];
  360. XCTAssertFalse(completionCalled);
  361. XCTAssertTrue(_keyWindowSet);
  362. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  363. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  364. XCTAssertNotNil(alert.title);
  365. XCTAssertNotNil(alert.message);
  366. XCTAssertEqual(alert.actions.count, 2);
  367. // Pretend to touch the "Cancel" button.
  368. UIAlertAction *action = alert.actions[0];
  369. XCTAssertEqualObjects(action.title, @"Cancel");
  370. action.actionHandler(action);
  371. XCTAssertTrue(completionCalled);
  372. }
  373. // Verifies that the handler handles EMM app verification required error user tapping 'Connect'.
  374. - (void)testAppVerificationConnect {
  375. __block BOOL completionCalled = NO;
  376. NSDictionary<NSString *, NSString *> *response =
  377. @{ @"error" : @"emm_app_verification_required: https://host.domain/path" };
  378. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  379. completion:^() {
  380. completionCalled = YES;
  381. }];
  382. if (![UIAlertController class]) {
  383. XCTAssertFalse(result);
  384. XCTAssertTrue(completionCalled);
  385. XCTAssertFalse(_keyWindowSet);
  386. XCTAssertNil(_presentedViewController);
  387. return;
  388. }
  389. XCTAssertTrue(result);
  390. XCTAssertFalse(completionCalled);
  391. XCTAssertFalse(_keyWindowSet);
  392. XCTAssertNil(_presentedViewController);
  393. // Wait for the code under test to be executed on the main thread.
  394. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  395. dispatch_async(dispatch_get_main_queue(), ^() {
  396. [expectation fulfill];
  397. });
  398. [self waitForExpectationsWithTimeout:1 handler:nil];
  399. XCTAssertFalse(completionCalled);
  400. XCTAssertTrue(_keyWindowSet);
  401. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  402. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  403. XCTAssertNotNil(alert.title);
  404. XCTAssertNotNil(alert.message);
  405. XCTAssertEqual(alert.actions.count, 2);
  406. // Pretend to touch the "Connect" button.
  407. UIAlertAction *action = alert.actions[1];
  408. XCTAssertEqualObjects(action.title, @"Connect");
  409. [self expectOpenURLString:@"https://host.domain/path" inAction:^() {
  410. action.actionHandler(action);
  411. }];
  412. XCTAssertTrue(completionCalled);
  413. }
  414. // Verifies that the handler can handle sequential errors independently.
  415. - (void)testSequentialErrors {
  416. [self testGeneralEMMErrorOK];
  417. _keyWindowSet = NO;
  418. _presentedViewController = nil;
  419. [self testScreenlockRequiredCancel];
  420. }
  421. // Temporarily disable testKeyWindow for Xcode 12 and under due to unexplained failure.
  422. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
  423. // Verifies that the `keyWindow` internal method works on all OS versions as expected.
  424. - (void)testKeyWindow {
  425. // The original method has been swizzled in `setUp` so get its original implementation to test.
  426. typedef id (*KeyWindowSignature)(id, SEL);
  427. KeyWindowSignature keyWindowFunction = (KeyWindowSignature)
  428. [GULSwizzler originalImplementationForClass:[GIDEMMErrorHandler class]
  429. selector:@selector(keyWindow)
  430. isClassSelector:NO];
  431. UIWindow *mockKeyWindow = OCMClassMock([UIWindow class]);
  432. OCMStub(mockKeyWindow.isKeyWindow).andReturn(YES);
  433. UIApplication *mockApplication = OCMClassMock([UIApplication class]);
  434. [GULSwizzler swizzleClass:[UIApplication class]
  435. selector:@selector(sharedApplication)
  436. isClassSelector:YES
  437. withBlock:^{ return mockApplication; }];
  438. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
  439. if (@available(iOS 15, *)) {
  440. UIWindowScene *mockWindowScene = OCMClassMock([UIWindowScene class]);
  441. OCMStub(mockApplication.connectedScenes).andReturn(@[mockWindowScene]);
  442. OCMStub(mockWindowScene.activationState).andReturn(UISceneActivationStateForegroundActive);
  443. OCMStub(mockWindowScene.keyWindow).andReturn(mockKeyWindow);
  444. } else
  445. #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
  446. {
  447. #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_15_0
  448. if (@available(iOS 13, *)) {
  449. OCMStub(mockApplication.windows).andReturn(@[mockKeyWindow]);
  450. } else {
  451. #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_13_0
  452. OCMStub(mockApplication.keyWindow).andReturn(mockKeyWindow);
  453. #endif // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_13_0
  454. }
  455. #endif // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_15_0
  456. }
  457. UIWindow *keyWindow =
  458. keyWindowFunction([GIDEMMErrorHandler sharedInstance], @selector(keyWindow));
  459. XCTAssertEqual(keyWindow, mockKeyWindow);
  460. [GULSwizzler unswizzleClass:[UIApplication class]
  461. selector:@selector(sharedApplication)
  462. isClassSelector:YES];
  463. }
  464. #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
  465. @end
  466. NS_ASSUME_NONNULL_END
  467. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST