autorelease deprecated, is there an alternative? - objective-c

Following a tutorial for a photo slideshow for code ivI'e run into what I think is some deprecated code. I have an ARC error at autorelease and a warning with the setDelegate:self
NSXMLParser *photoParser = [[[NSXMLParser alloc]
initWithContentsOfURL:[NSURL URLWithString:
#"http://localhost/photos/index.xml"]] autorelease];
[photoParser setDelegate:self];

Remove the autorelease so that the code looks like the following:
ARC does not allow explicit autorelease calls. If you want an object to be deallocated you don't have to do anything (ARC will handle it). In some case you may want to set an object to nil (such as photoParser = nil;)
NSXMLParser *photoParser = [[NSXMLParser alloc]
initWithContentsOfURL:[NSURL URLWithString:
#"http://localhost/photos/index.xml"]];
[photoParser setDelegate:self];

Related

Object released within block

I'm pretty sure I know what is happening, however I want to know if there is a nice way of stopping it from happening.
Basically, I have a class method which looks something up from the core data store, and if nothing exists attempts to fetch it from a web server. The core data lookup and request are performed in the managed object contexts performBlock method.
I have the following block of code:
[context performBlock:^{
__block NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([self class])];
[request setSortDescriptors:#[[[NSSortDescriptor alloc] initWithKey:key ascending:asc selector:#selector(caseInsensitiveCompare:)]]];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:keyPath
cacheName:nil];
[controller performFetch:&error];
if (!controller.fetchedObjects || controller.fetchedObjects.count == 0) {
// Nothing found or an error, query the server instead
NSString *url = [NSString stringWithFormat:#"%#%#", kMP_BASE_API_URL, [self baseURL]];
MPRequest *objRequest = [MPRequest requestWithURL:url];
[objRequest setRequestMethod:#"GET"];
[MPUser signRequest:objRequest];
[objRequest submit:^(MPResponse *resp, NSError *err) {
if (err) {
block(nil, err);
} else {
NSArray *objects = [self createListWithResponse:resp];
objects = [MPModel saveAllLocally:objects forEntityName:NSStringFromClass([self class])];
[controller performFetch:&error];
block(controller, nil);
}
}];
} else {
// Great, we found something :)
block (controller, nil);
}
}];
What is happening, is that the MPRequest object is created, and fired, however the submit method triggers an asynchronous request and thus returns almost instantly. I assume ARC is then releasing the MPRequest object. When the request is performed, the internal request object's delegate no longer exists, as ARC has released it (the MPRequest object is the delegate for the internal request it has). Because of this, the block that the submit method is provided with isn't called.
Is there any way I can prevent ARC for doing this without making the request synchronous?
Edit
The submit method of MPRequest looks like this
_completionBlock = block;
_responseData = [[NSMutableData alloc] init];
[self prepareRequest];
[self prepareRequestHeaders];
_connection = [[NSURLConnection alloc] initWithRequest:_urlRequest
delegate:self];
[self requestStarted];
Your MPRequest object needs to keep itself alive while the connection is running. The simplest way to do this is probably to retain itself when the connection is started, and then release itself after it calls the completion block. Under ARC, the simplest way to do this is
CFRetain((__bridge CFTypeRef)self);
and
CFRelease((__bridge CFTypeRef)self);
Conceptually, the run loop keeps the request alive. This is how NSURLConnection operates. But since MPRequest isn't actually attached to the run loop, it's just a wrapper around NSURLConnection, you need to do some work to keep it alive for the same period of time.
Also note that the NSURLConnection needs to be added to the appropriate runloop. The convenience method will add it to the current thread's runloop, but if you're in a background queue, that's not going to work. You can use something like
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:delegate startImmediately:NO];
[_connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
set this MPRequest outside of the context performBlock:^{...} like so:
__strong MPRequest = // allocate or set the MPRequest.
[context performBlock:^{...}
Now once the performBlock is finished, the MPRequest will not have been released, and still be set to whatever variable you designated it to in the __strong clause.

How to solve fails which are not reported in console

Maybe it is silly question, but I have an curious problem. My app fails every time I try to refresh my data in database using core data when I re-enter the view controller. If I enter the view controller and refresh data from internet it works. However, when I start the program, enter the view controller, then pop it and re-enter and try refresh data, it fails and nothing is logged in view controller. I do not know why it happens. So I tried to debug it but the problem disappeared. After I tried again without debug mode it failed once more. I do not know why. Could it be problem of allocation, or something not released? Here's my implementation of -viewDidLoad:
- (void)viewDidLoad {
pool = [[NSAutoreleasePool alloc] init];
if (managedObjectContext_ == nil)
{
managedObjectContext_ = [[(NavTestAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext] autorelease];
}
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:#selector(insertNewObject)];
self.navigationItem.rightBarButtonItem = addButton;
[addButton release];
prikazDatabaseArray = [[[NSMutableArray alloc] init]retain];
NSDate *now = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"dd.MM.yyyy"];
datumOd = [[dateFormatter stringFromDate:now] retain];
[dateFormatter release];
VObrat = YES;
}
I also use
NSAutoreleasePool *pool;
NSURLConnection *theConnection;
NSMutableURLRequest *theRequest;
NSData *xmlData;
NSData *xmlFile;
NSXMLParser *addressParser;
in my code. If you could, at least help me find where I have the problem, because without debug and console I feel like a blind man.
Thanks a lot
remove the autorelease from the line managedObjectContext_ = [[(NavTestAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext] autorelease];
You didn't retain, so you should not (auto)release it.
Edit: I see, there's more.
remove the retain in prikazDatabaseArray = [[[NSMutableArray alloc] init]retain];. Alloc retains your object already. No need to do it twice.
And get rid of the autoreleasepool or drain it at the end of the method.
You should consider to read the Memory Management Programming Guide again.
if you are creating your own pool then you need to release the pool in the same context or method or loop.
you should use [pool drain] for that.
if you are not able to see your crash log you run with performance tool it will give you the location where u are doing wrong.

NSString EXC_BAD_ACCESS

I'm stuck with the following bit of code.
NSString *gridRef = [[NSString alloc] initWithFormat: #"%#", [converter LatLongToOSGrid: latLong]];
NSLog(#"Grid Ref: %#", gridRef);
self.answerLabel.text = [[NSString alloc] initWithFormat: #"%#", gridRef];
When I log gridRef, it displays the correct result. However, the line setting answerLabel.text causes an EXC_BAD_ACCESS error and the program crashes. IB is connected to the correct label, what is the problem?
Thanks
I've updated the code as follows:
- (IBAction)convertLatLong {
NSArray *latLong = [[NSArray alloc] initWithObjects: latTextField.text, longTextField.text, nil];
GridRefsConverter *converter = [[GridRefsConverter alloc] init];
NSString *gridRef = [[NSString alloc] initWithFormat: #"%#", [converter LatLongToOSGrid: latLong]];
NSLog(#"Grid Ref: %#", gridRef);
NSLog(#"Label: %#", self.answerLabel.text);
answerLabel.text = #"Yippy";
self.answerLabel.text = gridRef;
[gridRef release];
[converter release];
[latLong release];
}
answerLabel is initialised through #property #synthesize when the view controller is pushed onto the stack. (I don't know how it gets init'd apart from it's one of the magical things IB does for you. Or so I assume. I've used exactly the same method in other view controllers and have not had this issue.
I've found the culprits - the question is, how do I go about releasing them?
NSString *eString = [[NSString alloc] initWithFormat: #"%f", e];
NSString *nString = [[NSString alloc] initWithFormat: #"%f", n];
eString = [eString stringByPaddingToLength: (digits/2) withString: #"0" startingAtIndex: 0];
nString = [nString stringByPaddingToLength: (digits/2) withString: #"0" startingAtIndex: 0];
NSString *theGridRef = [letterPair stringByAppendingString: eString];
theGridRef = [theGridRef stringByAppendingString: nString];
[eString release];
[nString release];
return theGridRef;
and:
NSArray *gridRef = [[NSArray alloc] init];
gridRef = [gridRef arrayByAddingObject: [NSNumber numberWithDouble: E]];
gridRef = [gridRef arrayByAddingObject: [NSNumber numberWithDouble: N]];
gridRef = [gridRef arrayByAddingObject: [NSNumber numberWithInteger: 8]];
NSString *theGridRef = [[NSString alloc] initWithFormat: #"%#", [self gridRefNumberToLetter: gridRef]];
[gridRef release];
[theGridRef autorelease];
return theGridRef;
}
You should enable zombie detection by setting the environment variable NSZombieEnabled to YES, so you can see which object causes the bad access (don't forget to remove this again when you found the bug).
Also you can use Instruments to find the location where the object actually gets released. For this start a new Instruments session and use the "Allocations" instrument. In the instrument settings check "Enable NSZombie detection" and "Record reference counts". When running the session you will break where the error occurs and you see a record of all retains/releases.
One place where you can have a quick look if your object is incorrectly freed is in the -viewDidUnload method, where you should release the outlet and set it to nil. If you forget the latter and you access the outlet somehow, it will result in a EXC_BAD_ACCESS.
Edited to match your update:
The problem is that you are assigning eString (and nString) a new string which was alloc/init-ed. Then you override those in the next statements, because -stringByPaddingToLength: (as well as all the other -stringBy... methods) return a new and autoreleased string object. So you lost the reference to the old string which means that there is a memory leak. Additionally at the end you release the already autoreleased objects explicitly which causes your bad access.
Instead you should create autoreleased strings from the beginning ([NSString stringWithFormat:...]) and don't release them at the end.
Check if asnwerLabel is actually non-null. You should also change this line:
self.answerLabel.text = [[NSString alloc] initWithFormat: #"%#", gridRef];
To:
self.answerLabel.text = [NSString stringWithFormat: #"%#", gridRef];
Otherwise, you will end up with a memory leak in that line.
Maybe the label is not inited at that point in your code, try to check it. Why are you allocating a new NSString?
Just do:
self.label.text = gridRef;
[gridRef release];
how is answerLabel created? You might need to retain that. Or you possibly need to release some things (gridRef)?
I can't see any other issues with your code.
You can (and probably should) set your
answerLabel.text = gridRef;
gridRef is already an NSString, so you don't need to alloc it again.
EXC_BAD_ACCESS is usually a memory thing related to your retain/release count not balancing (or in my extensive experience of it :p).
Okay, the problem was trying to release NSStrings, so I've stopped doing that and the problem has been solved.
Can someone clarify how strings are retained and released. I was of the impression that:
string = #"My String"; is autoreleased.
NSString *string = [[NSString alloc] init...]; is not autoreleased and needs to be done manually.

NSURLCache crashes with autoreleased objects, but leaks otherwise

CSURLCache is designed to cache resources for offline browsing, as NSURLCache only stores data in-memory.
If cachedResponse is autoreleased before returning the application crashes, if not, the objects are simply leaked.
Any light that could be shed onto this would be much appreciated.
Please note stringByEncodingURLEntities is a category method on NSString.
#interface CSURLCache : NSURLCache {} #end
#implementation CSURLCache
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:[[[request URL] absoluteString] stringByEncodingURLEntities]];
if ([[NSFileManager defaultManager] fileExistsAtPath:path])
{
NSData *data = [[NSData alloc] initWithContentsOfFile:path];
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[request URL]
MIMEType:nil
expectedContentLength:[data length]
textEncodingName:nil];
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response
data:data];
[response release];
[data release];
return cachedResponse;
}
return nil;
}
#end
UPDATE: After submitting a radar to Apple it appears that this is a known issue (Radar #7640470).
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
Well, this isn't an alloc, new, or copy method…
… and CSURLCache doesn't hold on to the object anywhere, so it's not owning it.
So, you need to autorelease it.
Of course, that means the object is doomed unless something retains it. Your app crashed because it tried to use the object after the object died.
Run your app under Instruments with the Zombies template. Look at where the app crashes and what it was doing when cachedResponseForRequest: was called. The caller needs to own the object until the time when the application would crash otherwise, and then release it.

why shouldn't I release this string?

Look at the following method:
-(void)updateProfile:(Profile *)profile WithJSON:(NSString *)JSON;
{
SBJSON *parser = [[SBJSON alloc] init];
NSDictionary *object = [parser objectWithString:JSON error:nil];
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
[nf setPositiveFormat:#"#,##0"];
profile.displayName = [object valueForKey:#"displayName"];
profile.profileURL = [object valueForKey:#"profileURL"];
NSString *rep = [object valueForKey:#"reputation"];
profile.reputation = [[nf numberFromString:rep] intValue];
//[rep release]; <-Why not release?
[nf release];
//[object release]; <-Why not release?
[parser release];
}
I have commented out two lines, which gives me EXC_BAD_ACCESS if not.
Can someone explain to me why it's wrong to release these objects?
You shouldn't release it because you didn't +alloc, -retain, or -copy it. Convenience constructors like +objectWith… return autoreleased objects.
The better question to ask is: Why should you release it? What have you done to claim ownership over the object? The answer in this case is "nothing." Since you don't own it, you can't very well release it.