AFHTTPSessionManager not deallocating - objective-c

Update
After posting this as an issue to the AFNetworking repo, turns out this is in fact a usage issue on my part. Per the response to my issue:
NSURLSession retains its delegate (i.e. AFURLSessionManager). Call invalidateSessionCancelingTasks: to ensure that sessions finalize and release their delegate.
So, long story short: If you are using AHTTPSessionManager in the manner described below, make sure to call invalidateSessionCancelingTasks: to ensure that sessions finalize and release their delegate
Original Question
I have a subclassed AFHTTPSessionManager called GTAPIClient that I am using to connect to my REST API. I realize the docs state to use as a singleton but there are a few cases where I need to gin up a new instance. However, it seems that whenever I do so, the object is never deallocated. Currently, GTAPIClient literally does nothing except NSLog itself when deallocated.
Here's some sample code that demonstrates the behavior
GTAPIClient.m
#implementation GTAPIClient
- (void)dealloc
{
NSLog(#"Dealloc: %#", self);
}
#end
GTViewController.m
#import "GTBaseEntityViewController.h"
//Models
#import "GTBaseEntity.h"
//Clients
#import "GTAPIClient.h"
#interface GTBaseEntityViewController ()
#property (nonatomic, weak) GTAPIClient *client;
#property (nonatomic, weak) GTBaseEntity *weakEntity;
#end
#implementation GTBaseEntityViewController
- (IBAction)makeClient:(id)sender {
self.client = [[GTAPIClient alloc] init];
NSLog(#"I just made an API client %#", self.client);
//Another random object assigned to a similar property, just to see what happens.
self.weakEntity = [[GTBaseEntity alloc] init];
NSLog(#"I just made a random object %#", self.weakEntity);
}
- (IBAction)checkClient:(id)sender {
NSLog(#"Client: %#", self.client);
NSLog(#"Entity: %#", self.weakEntity);
}
#end
NSLog output
Fire makeClient:
//It seems to me that both NSLog's should return (null) as they are assigning to a weak property
2014-06-22 16:41:39.143 I just made an API client <GTAPIClient: 0x10b913680, baseURL: (null), session: <__NSCFURLSession: 0x10b915010>, operationQueue: <NSOperationQueue: 0x10b9148a0>{name = 'NSOperationQueue 0x10b9148a0'}>
2014-06-22 16:41:39.144 I just made a random object (null)
Fire checkClient
//Again, both NSLog's should return null for the two objects. However...client is still around. Also, it's overridden dealloc method never fired.
2014-06-22 16:44:43.722 Client: <GTAPIClient: 0x10b913680, baseURL: (null), session: <__NSCFURLSession: 0x10b915010>, operationQueue: <NSOperationQueue: 0x10b9148a0>{name = 'NSOperationQueue 0x10b9148a0'}>
2014-06-22 16:44:43.723 Entity: (null)
For reference, I am using v2.3.1 of AFNetworking. Compiler is warning me that assigning retained object to weak property will release after assignment - which is correct, and functions as expects with my random object. There is nothing else going on in the app. No other view controllers, no other methods on the GTAPIClient, all singleton functionality is removed. Any thoughts on what I am doing wrong here?

Posting the response from Mattt Thompson here to assist future readers:
NSURLSession retains its delegate (i.e. AFURLSessionManager). Call invalidateSessionCancelingTasks: to ensure that sessions finalize and release their delegate.
If, like many apps, your app uses a singleton Session Manager and one URL Session for your entire app, then you don't need to worry about this.

Replicating your scenario and running it through Instruments shows that AFURLSessionManagers are retained by the NSURLSessions they create, as AFURLSessionManager acts as the delegate for every NSURLSession created. This creates a retain cycle and thus the AFHTTPSessionManager cannot be released. Whether this is a bug in either library or not a bug at all, I'm not sure. You may want to report it on the AFNetworking GitHub page (https://github.com/AFNetworking/AFNetworking).

__weak typeof(manager) weak_manager = manager;
[manager requestWithMethod:method URLString: uri parameters:param
success:^(NSURLSessionDataTask *task, id responseObject) {
if (completion) {
completion(YES, responseObject, task.response);
}
[weak_manager invalidateSessionCancelingTasks:YES];
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (completion) {
completion(NO, error, task.response);
}
[weak_manager invalidateSessionCancelingTasks:YES];
}];

Related

How to use OCMock to verify static methods

I am trying to use OCMock library. I am trying to create mock of class object, but it is failing to verify the method. I am unable to understand why the tests are failing.
#interface MyClass:NSObject
+(void) someMethod;
#end
#implementation MyClass
+(void) someMethod
{
NSError* error = nil;
if (![Utility isValidPropWithError:&error])
{
[Logger log:LoggerLevelWarning message:[error localizedDescription] className:className];
}
}
#end
Test :
-(void)testIfLoggerIsset{
id partialMockLogger = OCMClassMock([Logger class]);
id partialMockUtility = OCMClassMock([Utility class]);
id partialMockClass = OCMClassMock([MyClass class]);
NSError *error = nil;
OCMExpect([partialMockUtility isValidPropWithError:&error]);
[MyClass someMethod];
//This works fine.
OCMVerifyAll(partialMockClass);
NSString *className = #"classname";
//This is failing...
OCMVerify([partialMockUtility isValidPropWithError:&error]);
OCMVerifyAll(partialMockUtility);
//This is failing...
OCMVerify([partialMockLogger log:LoggerLevelWarning message:[error localizedDescription] className:className]);
[partialMockUtility stopMocking];
[partialMockLogger stopMocking];
}
In the above code, although [Utility isValidPropWithError:&error]; is called OCMVerify([partialMockUtility isValidPropWithError:&error]);is failing.
Several things here:
First, OCMVerify([partialMockUtility isValidPropWithError:&error] is failing because you are expecting the address of the NSError object you created in the test to be passed to isValidPropWithError:, but in MyClass +someMethod you are creating a different NSError object. The addresses of two different objects will not be the same.
To fix this, change your expectation and verification to:
OCMExpect([partialMockUtility isValidPropWithError:(NSError __autoreleasing**)[OCMArg anyPointer]]);
OCMVerify([partialMockUtility isValidPropWithError:(NSError __autoreleasing**)[OCMArg
and just ignore the actual value of the parameter and expect that it's going to be an NSError pointer (since you're creating it inside of someMethod, there's no way to know what it's going to be before you call the method).
Second, since you are already explicitly verifying +isValidPropWithError, OCMVerifyAll(partialMockUtility) isn't going to verify anything. You should either explicitly verify all of your expectations, or simply use OCMVerifyAll(partialMockUtility) and let it verify all your expectations and don't bother with expecting the specific call. OCMVerifyAll will verify everything you expect on the mock object you give it. This isn't going to cause a test failure - both calls will pass, since you've already verified the call the first time, the call to OCMVerifyAll() isn't going to have anything to verify, so it will pass.
Last, OCMVerify([partialMockLogger log:LoggerLevelWarning message:[error localizedDescription] className:className]); is failing because you didn't set an expectation for it.

Insert data with MagicalRecord

I am using CoreData with MagicalRecord.
I'd like to insert Data in following code, but to insert data become an error with a message Cocoa error 133000.
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:#"class_schedule.sqlite"];
return YES;
}
ViewController.m
- (void)saveData
{
Data *data = [Data MR_createEntity];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Data *localData = [data MR_inContext:localContext];
localData.title = textField.text;
} completion:^(BOOL success, NSError *error) {
}];
}
Data.h
#interface Data : NSManagedObject
#property (nonatomic, retain) NSNumber * id;
#property (nonatomic, retain) NSString * title;
#end
Can you tell me how to insert record with Magical Record?
Error:
Cocoa error 133000 is:
NSManagedObjectReferentialIntegrityError = 133000, // attempt to fire a fault pointing to an object that does not exist (we can see the store, we can't see the object)
(Taken from this SO question). Basically you are doing something with a NSManagedObject that doesn't exist.
Inserting data:
In terms of how to insert data using magical record take a look at this tutorial which will probably explain it much better than I can.
My advice:
Use Core Data straight up. It's quite a steep learning curve, but very quickly becomes intuitive and easy to use. It will also stand you in good stead if you know how it all works rather than relying on a third party.
If you're interested in how it works at a more fundamental level, take a look at SQLite. I wouldn't necessarily recommend using it as it is a C library but it will help you get a deeper understanding.
You get error 133000 when you try to access object that is not existing. "But hey", you might say, "what do you mean not existing? I'm creating it right there!".
When you are creating NSManagedObject like you do, that is with MR_createEntity, under the hood it calls
NSManagedObject *newEntity = [self MR_createEntityInContext:[NSManagedObjectContext MR_contextForCurrentThread]]
This context is not saved in any way by doing that, and created entity is not persisted. Then by calling
Data *localData = [data MR_inContext:localContext];
You are actually making this under the hood:
BOOL success = [[self managedObjectContext] obtainPermanentIDsForObjects:#[self] error:&error];
The problem is that if NSManagedObject isn't persisted, you won't get persistent ID that is next used in
NSManagedObject *inContext = [otherContext existingObjectWithID:[self objectID] error:&error];
Above method fails to retrieve existing object because it's not existing in store yet (remember, context for current thread in which created entity exists is not saved at any point).
But worry not, fix for this is pretty simple. Don't create new entity like that. Instead do it like this:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Data *localData = [data MR_createEntityInContext:localContext];
localData.title = textField.text;
} completion:^(BOOL success, NSError *error) {
}];
That way create entity and modify in context that is going to be saved immediately. This is the correct way to create entities in MagicalRecord.

Magical Record object for current thread

I'm using Magical Record 2.3.0 beta 5 and I have troubles understanding how to get my NSManagedObjects for the current thread. I have a long running NSOperation where I need my PSPlayer (NSManagedObject).
When I init the NSOperation, I keep an id of my PSPlayer and re-fetch the same object in the operation's main method. According to Apple that the way to do it.
#implementation TAPlayerUpdateOperation
- (instancetype)initWithPlayer:(PSPlayer *)player;
{
self = [super init];
if (self) {
self.playerMD5Id = player.md5Id;
}
}
- (void)main
{
#autoreleasepool {
__block BOOL keepUpdating = YES;
PSPlayer *player = [[PSPlayer MR_findAllWithPredicate:[NSPredicate predicateWithFormat:#"md5Id == %#", self.playerMD5Id]] firstObject];
NSLog(#"player.md5Id = %#", player.md5Id);
// rest of my operation logic
}
}
#end
When I run my app with -com.apple.CoreData.ConcurrencyDebug 1, I get a crash when accessing the property in the NSLog statement.
What is the correct way to get my NSManagedObject so that it is safe for the current thread?
I've pinned the problem down to the following snippet where it crashes as well.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
PSPlayer *player =[[PSPlayer MR_findAll] firstObject];
NSLog(#"player = %#", player.name);
});
cheers,
Jan
You need to ensure that everything is saved and merged before the fetch would work. If you're using MR then it's better to take the managed object and call inContext: on it supplying the other context and have it do the work (it also avoids a predicate).
I expect the crash is because you use player.md5Id instead of self.playerMD5Id so you're accessinh the managed object on the wrong thread.

NSFilePresenter methods never get called

I'm trying to write a simple (toy) program that uses the NSFilePresenter and NSFileCoordinator methods to watch a file for changes.
The program consists of a text view that loads a (hardcoded) text file and a button that will save the file with any changes. The idea is that I have two instances running and saving in one instance will cause the other instance to reload the changed file.
Loading and saving the file works fine but the NSFilePresenter methods are never called. It is all based around a class called FileManager which implements the NSFilePresenter protocol. The code is as follows:
Interface:
#interface FileManager : NSObject <NSFilePresenter>
#property (unsafe_unretained) IBOutlet NSTextView *textView;
- (void) saveFile;
- (void) reloadFile;
#end
Implementation:
#implementation FileManager
{
NSOperationQueue* queue;
NSURL* fileURL;
}
- (id) init {
self = [super init];
if (self) {
self->queue = [NSOperationQueue new];
self->fileURL = [NSURL URLWithString:#"/Users/Jonathan/file.txt"];
[NSFileCoordinator addFilePresenter:self];
}
return self;
}
- (NSURL*) presentedItemURL {
NSLog(#"presentedItemURL");
return self->fileURL;
}
- (NSOperationQueue*) presentedItemOperationQueue {
NSLog(#"presentedItemOperationQueue");
return self->queue;
}
- (void) saveFile {
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
[coordinator coordinateWritingItemAtURL:self->fileURL options:NSFileCoordinatorWritingForMerging error:&error byAccessor:^(NSURL* url) {
NSString* content = [self.textView string];
[content writeToFile:[url path] atomically:YES encoding:NSUTF8StringEncoding error:NULL];
}];
}
- (void) reloadFile {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
__block NSData* content;
[coordinator coordinateReadingItemAtURL:self->fileURL options:0 error:&error byAccessor:^(NSURL* url) {
if ([fileManager fileExistsAtPath:[url path]]) {
content = [fileManager contentsAtPath:[url path]];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self.textView setString:[[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding]];
});
}
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
#end
Note, reloadFile is called in applicationDidFinishLaunching and saveFile gets called every time the save button is click (via the app delegate).
The only NSFilePresenter method that ever gets called (going by the logs) is presentedItemURL (which gets called four times when the program starts and loads the file and three times whenever save is clicked. Clicking save in a second instance has no noticeable effect on the first instance.
Can anyone tell me what I'm doing wrong here?
I was struggling with this exact issue for quite a while. For me, the only method that would be called was -presentedSubitemDidChangeAtURL: (I was monitoring a directory rather than a file). I opened a technical support issue with Apple, and their response was that this is a bug, and the only thing we can do right now is to do everything through -presentedSubitemDidChangeAtURL: if you're monitoring a directory. Not sure what can be done when monitoring a file.
I would encourage anyone encountering this issue to file a bug (https://bugreport.apple.com) to encourage Apple to get this problem fixed as soon as possible.
(I realize that this is an old question, but... :) )
First of all, I notice you don't have [NSFileCoordinator removeFilePresenter:self]; anywhere (it should be in dealloc).
Secondly, you wrote:
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
You're right: it's the incorrect implementation! And you're wrong: it's not good enough, because it's essential for methods like accommodatePresentedItemDeletionWithCompletionHandler: which take a completion block as a parameter, that you actually call this completion block whenever you implement them, e.g.
- (void) savePresentedItemChangesWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler
{
// implement your save routine here, but only if you need to!
if ( dataHasChanged ) [self save]; // <-- meta code
//
NSError * err = nil; // <-- = no error, in this simple implementation
completionHandler(err); // <-- essential!
}
I don't know whether this is the reason your protocol methods are not being called, but it's certainly a place to start. Well, assuming you haven't already worked out what was wrong in the past three years! :-)

Cocoa: Checks required for multiple asynchronous NSURLConnections using same delegate functions?

This is with reference to the StackOverflow question Managing multiple asynchronous NSURLConnection connections
I have multiple asynchronous HTTP requests being made at the same time. All these use the same NSURLConnection delegate functions. (The receivedData object is different for each connection as specified in the other question above. In the delegate, I parse the receivedDate object, and do additional operations on those parsed strings)
Everything works fine for me so far, but I'm not sure if I need to do anything to ensure correct “multithreaded” behavior.
Is it possible that more than two connections will use the delegate at the same time? (I would think yes)
If yes, how is it resolved? (Does Cocoa do this automatically?)
Do I need to have additional checks in place to ensure that each request is handled “correctly”?
I enhanced the Three20 library to implement asynchronous connections across multiple threads in order to fetch data even if the user was playing with the UI. After many hours of chasing down random memory leaks that were detected within the CFNetwork framework I finally root caused the issue. I was occasionally losing track of responses and data.
Any data structures which are accessed by multiple threads must be protected by an appropriate lock. If you are not using locks to access shared data structures in a mutually exclusive manner then you are not thread safe. See the "Using Locks" section of Apple's Threading Programming Guide.
The best solution is to subclass NSURLConnection and add instance variables to store its associated response and response data. In each connection delegate method you then cast the NSURLConnection to your subclass and access those instance variables. This is guaranteed to be mutually exclusive because every connection will be bundled with its own response and data. I highly recommend trying this since it is the cleanest solution. Here's the code from my implementation:
#interface TTURLConnection : NSURLConnection {
NSHTTPURLResponse* _response;
NSMutableData* _responseData;
}
#property(nonatomic,retain) NSHTTPURLResponse* response;
#property(nonatomic,retain) NSMutableData* responseData;
#end
#implementation TTURLConnection
#synthesize response = _response, responseData = _responseData;
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate {
NSAssert(self != nil, #"self is nil!");
// Initialize the ivars before initializing with the request
// because the connection is asynchronous and may start
// calling the delegates before we even return from this
// function.
self.response = nil;
self.responseData = nil;
self = [super initWithRequest:request delegate:delegate];
return self;
}
- (void)dealloc {
[self.response release];
[self.responseData release];
[super dealloc];
}
#end
/////////////////////////////////////////////////////////////////
////// NSURLConnectionDelegate
- (void)connection:(NSURLConnection*)connection
didReceiveResponse:(NSHTTPURLResponse*)response {
TTURLConnection* ttConnection = (TTURLConnection*)connection;
ttConnection.response = response;
ttConnection.responseData = [NSMutableData
dataWithCapacity:contentLength];
}
- (void)connection:(NSURLConnection*)connection
didReceiveData:(NSData*)data {
TTURLConnection* ttConnection = (TTURLConnection*)connection;
[ttConnection.responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
TTURLConnection* ttConnection = (TTURLConnection*)connection;
if (ttConnection.response.statusCode == 200) {
// Connection success
}
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error {
TTURLConnection* ttConnection = (TTURLConnection*)connection;
// Handle the error
}
Assuming you're launching all of the (asynchronous) connections on a single thread, then the delegate messages will all get posted in that thread's run loop. Therefore the delegate only needs to be able to deal with one message being handled at once; the run loop will hand one message off at a time. This means that while the order of the delegate messages is unknown and the next message could come from any connection object, there will be no concurrent execution of your delegate methods.
However, were you actually trying to use the same delegate object across multiple threads, rather than just using the asynchronous nature of the API, then you would need to deal with concurrent delegate methods.
Yes it's possible to have multiple connections. the notification object contains a pointer to the NSURLConnection that triggered the notification.
Internally I guess NSURLConnection listens to a socket and does something like this when it has data ready.
[your_delegate
performSelectorOnMainThread:#selector(connectionCallback:)
withObject:self
waitUntilDone:NO];
so you don't have to worry about it being multithreaded, NSURLConnection will take care of this. For simplicity I have written self, in the real world a NSNotification object is given.
You shouldn't have to do any checks related to multithreading.