I have just set up WCConnectivity in my app using tutorials/sample code and I feel I have it implemented correctly. I am not using the simulator to test. For some reason, the didReceiveApplicationContext is not being called in the watch app, even though everything is set up correctly.
I've tried calling in the the Interface Controller of the WatchKit app, in the ExtensionDelegate and using NSUserDefaults to set the Interface Controller data instead.
iOS App
ViewController.m
- (void) viewDidLoad{
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
}
-(void) saveTrip{
NSMutableArray *currentTrips = [NSMutableArray arrayWithArray:[self.sharedDefaults objectForKey:#"UserLocations"]];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:newLocation];
[currentTrips addObject:data];
[self.sharedDefaults setObject:currentTrips forKey:#"UserLocations"];
[self.sharedDefaults synchronize];
WCSession *session = [WCSession defaultSession];
NSDictionary *applicationDict = [[NSDictionary alloc] initWithObjects:#[currentTrips] forKeys:#[#"UserLocations"]];;
[session updateApplicationContext:applicationDict error:nil];
}
Watch Extension Code
ExtensionDelegate.m
- (void)applicationDidFinishLaunching {
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
}
- (void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *,id> *)applicationContext {
self.places= [applicationContext objectForKey:#"UserLocations"];
[[NSUserDefaults standardUserDefaults] setObject:self.places forKey:#"UserLocations"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
InterfaceController.m
- (void)willActivate {
[super willActivate];
self.placesData = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:#"UserLocations"]];
[self loadData];
}
For me it was two things:
Passing invalid values in the dictionary. Can't even pass NSNull() to represent nil values. If you have data in there that can't be represented in a plist, it fails.
If the dictionary doesn't change, subsequent calls to updateApplicationContext won't trigger a corresponding call to didReceiveApplicationContext. To force an update—perhaps in debug builds—you could add a UUID to the payload, e.g.
context["force_send"] = UUID().uuidString
It might prove useful to handle any errors, so change:
[session updateApplicationContext:applicationDict error:nil];
to
NSError *error;
if (![session updateApplicationContext:applicationDict error:&error]) {
NSLog(#"updateApplicationContext failed with error %#", error);
}
If it's not working, it's probably not implemented correctly.
A few issues at first glance:
In your iOS app, you get a reference to the shared session and activate it, but don't assign that local variable to any property on your view controller. This means your local variable will go out of scope, once viewDidLoad exits. You also need to correct that in your watch extension.
In saveTrip, you again create another local session, which isn't activated and doesn't have any delegate. You need to use the first session that you set up and activated earlier.
On your watch, you save data that is received but your interface controller won't know that there is new data that it should load and display.
A few tips:
If you setup and activate the session in application:didFinishLaunchingWithOptions, it will be available app-wide (and activated much earlier in your app's lifecycle).
You should check that your session is valid, as a watch may not be paired, or the watch app may not be installed. Here's a good (Swift) tutorial that covers those cases, and also uses a session manager to make it easier to support using Watch Connectivity throughout your app.
You may want to pass less/lighter data across to the watch, than trying to deal with archiving an array of custom objects.
As an aside, what you're trying to do with NSUserDefaults is very convoluted. It's really meant to persist preferences across launches. It's not appropriate to misuse it as a way to pass your model back and forth between your extension and controller.
I had the same issue and fixed.
I forgot to add WatchConnectivity framework in watch extension.
Hey I have made an application for my phone that can unlock/lock etc my car. Basically the iphone interface is just 4 buttons: lock, unlock, trunk, and connect. When ever I press a button a writes something to my arduino located inside of my car. I was wondering if I could "copy" these four buttons onto my apple watch. What I mean by that is that can I use openParentApplication to do that or is there some other command I could use to simulate so say button presses on my iphone when the button is clicked on my apple watch.
Code for Iphone buttons:
- (IBAction)lockCar:(UIButton *)sender;{
NSString *string = #"l";
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
if (bleShield.activePeripheral.state == CBPeripheralStateConnected) {
[bleShield write:data];
}
}
- (IBAction)trunkCar:(UIButton *)sender;{
NSString *string = #"t";
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
if (bleShield.activePeripheral.state == CBPeripheralStateConnected) {
[bleShield write:data];
}
}
- (IBAction)unlockCar:(UIButton *)sender;{
NSString *string = #"u";
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
if (bleShield.activePeripheral.state == CBPeripheralStateConnected) {
[bleShield write:data];
}
}
Code for Apple Watch so far:
- (IBAction)carConnect {
}
- (IBAction)carUnlock {
}
- (IBAction)carLock {
}
- (IBAction)carTrunk {
}
You can use WCSession to establish communication between the WatchKit extension and the companion app.
Communication channel is established as:
if ([WCSession isSupported]) {
WCSession* session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
The WCSessionDelegate must also be implemented to respond messages.
The session must be activated on each side.
Then you can send messages with sendMessage:replyHandler:errorHandler: and receive with session:didReceiveMessage:replyHandler: to execute those tasks.
I recommend reading the WatchKit Class reference and this article on Watchkit communication patterns.
I have a Watch OS 2 application that communicates with the iOS app via WCSession method sendMessage:replyHandler:errorHandler:
The iOS application reply correctly but time to time I get the error with code 7014 of domain WCErrorDomain: "Payload could not be delivered"
It happens more often when the iOS application is not foreground.
I do not find any solution of this problem, I hope one of you know a solution to this problem
In my case, I had to implement both delegates:
The one without any replyHandler
func session(_ session: WCSession,
didReceiveMessage message: [String : Any])
The one with replyHandler
func session(_ session: WCSession,
didReceiveMessage message: [String : Any],
replyHandler: #escaping ([String : Any]) -> Void)
If you send a message without a replyHandler then the first delegate runs.
If you send a message with a replyHandler then the second delegate runs.
In some cases I was sending only a message, and in other cases I was sending a message and expecting a reply from the counterpart.
BUT... I had implemented only the second delegate -_-
Anyways, eventually to reduce duplicate code I implemented a common method and ended up with:
func session(_ session: WCSession,
didReceiveMessage message: [String : Any]) {
handleSession(session,
didReceiveMessage: message)
}
func session(_ session: WCSession,
didReceiveMessage message: [String : Any],
replyHandler: #escaping ([String : Any]) -> Void) {
handleSession(session,
didReceiveMessage: message,
replyHandler: replyHandler)
}
//Helper Method
func handleSession(_ session: WCSession,
didReceiveMessage message: [String : Any],
replyHandler: (([String : Any]) -> Void)? = nil) {
//Common logic
}
Watch OS 4
For anyone having issues on iOS10 beta 6 and GM, and you are using Swift3, the solution is to change the delegate function header in the iOS app to the following:
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
Note the #escaping and the Any instead of the AnyObject type.
Try this one, this fixed my issue. Inside the InterfaceController add the following methods for passing the data to phone.
-(void)sendDataToPhone:(NSDictionary* _Nonnull)dictData
{
if(WCSession.isSupported){
WCSession* session = WCSession.defaultSession;
session.delegate = self;
[session activateSession];
if(session.reachable)
{
[session sendMessage:dictData replyHandler: ^(NSDictionary<NSString *,id> * __nonnull replyMessage) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#".....replyHandler called --- %#",replyMessage);
// Play a sound in watch
[[WKInterfaceDevice currentDevice] playHaptic:WKHapticTypeSuccess];
});
}
errorHandler:^(NSError * __nonnull error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Error = %#",error.localizedDescription);
});
}
];
}
else
NSLog(#"Session Not reachable");
}
else
NSLog(#"Session Not Supported");
}
#pragma mark - Standard WatchKit delegate
-(void)sessionWatchStateDidChange:(nonnull WCSession *)session
{
if(WCSession.isSupported){
WCSession* session = WCSession.defaultSession;
session.delegate = self;
[session activateSession];
}
}
In the phone side, add the following codes for receiving the data from watch.
Add the following in didFinishLaunchingWithOptions.
// Allocating WCSession inorder to communicate back to watch.
if(WCSession.isSupported){
WCSession* session = WCSession.defaultSession;
session.delegate = self;
[session activateSession];
}
Now add the WCSessionDelegate.
#pragma mark - WCSession Delegate
- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *, id> *)message replyHandler:(void(^)(NSDictionary<NSString *, id> *replyMessage))replyHandler
{
if(message){
NSData *receivedData = [message objectForKey:#"AudioData"];
NSDictionary* response = #{#"response" : [NSString stringWithFormat:#"Data length: %lu",(unsigned long)receivedData.length]} ;
replyHandler(response);
}
}
#pragma mark - Standard WatchKit Delegate
-(void)sessionWatchStateDidChange:(nonnull WCSession *)session
{
if(WCSession.isSupported){
WCSession* session = WCSession.defaultSession;
session.delegate = self;
[session activateSession];
if(session.reachable){
NSLog(#"session.reachable");
}
if(session.paired){
if(session.isWatchAppInstalled){
if(session.watchDirectoryURL != nil){
}
}
}
}
}
Hope this helps you :)
Sorry I'dont have enough reputation to comment answers.
My issue got resolved with Peter Robert's answer:
With Swift 3 WCErrorCodeDeliveryFailed appeared and the solution was simply changing AnyObject to Any on the replyHandlers.
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
//code
replyHandler (answer as [String : Any])
}
I was experiencing the same and moving WCSession initialization (setting delegate and activating it) later in the app lifecycle fixed the issue.
I had WCSession activation in app delegates didFinishLaunching and having it there broke the communication. Moving WCSession intialization later in the app made comms work again.
In my case I put WCSessionDelegate(iOS side) in a separate class and initialize it as local variable. Changing it to global instance variable solved the issue.
So my iOS Code was:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
SessionHandler()
}
Changed to below to get it working:
var handler: SessionHandler!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
handler = SessionHandler()
}
You may need to (check and) implement that your WCSession delegate implemented the following method. I got this error due to missing the implementation.
- (void)session:(WCSession * _Nonnull)session
didReceiveMessage:(NSDictionary<NSString *, id> * _Nonnull)replyMessage
replyHandler:(void (^ _Nonnull)(NSDictionary<NSString *, id> * _Nonnull replyMessage))replyHandler
{
NSLog(#"Received. %#", replyMessage);
[self processResponse:replyMessage];
}
Check the delegate connected correct ?
WCSession* session = WCSession.defaultSession;
session.delegate = self;
[session activateSession];
Note : Verify session.delegate = self; set to self.
Working on an app and have the exact same behavior.
I feel fairly sure that I have looked everywhere in my code and found nothing wrong. My best guess is that this must be a bug with WatchConnectivity.
My current error handler workaround simply tries to reload data on this particular error. Not very beautiful, but it works ok.
You might want to try something similar?
func messageErrorHandler(error: NSError) {
isLoading = false
print("Error Code: \(error.code)\n\(error.localizedDescription)")
// TODO: WTF?. Check future releases for fix on error 7014, and remove this...
if error.code == 7014 {
// Retry after 1.5 seconds...
retryTimer = NSTimer.scheduledTimerWithTimeInterval(
NSTimeInterval(1.5), target: self, selector: "reloadData", userInfo: nil, repeats: false)
return
}
displayError("\(error.localizedDescription) (\(error.code))",
message: "\(error.localizedDescription)")
}
UPDATE:
For anyone working with WatchConnectivity; I need to have a similar "hack" for testing the session.reachable variable.
I have noticed that my app manages to send a message before the session becomes reachable. So I simply try to reload data (re-send the message) a couple of times before actually telling the user their phone is out of reach.
UPDATE 2:
The above example is using .sessionWatchStateDidChange(), so the issue is not that .sendMessage() is triggered too early because of not waiting for connection ack. This must be a bug as it is not happening every time, it just freaks out like 1 per 100 messages.
I have found that putting the reply code as the first thing to run fixes this issue (possible being caused by timing out?).
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: ([String : Any]) -> Void) {
print("Message - received")
//Send reply
let data = ["receivedData" : true]
replyHandler(data as [String : AnyObject])
}
In Swift 3 I solved implementing didReceiveMessage with this signature:
func session(_ session: WCSession, didReceiveMessage message: [String : Any],
replyHandler: #escaping ([String : Any]) -> Void)
Be sure that your Session is always active. I for example had an other View which was part of the testing and then returned on the initially View and was wondering why the Session wasn't active anymore.
- (void)willActivate {
// This method is called when watch view controller is about to be visible to user
[super willActivate];
//Setup WCSession
if ([WCSession isSupported]) {
[[WCSession defaultSession] setDelegate:self];
[[WCSession defaultSession] activateSession];
}}
Above did it for me. Had it first placed in the awakeWithContext, stupid me....
This scenario will cover several use case. Please take look at these steps , it help me a lot.
1 - Understand that each device must have their own WCSession instance configured and with the appropriate delegates configured.
2 - implement WCSessionDelegate only at one single place on each device, ej. on iOS app on the AppDelegate, on watchOS on ExtensionDelegate. This is very important because with the appropriate WCSession configured on watchOS but on iPhone implementing on two different place, ej. on app delegate and then on the first viewcontorllweer of the app, (on my case ) lead to unstable behaviour and thats the main reason why sometimes the iOS app stop responding when message received from watch.
3 - reactivating the session is advisable only do it on the Host App. This is an example of my iOS App with only one WCSessionDelegate.
(AppDelegate )
#pragma mark - WCSessionDelegate
- (void)session:(WCSession *)session activationDidCompleteWithState:(WCSessionActivationState)activationState error:(NSError *)error{
if( activationState == WCSessionActivationStateActivated) {
NSLog(#"iPhone WKit session Activated");
}else if (activationState == WCSessionActivationStateInactive) {
NSLog(#"iPhone WKit Inactive");
}else if (activationState == WCSessionActivationStateNotActivated) {
NSLog(#"iPhone WKit NotActivated");
}
}
- (void)sessionDidBecomeInactive:(WCSession *)session{
/*
The session calls this method when it detects that the user has switched to a different Apple Watch. While in the inactive state, the session delivers any pending data to your delegate object and prevents you from initiating any new data transfers. After the last transfer finishes, the session moves to the deactivated state
*/
NSLog(#"sessionDidBecomeInactive");
if (session.hasContentPending) {
NSLog(#"inactive w/ pending content");
}
}
- (void)sessionDidDeactivate:(WCSession *)session{
// Begin the activation process for the new Apple Watch.
[[WCSession defaultSession] activateSession];
//perform any final cleanup tasks related to closing out the previous session.
}
- (void)sessionReachabilityDidChange:(WCSession *)session{
NSLog(#"sessionReachabilityDidChange");
}
last thing, write the appropriate method signature, if you need a reply sending data from watch , take the method signature who have reply:...
According to apple the following methods
sendMessage:replyHandler:errorHandler:, sendMessageData:replyHandler:errorHandler:, and transferCurrentComplicationUserInfo:
has a higher priority and is transmitted right away. All messages received by your app are delivered to the session delegate serially on a background thread.
So do not waste time dispatching the reply object on mainQueue on the iOS appDelegate, wait till you have the response on your watchOS back and change it to main thread to update your UI accordingly.
is anywhere a simple tutorial, how I can use the WCSessionUserInfoTransfer to change data between my watch and iOS ?
And what must be written in the delegate of the iOS -App?
in my old programming I used:
[WKInterfaceController openParentApplication:#{#"Kommando":#"Radius"} reply:^(NSDictionary *replyInfo, NSError *error) {
if (error) {
NSLog(#"Error from parent: %#", error);
} else {
NSLog(#"Radius from parent: %#", [replyInfo objectForKey:#"Radius"]);
}
}];
The first thing you need to do is to define a WCSession. This must be defined and activated in every class that you're planning on transferring data to and receiving data from. To use WCSession, make sure it's supported, and then activate the default session as shown below.
#import <WatchConnectivity/WatchConnectivity.h>
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
From here, you can use transferUserInfo where ever you need to send data (from the watch or the iOS app):
[[WCSession defaultSession] transferUserInfo:#{#"Kommando":#"Radius"}];
On the receiving end, you would use session:didReceiveUserInfo. Note that this does NOT need to be in the app delegate anymore on the iOS app side, unlike handleWatchKitExtensionRequest. You can use this where ever you need to receive the data. Make sure to activate WCSession, as shown above, in the class where you have didReceiveUserInfo also.
- (void)session:(nonnull WCSession *)session didReceiveUserInfo:(nonnull NSDictionary<NSString *,id> *)userInfo {
NSLog(#"Radius from parent: %#", [userInfo objectForKey:#"Radius"]);
}
I am building an app that uses a web service and to get information from that web service I use NSURLSession and NSURLSessionDataTask.
I am now in the memory testing phase and I have found that NSURLSession is causing memory leaks.
This is not all of the leaks. It is all that I could fit in the picture.
Below is how I setup the NSURLSession and request the information from the web service.
#pragma mark - Getter Methods
- (NSURLSessionConfiguration *)sessionConfiguration
{
if (_sessionConfiguration == nil)
{
_sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
[_sessionConfiguration setHTTPAdditionalHeaders:#{#"Accept": #"application/json"}];
_sessionConfiguration.timeoutIntervalForRequest = 60.0;
_sessionConfiguration.timeoutIntervalForResource = 120.0;
_sessionConfiguration.HTTPMaximumConnectionsPerHost = 1;
}
return _sessionConfiguration;
}
- (NSURLSession *)session
{
if (_session == nil)
{
_session = [NSURLSession
sessionWithConfiguration:self.sessionConfiguration
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
}
return _session;
}
#pragma mark -
#pragma mark - Data Task
- (void)photoDataTaskWithRequest:(NSURLRequest *)theRequest
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Photo Request Data Task Set");
#endif
// Remove existing data, if any
if (_photoData)
{
[self setPhotoData:nil];
}
self.photoDataTask = [self.session dataTaskWithRequest:theRequest];
[self.photoDataTask resume];
}
#pragma mark -
#pragma mark - Session
- (void)beginPhotoRequestWithReference:(NSString *)aReference
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Fetching Photo Data...");
#endif
_photoReference = aReference;
NSString * serviceURLString = [[NSString alloc] initWithFormat:#"%#/json?photoreference=%#", PhotoRequestBaseAPIURL, self.photoReference];
NSString * encodedServiceURLString = [serviceURLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
serviceURLString = nil;
NSURL * serviceURL = [[NSURL alloc] initWithString:encodedServiceURLString];
encodedServiceURLString = nil;
NSURLRequest * request = [[NSURLRequest alloc] initWithURL:serviceURL];
[self photoDataTaskWithRequest:request];
serviceURL = nil;
request = nil;
}
- (void)cleanupSession
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Session Cleaned Up");
#endif
[self setPhotoData:nil];
[self setPhotoDataTask:nil];
[self setSession:nil];
}
- (void)endSessionAndCancelTasks
{
if (_session)
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Session Ended and Tasks Cancelled");
#endif
[self.session invalidateAndCancel];
[self cleanupSession];
}
}
#pragma mark -
#pragma mark - NSURLSession Delegate Methods
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Session Completed");
#endif
if (error)
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Photo Request Fetch: %#", [error description]);
#endif
[self endSessionAndCancelTasks];
switch (error.code)
{
case NSURLErrorTimedOut:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"RequestTimedOut" object:self];
}
break;
case NSURLErrorCancelled:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"RequestCancelled" object:self];
}
break;
case NSURLErrorNotConnectedToInternet:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotConnectedToInternet" object:self];
}
break;
case NSURLErrorNetworkConnectionLost:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"NetworkConnectionLost" object:self];
}
break;
default:
{
}
break;
}
}
else {
if ([task isEqual:_photoDataTask])
{
[self parseData:self.photoData fromTask:task];
}
}
}
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
if (error)
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Session Invalidation: %#", [error description]);
#endif
}
if ([session isEqual:_session])
{
[self endSessionAndCancelTasks];
}
}
#pragma mark -
#pragma mark - NSURLSessionDataTask Delegate Methods
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Received Data");
#endif
if ([dataTask isEqual:_photoDataTask])
{
[self.photoData appendData:data];
}
}
#pragma mark -
Question:
Why is NSURLSession causing these memory leaks? I am invalidating the NSURLSession when I am finished with it. I am also releasing any properties that I do not need and setting the session to nil (refer to - (void)cleanupSession & - (void) endSessionAndCancelTasks).
Other Information:
The memory leaks occur after the session has completed and "cleaned up". Sometimes, they also occur after I have popped the UIViewController. But, all of my custom (GPPhotoRequest and GPSearch) objects and UIViewController are being dealloced (I've made sure by adding an NSLog).
I tried not to post to much code, but I felt like most of it needed to be seen.
Please let me know if you need any more information. Help is greatly appreciated.
I had this same "leaky", memory management issue when I switched to NSURLSession. For me, after creating a session and resuming/starting a dataTask, I was never telling the session to clean itself up (i.e. release the memory allocated to it).
// Ending my request method with only the following line causes memory leaks
[dataTask resume];
// Adding this line fixed my memory management issues
[session finishTasksAndInvalidate];
From the docs:
After the last task finishes and the session makes the last delegate call, references to the delegate and callback objects are broken.
Cleaning up my sessions fixed the memory leaks being reported via Instruments.
After rereading the URL Loading System Programming Guide it turns that I was setting the NSURLSession property to nil too early.
Instead, I need to set the NSURLSession property to nil AFTER I receive the delegate message URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error, which makes sense. Luckily, it was a minor mistake.
E.g.
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
if (error)
{
#ifdef DEBUG
NSLog(#"[GPPhotoRequest] Session Invalidation: %#", [error description]);
#endif
}
if ([session isEqual:_session])
{
[self cleanupSession];
}
}
Had the same issue. The #Jonathan's answer didn't make a sense - my app still leaked memory. I found out that setting the session property to nil in URLSession:didBecomeInvalidWithError: delegate method is causing the issue. Tried to look deeper into the URL Loading System Programming Guide. It says
After invalidating the session, when all outstanding tasks have been canceled or have finished, the session sends the delegate a URLSession:didBecomeInvalidWithError: message. When that delegate method returns, the session disposes of its strong reference to the delegate.
I left the delegate method blank. But the invalidated session property still have a pointer, when should I set it nil? I just set this property weak
// .h-file
#interface MyClass : NSObject <NSURLSessionDelegate>
{
__weak NSURLSession *_session;
}
// .m-file
- (NSURLSessionTask *)taskForRequest:(NSURLRequest *)request withCompletionHandler:(void(^)(NSData *,NSURLResponse *,NSError *))handler
{
if(!_session)
[self initSession];
//...
}
The app stopped leaking memory.
Please see my answer here: https://stackoverflow.com/a/53428913/4437636
I believe this leak is the same one I was seeing, and only happens when running network traffic through a proxy. My code was fine, but it turned out that an internal bug in the Apple API was causing the leak.