From be577e6ac29b3d2e5dd721bdf96e4a26e235bf7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agustin=20De=20Le=C3=B3n?= Date: Mon, 22 Jun 2026 13:43:41 -0300 Subject: [PATCH 01/10] Fix EXC_BAD_ACCESS in GULNetworkURLSession cleanup This commit replaces the manual O(N) cleanup loop in `setSessionInFetcherMap:forSessionID:` with an O(1) passive self-cleanup mechanism using Associated Objects. It introduces `GULSessionDeallocTracker`, which is tied to the lifecycle of the `NSURLSession`. To prevent `objc_loadWeakRetained` crashes when the session is being destroyed, the tracker holds a strong reference to the `GULNetworkURLSessionWeakHolder` wrapper and uses direct pointer comparison during `dealloc` instead of reading the weak reference. It also ensures old trackers are deallocated outside of the `NSLock` to prevent deadlocks. --- .../Network/GULNetworkURLSession.m | 54 +++++++++++++++---- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/GoogleUtilities/Network/GULNetworkURLSession.m b/GoogleUtilities/Network/GULNetworkURLSession.m index 762b0f1..b157de0 100644 --- a/GoogleUtilities/Network/GULNetworkURLSession.m +++ b/GoogleUtilities/Network/GULNetworkURLSession.m @@ -13,6 +13,7 @@ // limitations under the License. #import +#import #import "GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkURLSession.h" @@ -35,6 +36,37 @@ @interface GULNetworkURLSessionWeakHolder : NSObject @implementation GULNetworkURLSessionWeakHolder @end +@interface GULNetworkURLSession (Private) ++ (NSMutableDictionary *)sessionIDToFetcherMap; ++ (NSLock *)sessionIDToFetcherMapReadWriteLock; +@end + +@interface GULSessionDeallocTracker : NSObject +@property(nonatomic, copy) NSString *sessionID; +@property(nonatomic, strong) GULNetworkURLSessionWeakHolder *holder; +- (instancetype)initWithSessionID:(NSString *)sessionID holder:(GULNetworkURLSessionWeakHolder *)holder; +@end + +@implementation GULSessionDeallocTracker +- (instancetype)initWithSessionID:(NSString *)sessionID holder:(GULNetworkURLSessionWeakHolder *)holder { + self = [super init]; + if (self) { + _sessionID = [sessionID copy]; + _holder = holder; + } + return self; +} + +- (void)dealloc { + [[GULNetworkURLSession sessionIDToFetcherMapReadWriteLock] lock]; + GULNetworkURLSessionWeakHolder *currentDictionaryHolder = [[GULNetworkURLSession sessionIDToFetcherMap] objectForKey:_sessionID]; + if (currentDictionaryHolder == _holder) { + [[GULNetworkURLSession sessionIDToFetcherMap] removeObjectForKey:_sessionID]; + } + [[GULNetworkURLSession sessionIDToFetcherMapReadWriteLock] unlock]; +} +@end + @implementation GULNetworkURLSession { /// The handler to be called when the request completes or error has occurs. GULNetworkURLSessionCompletionHandler _completionHandler; @@ -678,10 +710,15 @@ - (void)URLSession:(NSURLSession *)session #pragma mark - Helper Methods +static const void *kGULSessionTrackerKey = &kGULSessionTrackerKey; + + (void)setSessionInFetcherMap:(GULNetworkURLSession *)session forSessionID:(NSString *)sessionID { if (!sessionID) { return; } + + GULSessionDeallocTracker *oldTrackerToReleaseOutsideLock = nil; + [[self sessionIDToFetcherMapReadWriteLock] lock]; GULNetworkURLSessionWeakHolder *holder = [[[self class] sessionIDToFetcherMap] objectForKey:sessionID]; @@ -693,27 +730,22 @@ + (void)setSessionInFetcherMap:(GULNetworkURLSession *)session forSessionID:(NSS messageCode:kGULNetworkMessageCodeURLSession019 message:message]; } + oldTrackerToReleaseOutsideLock = objc_getAssociatedObject(existingSession, kGULSessionTrackerKey); + objc_setAssociatedObject(existingSession, kGULSessionTrackerKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [existingSession->_URLSession finishTasksAndInvalidate]; } if (session) { - // Cleanup nil entries. - NSMutableArray *keysToRemove = [[NSMutableArray alloc] init]; - [[[self class] sessionIDToFetcherMap] - enumerateKeysAndObjectsUsingBlock:^(NSString *key, GULNetworkURLSessionWeakHolder *holder, - BOOL *stop) { - if (holder.session == nil) { - [keysToRemove addObject:key]; - } - }]; - [[[self class] sessionIDToFetcherMap] removeObjectsForKeys:keysToRemove]; - GULNetworkURLSessionWeakHolder *newHolder = [[GULNetworkURLSessionWeakHolder alloc] init]; newHolder.session = session; [[[self class] sessionIDToFetcherMap] setObject:newHolder forKey:sessionID]; + + GULSessionDeallocTracker *tracker = [[GULSessionDeallocTracker alloc] initWithSessionID:sessionID holder:newHolder]; + objc_setAssociatedObject(session, kGULSessionTrackerKey, tracker, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } else { [[[self class] sessionIDToFetcherMap] removeObjectForKey:sessionID]; } [[self sessionIDToFetcherMapReadWriteLock] unlock]; + // oldTrackerToReleaseOutsideLock deallocates here, outside the lock. } + (nullable GULNetworkURLSession *)sessionFromFetcherMapForSessionID:(NSString *)sessionID { From 3a659db4891936f48d28e2fc003532fa9a2c171f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agustin=20De=20Le=C3=B3n?= Date: Mon, 22 Jun 2026 14:57:25 -0300 Subject: [PATCH 02/10] Refactor: explicitly nil out old tracker to silence potential unused variable warnings --- GoogleUtilities/Network/GULNetworkURLSession.m | 1 + 1 file changed, 1 insertion(+) diff --git a/GoogleUtilities/Network/GULNetworkURLSession.m b/GoogleUtilities/Network/GULNetworkURLSession.m index b157de0..ba66a2c 100644 --- a/GoogleUtilities/Network/GULNetworkURLSession.m +++ b/GoogleUtilities/Network/GULNetworkURLSession.m @@ -746,6 +746,7 @@ + (void)setSessionInFetcherMap:(GULNetworkURLSession *)session forSessionID:(NSS } [[self sessionIDToFetcherMapReadWriteLock] unlock]; // oldTrackerToReleaseOutsideLock deallocates here, outside the lock. + oldTrackerToReleaseOutsideLock = nil; } + (nullable GULNetworkURLSession *)sessionFromFetcherMapForSessionID:(NSString *)sessionID { From 37b9bcef771c4669b623df55a9c8fbd276b45e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agustin=20De=20Le=C3=B3n?= Date: Mon, 22 Jun 2026 14:59:33 -0300 Subject: [PATCH 03/10] Refactor: remove redundant comment --- GoogleUtilities/Network/GULNetworkURLSession.m | 1 - 1 file changed, 1 deletion(-) diff --git a/GoogleUtilities/Network/GULNetworkURLSession.m b/GoogleUtilities/Network/GULNetworkURLSession.m index ba66a2c..74b3625 100644 --- a/GoogleUtilities/Network/GULNetworkURLSession.m +++ b/GoogleUtilities/Network/GULNetworkURLSession.m @@ -745,7 +745,6 @@ + (void)setSessionInFetcherMap:(GULNetworkURLSession *)session forSessionID:(NSS [[[self class] sessionIDToFetcherMap] removeObjectForKey:sessionID]; } [[self sessionIDToFetcherMapReadWriteLock] unlock]; - // oldTrackerToReleaseOutsideLock deallocates here, outside the lock. oldTrackerToReleaseOutsideLock = nil; } From 337850def263a5b427c0c498387c58daafdfb72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agustin=20De=20Le=C3=B3n?= Date: Tue, 23 Jun 2026 13:58:07 -0300 Subject: [PATCH 04/10] Fixing linter and style --- .../Network/GULNetworkURLSession.m | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/GoogleUtilities/Network/GULNetworkURLSession.m b/GoogleUtilities/Network/GULNetworkURLSession.m index 74b3625..dfc7c8c 100644 --- a/GoogleUtilities/Network/GULNetworkURLSession.m +++ b/GoogleUtilities/Network/GULNetworkURLSession.m @@ -44,11 +44,13 @@ + (NSLock *)sessionIDToFetcherMapReadWriteLock; @interface GULSessionDeallocTracker : NSObject @property(nonatomic, copy) NSString *sessionID; @property(nonatomic, strong) GULNetworkURLSessionWeakHolder *holder; -- (instancetype)initWithSessionID:(NSString *)sessionID holder:(GULNetworkURLSessionWeakHolder *)holder; +- (instancetype)initWithSessionID:(NSString *)sessionID + holder:(GULNetworkURLSessionWeakHolder *)holder; @end @implementation GULSessionDeallocTracker -- (instancetype)initWithSessionID:(NSString *)sessionID holder:(GULNetworkURLSessionWeakHolder *)holder { +- (instancetype)initWithSessionID:(NSString *)sessionID + holder:(GULNetworkURLSessionWeakHolder *)holder { self = [super init]; if (self) { _sessionID = [sessionID copy]; @@ -59,7 +61,8 @@ - (instancetype)initWithSessionID:(NSString *)sessionID holder:(GULNetworkURLSes - (void)dealloc { [[GULNetworkURLSession sessionIDToFetcherMapReadWriteLock] lock]; - GULNetworkURLSessionWeakHolder *currentDictionaryHolder = [[GULNetworkURLSession sessionIDToFetcherMap] objectForKey:_sessionID]; + GULNetworkURLSessionWeakHolder *currentDictionaryHolder = + [[GULNetworkURLSession sessionIDToFetcherMap] objectForKey:_sessionID]; if (currentDictionaryHolder == _holder) { [[GULNetworkURLSession sessionIDToFetcherMap] removeObjectForKey:_sessionID]; } @@ -716,9 +719,9 @@ + (void)setSessionInFetcherMap:(GULNetworkURLSession *)session forSessionID:(NSS if (!sessionID) { return; } - + GULSessionDeallocTracker *oldTrackerToReleaseOutsideLock = nil; - + [[self sessionIDToFetcherMapReadWriteLock] lock]; GULNetworkURLSessionWeakHolder *holder = [[[self class] sessionIDToFetcherMap] objectForKey:sessionID]; @@ -730,8 +733,10 @@ + (void)setSessionInFetcherMap:(GULNetworkURLSession *)session forSessionID:(NSS messageCode:kGULNetworkMessageCodeURLSession019 message:message]; } - oldTrackerToReleaseOutsideLock = objc_getAssociatedObject(existingSession, kGULSessionTrackerKey); - objc_setAssociatedObject(existingSession, kGULSessionTrackerKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + oldTrackerToReleaseOutsideLock = + objc_getAssociatedObject(existingSession, kGULSessionTrackerKey); + objc_setAssociatedObject(existingSession, kGULSessionTrackerKey, nil, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); [existingSession->_URLSession finishTasksAndInvalidate]; } if (session) { @@ -739,8 +744,10 @@ + (void)setSessionInFetcherMap:(GULNetworkURLSession *)session forSessionID:(NSS newHolder.session = session; [[[self class] sessionIDToFetcherMap] setObject:newHolder forKey:sessionID]; - GULSessionDeallocTracker *tracker = [[GULSessionDeallocTracker alloc] initWithSessionID:sessionID holder:newHolder]; - objc_setAssociatedObject(session, kGULSessionTrackerKey, tracker, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + GULSessionDeallocTracker *tracker = + [[GULSessionDeallocTracker alloc] initWithSessionID:sessionID holder:newHolder]; + objc_setAssociatedObject(session, kGULSessionTrackerKey, tracker, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); } else { [[[self class] sessionIDToFetcherMap] removeObjectForKey:sessionID]; } From 4b2a1c880fe6f238e5c730ac614b8f9f93e4dd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agustin=20De=20Le=C3=B3n?= Date: Tue, 23 Jun 2026 15:31:11 -0300 Subject: [PATCH 05/10] Fix dead store warning for static analysis in GULNetworkURLSession --- GoogleUtilities/Network/GULNetworkURLSession.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoogleUtilities/Network/GULNetworkURLSession.m b/GoogleUtilities/Network/GULNetworkURLSession.m index dfc7c8c..3c39bf0 100644 --- a/GoogleUtilities/Network/GULNetworkURLSession.m +++ b/GoogleUtilities/Network/GULNetworkURLSession.m @@ -752,7 +752,7 @@ + (void)setSessionInFetcherMap:(GULNetworkURLSession *)session forSessionID:(NSS [[[self class] sessionIDToFetcherMap] removeObjectForKey:sessionID]; } [[self sessionIDToFetcherMapReadWriteLock] unlock]; - oldTrackerToReleaseOutsideLock = nil; + (void)oldTrackerToReleaseOutsideLock; } + (nullable GULNetworkURLSession *)sessionFromFetcherMapForSessionID:(NSString *)sessionID { From eec0d4007c8e107499fb5beea9423f7b261bd8ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agustin=20De=20Le=C3=B3n?= Date: Tue, 23 Jun 2026 16:23:25 -0300 Subject: [PATCH 06/10] Fix nil key exception in GULRuntimeSnapshot by verifying classString --- .../SwizzlerTestHelpers/GULRuntimeSnapshot.m | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/GoogleUtilities/SwizzlerTestHelpers/GULRuntimeSnapshot.m b/GoogleUtilities/SwizzlerTestHelpers/GULRuntimeSnapshot.m index e358b25..00b850c 100644 --- a/GoogleUtilities/SwizzlerTestHelpers/GULRuntimeSnapshot.m +++ b/GoogleUtilities/SwizzlerTestHelpers/GULRuntimeSnapshot.m @@ -76,11 +76,13 @@ - (void)capture { for (int i = 0; i < numberOfClasses; i++) { Class aClass = classList[i]; NSString *classString = NSStringFromClass(aClass); - GULRuntimeClassSnapshot *classSnapshot = - [[GULRuntimeClassSnapshot alloc] initWithClass:aClass]; - _classSnapshots[classString] = classSnapshot; - [classSnapshot capture]; - _runningHash ^= [classSnapshot hash]; + if (classString) { + GULRuntimeClassSnapshot *classSnapshot = + [[GULRuntimeClassSnapshot alloc] initWithClass:aClass]; + _classSnapshots[classString] = classSnapshot; + [classSnapshot capture]; + _runningHash ^= [classSnapshot hash]; + } } } free(classList); From fed00699e94607dd5c3e5af758189ec931bd8a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agustin=20De=20Le=C3=B3n?= Date: Tue, 23 Jun 2026 16:53:19 -0300 Subject: [PATCH 07/10] Address PR feedback: Add deadlock prevention comment and parameter assert --- GoogleUtilities/Network/GULNetworkURLSession.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GoogleUtilities/Network/GULNetworkURLSession.m b/GoogleUtilities/Network/GULNetworkURLSession.m index 3c39bf0..6c7e684 100644 --- a/GoogleUtilities/Network/GULNetworkURLSession.m +++ b/GoogleUtilities/Network/GULNetworkURLSession.m @@ -53,6 +53,7 @@ - (instancetype)initWithSessionID:(NSString *)sessionID holder:(GULNetworkURLSessionWeakHolder *)holder { self = [super init]; if (self) { + NSParameterAssert(sessionID); _sessionID = [sessionID copy]; _holder = holder; } @@ -752,6 +753,9 @@ + (void)setSessionInFetcherMap:(GULNetworkURLSession *)session forSessionID:(NSS [[[self class] sessionIDToFetcherMap] removeObjectForKey:sessionID]; } [[self sessionIDToFetcherMapReadWriteLock] unlock]; + + // Retain the old tracker until the lock is released. If it deallocates inside the lock, + // its -dealloc method will attempt to re-acquire the lock, resulting in a deadlock. (void)oldTrackerToReleaseOutsideLock; } From cd2f8dec0bf1873e3487118c2e62675cadf67955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agustin=20De=20Le=C3=B3n?= Date: Tue, 23 Jun 2026 17:52:15 -0300 Subject: [PATCH 08/10] Increase timeout for testSessionMapStress to 60s --- GoogleUtilities/Tests/Unit/Network/GULMutableDictionaryTest.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoogleUtilities/Tests/Unit/Network/GULMutableDictionaryTest.m b/GoogleUtilities/Tests/Unit/Network/GULMutableDictionaryTest.m index 8c0824f..618ce97 100644 --- a/GoogleUtilities/Tests/Unit/Network/GULMutableDictionaryTest.m +++ b/GoogleUtilities/Tests/Unit/Network/GULMutableDictionaryTest.m @@ -129,7 +129,7 @@ - (void)testSessionMapStress { [expectation fulfill]; }); - [self waitForExpectationsWithTimeout:30 handler:nil]; + [self waitForExpectationsWithTimeout:60 handler:nil]; } @end From 5e7d4207e56eda8c24c32f2da2f5e5070be94e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agustin=20De=20Le=C3=B3n?= Date: Tue, 23 Jun 2026 21:40:50 -0300 Subject: [PATCH 09/10] Refactor: remove misplaced stress test and add explicit test for passive ARC cleanup in GULNetworkURLSession --- .../Unit/Network/GULMutableDictionaryTest.m | 48 ------------------- .../Tests/Unit/Network/GULNetworkTest.m | 30 ++++++++++++ 2 files changed, 30 insertions(+), 48 deletions(-) diff --git a/GoogleUtilities/Tests/Unit/Network/GULMutableDictionaryTest.m b/GoogleUtilities/Tests/Unit/Network/GULMutableDictionaryTest.m index 618ce97..50cc812 100644 --- a/GoogleUtilities/Tests/Unit/Network/GULMutableDictionaryTest.m +++ b/GoogleUtilities/Tests/Unit/Network/GULMutableDictionaryTest.m @@ -15,12 +15,6 @@ #import #import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h" -#import "GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkURLSession.h" - -@interface GULNetworkURLSession (Test) -+ (void)setSessionInFetcherMap:(GULNetworkURLSession *)session forSessionID:(NSString *)sessionID; -+ (nullable GULNetworkURLSession *)sessionFromFetcherMapForSessionID:(NSString *)sessionID; -@end const static NSString *const kKey = @"testKey1"; const static NSString *const kValue = @"testValue1"; @@ -90,46 +84,4 @@ - (void)testUnderlyingDictionary { XCTAssertEqual(dict[kKey2], kValue2); } -- (void)testSessionMapStress { - int iterations = 1000; - int numSessionIDs = 10; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Stress test finished"]; - - dispatch_queue_t queue = - dispatch_queue_create("com.google.utilities.stress", DISPATCH_QUEUE_CONCURRENT); - - dispatch_group_t group = dispatch_group_create(); - - // Reader threads - for (int t = 0; t < 5; t++) { - dispatch_group_async(group, queue, ^{ - for (int i = 0; i < iterations; i++) { - int id_idx = i % numSessionIDs; - NSString *sessionID = [NSString stringWithFormat:@"session_%d", id_idx]; - [GULNetworkURLSession sessionFromFetcherMapForSessionID:sessionID]; - } - }); - } - - // Writer threads - for (int t = 0; t < 5; t++) { - dispatch_group_async(group, queue, ^{ - for (int i = 0; i < iterations; i++) { - int id_idx = i % numSessionIDs; - NSString *sessionID = [NSString stringWithFormat:@"session_%d", id_idx]; - GULNetworkURLSession *session = - [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:nil]; - [GULNetworkURLSession setSessionInFetcherMap:session forSessionID:sessionID]; - } - }); - } - - dispatch_group_notify(group, dispatch_get_main_queue(), ^{ - [expectation fulfill]; - }); - - [self waitForExpectationsWithTimeout:60 handler:nil]; -} - @end diff --git a/GoogleUtilities/Tests/Unit/Network/GULNetworkTest.m b/GoogleUtilities/Tests/Unit/Network/GULNetworkTest.m index c6bb89f..58e9f3d 100644 --- a/GoogleUtilities/Tests/Unit/Network/GULNetworkTest.m +++ b/GoogleUtilities/Tests/Unit/Network/GULNetworkTest.m @@ -122,6 +122,36 @@ - (void)testReachability { reachabilityMock = nil; } +#pragma mark - Test Passive Deallocation + +- (void)testSessionPassiveRemovalOnDeallocation { + NSString *testSessionID = @"test_passive_removal_id"; + + @autoreleasepool { + // 1. Create a session inside a limited scope. + GULNetworkURLSession *session = + [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:nil]; + + // 2. Insert it into the fetcher map (which stores a WeakHolder and sets an Associated Object). + [GULNetworkURLSession setSessionInFetcherMap:session forSessionID:testSessionID]; + + // 3. Verify it is accessible from the map. + GULNetworkURLSession *retrievedSession = + [GULNetworkURLSession sessionFromFetcherMapForSessionID:testSessionID]; + XCTAssertEqualObjects(session, retrievedSession, @"Session should be in the fetcher map."); + } + // 4. Exiting the autoreleasepool destroys the 'session' local variable. + // ARC calls -dealloc on the session, which triggers the destruction of its + // Associated Objects (including the GULSessionDeallocTracker), which in turn + // cleans up the sessionID from the fetcher map. + + // 5. Verify the session was passively removed. + GULNetworkURLSession *retrievedAfterDealloc = + [GULNetworkURLSession sessionFromFetcherMapForSessionID:testSessionID]; + XCTAssertNil(retrievedAfterDealloc, + @"Session should be automatically removed from fetcher map upon deallocation."); +} + #pragma mark - Test POST Foreground - (void)testSessionNetwork_POST_foreground { From f3469120ab5119febd372a20610c107290e2faa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agustin=20De=20Le=C3=B3n?= Date: Wed, 24 Jun 2026 07:42:42 -0300 Subject: [PATCH 10/10] Increase Keychain test expectation timeouts to reduce CI flakiness --- .../Tests/Unit/Environment/GULKeychainStorageTests.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/GoogleUtilities/Tests/Unit/Environment/GULKeychainStorageTests.m b/GoogleUtilities/Tests/Unit/Environment/GULKeychainStorageTests.m index 12160c6..05d0af8 100644 --- a/GoogleUtilities/Tests/Unit/Environment/GULKeychainStorageTests.m +++ b/GoogleUtilities/Tests/Unit/Environment/GULKeychainStorageTests.m @@ -133,7 +133,7 @@ - (void)testGetExistingObjectClassMismatch { OCMVerifyAll(self.mockCache); [expectation fulfill]; }]; - [self waitForExpectations:@[ expectation ] timeout:1.0]; + [self waitForExpectations:@[ expectation ] timeout:5.0]; } - (void)testRemoveExistingObject { @@ -174,7 +174,7 @@ - (void)assertSuccessWriteObject:(id)object forKey:(NSString *)k [expectation fulfill]; }]; - [self waitForExpectations:@[ expectation ] timeout:1.0]; + [self waitForExpectations:@[ expectation ] timeout:5.0]; OCMVerifyAll(self.mockCache); } @@ -204,7 +204,7 @@ - (void)assertSuccessReadObject:(id)object XCTAssertEqualObjects([weakSelf.cache objectForKey:key], object, @"%@", weakSelf.name); [expectation fulfill]; }]; - [self waitForExpectations:@[ expectation ] timeout:1.0]; + [self waitForExpectations:@[ expectation ] timeout:5.0]; OCMVerifyAll(self.mockCache); } @@ -224,7 +224,7 @@ - (void)assertNonExistingObjectForKey:(NSString *)key class:(Class)class { XCTAssertNil(obj, @"%@", weakSelf.name); [expectation fulfill]; }]; - [self waitForExpectations:@[ expectation ] timeout:1.0]; + [self waitForExpectations:@[ expectation ] timeout:5.0]; OCMVerifyAll(self.mockCache); } @@ -238,7 +238,7 @@ - (void)assertRemoveObjectForKey:(NSString *)key { XCTAssertNil(error); [expectation fulfill]; }]; - [self waitForExpectations:@[ expectation ] timeout:1.0]; + [self waitForExpectations:@[ expectation ] timeout:5.0]; OCMVerifyAll(self.mockCache); }