diff --git a/Sources/BranchSDK/BNCNetworkService.m b/Sources/BranchSDK/BNCNetworkService.m index 01c5178..9f6b4dd 100644 --- a/Sources/BranchSDK/BNCNetworkService.m +++ b/Sources/BranchSDK/BNCNetworkService.m @@ -86,6 +86,36 @@ - (instancetype) init { return self; } +// Avoid locking in dealloc; just tear down with ivars +- (void)dealloc { + NSURLSession *session = _session; + if (session) { + [session invalidateAndCancel]; + _session = nil; + } + NSOperationQueue *queue = _sessionQueue; + if (queue) { + [queue cancelAllOperations]; + _sessionQueue = nil; + } +} + +// IMPORTANT: do not touch properties here; use ivars so you don’t create a session while tearing down +- (void)cancelAllOperations { + @synchronized (self) { + NSURLSession *session = _session; + if (session) { + [session invalidateAndCancel]; + _session = nil; + } + NSOperationQueue *queue = _sessionQueue; + if (queue) { + [queue cancelAllOperations]; + _sessionQueue = nil; + } + } +} + #pragma mark - Getters & Setters - (void) setDefaultTimeoutInterval:(NSTimeInterval)defaultTimeoutInterval { @@ -183,25 +213,25 @@ - (void)startOperation:(BNCNetworkOperation *)operation { } else { [[BranchLogger shared] logError:[NSString stringWithFormat:@"Expected NSMutableURLRequest, got %@", [operation.request class]] error:nil]; } - - operation.sessionTask = [self.session dataTaskWithRequest:operation.request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + + operation.sessionTask = [self.session dataTaskWithRequest:operation.request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { operation.responseData = data; - operation.response = (NSHTTPURLResponse*) response; + operation.response = (NSHTTPURLResponse*)response; operation.error = error; - - if (operation.completionBlock) { - operation.completionBlock(operation); + + // Capture, clear, then invoke to avoid unexpected long-lived captures + void (^completion)(BNCNetworkOperation *) = operation.completionBlock; + operation.completionBlock = nil; + + if (completion) { + completion(operation); } + + // Break back-reference to the service + operation.networkService = nil; }]; [operation.sessionTask resume]; } -- (void) cancelAllOperations { - @synchronized (self) { - [self.session invalidateAndCancel]; - _session = nil; - } -} - @end diff --git a/Sources/BranchSDK/BNCPreferenceHelper.m b/Sources/BranchSDK/BNCPreferenceHelper.m index 05c4d84..2680a21 100644 --- a/Sources/BranchSDK/BNCPreferenceHelper.m +++ b/Sources/BranchSDK/BNCPreferenceHelper.m @@ -1038,23 +1038,33 @@ - (void)writeObjectToDefaults:(NSString *)key value:(NSObject *)value { } - (void)persistPrefsToDisk { - if (self.useStorage) { - @synchronized (self) { - if (!self.persistenceDict) return; - - NSData *data = [self serializePrefDict:self.persistenceDict]; - if (!data) return; - - NSURL *prefsURL = [self.class.URLForPrefsFile copy]; - NSBlockOperation *newPersistOp = [NSBlockOperation blockOperationWithBlock:^ { + if (!self.useStorage) return; + + @synchronized (self) { + if (!self.persistenceDict) return; + + NSData *data = [self serializePrefDict:self.persistenceDict]; + if (!data) return; + + NSURL *prefsURL = [self.class.URLForPrefsFile copy]; + + // Coalesce: drop older pending writes to avoid retaining many large NSData snapshots + [_persistPrefsQueue cancelAllOperations]; + + NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ + @autoreleasepool { NSError *error = nil; [data writeToURL:prefsURL options:NSDataWritingAtomic error:&error]; if (error) { [[BranchLogger shared] logWarning:@"Failed to persist preferences" error:error]; } - }]; - [_persistPrefsQueue addOperation:newPersistOp]; - } + } + }]; + + // Optional: lower priority; it’s background I/O + op.queuePriority = NSOperationQueuePriorityLow; + + [_persistPrefsQueue addOperation:op]; } } diff --git a/Sources/BranchSDK/BNCURLFilter.m b/Sources/BranchSDK/BNCURLFilter.m index 387070f..db5fb1d 100644 --- a/Sources/BranchSDK/BNCURLFilter.m +++ b/Sources/BranchSDK/BNCURLFilter.m @@ -106,19 +106,36 @@ - (void)updatePatternListFromServerWithCompletion:(void (^_Nullable) (void))comp return; } - NSString *urlString = [NSString stringWithFormat:@"%@/sdk/uriskiplist_v%ld.json", [BNCPreferenceHelper sharedInstance].patternListURL, (long) self.listVersion+1]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30.0]; + NSString *urlString = [NSString stringWithFormat:@"%@/sdk/uriskiplist_v%ld.json", + [BNCPreferenceHelper sharedInstance].patternListURL, + (long)self.listVersion + 1]; + NSMutableURLRequest *request = + [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString] + cachePolicy:NSURLRequestReloadIgnoringLocalCacheData + timeoutInterval:30.0]; __block id networkService = [[Branch networkServiceClass] new]; - id operation = [networkService networkOperationWithURLRequest:request completion: ^(id operation) { - [self processServerOperation:operation]; + + __weak typeof(self) weakSelf = self; + id operation = + [networkService networkOperationWithURLRequest:request + completion:^(id op) { + // Process result + [weakSelf processServerOperation:op]; + if (completion) { completion(); } + + // IMPORTANT: break the NSURLSession delegate retain cycle + [networkService cancelAllOperations]; // invalidates and cancels the session + networkService = nil; // drop our last reference ASAP }]; + [operation start]; } + - (BOOL)foundUpdatedURLList:(id)operation { NSInteger statusCode = operation.response.statusCode; NSError *error = operation.error;