Potential leak of an object - NSData - xcode

When I Analyze a class I'm working on this line of code:
myObject.myImageData = [NSData dataWithContentsOfURL:[[NSURL alloc] initWithString:myObject.thumbnailUrlString]];
has a warning Potential leak of an object.
Any idea why and how you fix it?
==== Note
If I try and separate this line out I get additional errors, e.g.
NSData *myImageData = [NSData dataWithContentsOfURL:[[NSURL alloc] initWithString:myObject.thumbnailUrlString]]; // 1. Method returns an Objective-C object with a +0 retain count
myObject.myImageData = myImageData;
[myImageData release]; // 2. Incorrect decrement of the reference count of an object that is not owned at this point by the caller

You don't have ARC turned on. You almost certainly should turn on ARC so that the system will handle all of this for you.
That said, this is a basic manual memory management error, and the analyzer is telling you so.
myObject.myImageData = [NSData dataWithContentsOfURL:[[NSURL alloc] initWithString:myObject.thumbnailUrlString]];
This leaks the NSURL you created with +alloc. You need to call release on it at some point, but you no longer have a pointer to it. The usual way to fix this is with an autoreleased NSURL:
myObject.myImageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:myObject.thumbnailUrlString]];
But the much better way to fix it is to turn on ARC.
Your attempt at fixing it releases the wrong object. You don't own myImageData. You didn't create it with alloc, new, or copy, and you didn't call retain on it. The analyzer is warning you that you're incorrectly releasing it.

Related

CMPedometer is not running

My CMPedometer is not running.
The code before it and after it gets run, but it itself does not work.
I get no warning or exception.
I'm testing it on a real 5s.
I've tried both querydata and startpedometerupdates.
I am importing core motion and the library is linked.
Any help?
if ([CMPedometer isStepCountingAvailable] == YES)
{
CMPedometer *cmped;
[cmped queryPedometerDataFromDate:start toDate:[NSDate date] withHandler:^(CMPedometerData *pedometerData, NSError *error){
stepslabel.text = [pedometerData.numberOfSteps stringValue];
}];
}
The problem with the original code above is the cmped variable gets deallocated at the end of the if statement, so the query is destroyed before it finishes.
By changing it to a strong property, it is retained in memory for the life of the class.
It seems really odd but I got it working by not declaring in the .h or before using it. What worked was declaring as #property CMPedometer *cmped; right after interface

How to properly save a QTMovie after editing using QTKit?

I am making minor edits to a QTMovie in an application using NSDocument architecture (such as adding a track, as shown below). After the edit, I want to save to the original file. However, I keep getting a 'file is busy' error. I assume this is due to some oversight I made in the handling of the files, or a failure in how I am using NSDocument. Any tips would be helpful! Here is (some of) the relevant code:
// open file that has the track I want to add to my movie
QTMovie* tempMovie = [QTMovie movieWithURL:outputFileURL error:nil];
// Use old API to add the track
AddMovieSelection([movie quickTimeMovie], [tempMovie quickTimeMovie]);
// get the filename from the NSDocument
NSString *filename = [[self fileURL] path];
NSLog(#"writing to filename: %#", filename);
// FLATTEN the movie file so I don't get external references
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:1];
[attributes setObject:[NSNumber numberWithBool:YES] forKey:QTMovieFlatten];
// attempt to write
NSError *error;
// this is where I get the "file is busy"
if (![movie writeToFile:filename withAttributes:attributes error:&error]) {
NSLog(#"Error: %#", [error localizedDescription]);
NSRunAlertPanel(#"Error", [error localizedDescription], nil, nil, nil);
}
Do I have to first release the movie in my NSDocument? What is the "proper" way to do that? Keep in mind, I am not necessarily finished with this document, I am not closing it. I have just finished this operation, and I want the file on disk to reflect my changes. I would love to use [movie updateMovieFile], but that call doesn't seem to flatten the movie. I don't want any external references in my file.
I am not too familiar with the QuickTime C API, so I honestly can't tell you anything about what is going wrong there. Absolute guesswork: Maybe a call to EndMediaEdits is missing?
Though that shouldn't be required by AddMovieSelection, you said "[...] such as adding a track [...]". So maybe there is something else going on, like AddMediaSample or something similar?
That said, if you don't need to target anything below 10.5 and all you need to do is add some segment from another movie, you can achieve that without dropping down to the C API:
Have a look at
-[QTMovie insertSegmentOfTrack:fromRange:scaledToRange:]
and
-[QTMovie insertSegmentOfMovie:fromRange:scaledToRange:], if you want to have the inserted segment "overlayed" (temporally speaking).
-[QTMovie insertSegmentOfMovie:timeRange:atTime:] and -[QTMovie insertSegmentOfTrack:timeRange:atTime:], if you want { movieA.firstPart, movieB, movieA.secondPart }.
Do I have to first release the movie in my NSDocument?
You mean in order to write it to disk? No: That should even result in a crash.
The role of release is to handle memory-management. It doesn't have much to do with the busy-state of a file.
Turns out I just wasn't using the NSDocument architecture properly. When I changed it to use Save/SaveAs properly, this problem went away.

cocoa; What is the leak in this code?

NSMutableArray *tempData=[[NSMutableArray alloc]init];
TBXMLElement * city = [TBXML childElementNamed:#"city" parentElement:root];
while(city!=nil){
if([TBXML valueOfAttributeNamed:#"name" forElement:city]!=nil){
NSString *tempDataHolder=[NSString stringWithFormat :#"%#,%#",[TBXML valueOfAttributeNamed:#"name" forElement:city],[TBXML valueOfAttributeNamed:#"country_name" forElement:city]];
[tempData addObject:[tempDataHolder copy]];
[tempDataHolder release];
}
city = [TBXML nextSiblingNamed:#"city" searchFromElement:city];
}
tableData=[tempData copy];
[tableCities reloadData];
[tempData release];
Instruments with Memory leaks says there is a leak of multiple NSCFStrings,i have been trying to figure it out for a while, any help is highly appreciated.
Thanks
edit: The above set of code runs a few times, and i have a bunch of leaks referring to NSCFString - NSPlaceholderString. I am releasing tempDataHolder almost immediately and the rest of the variables are being released as well. I cant pin point on where the leak is.
Copied objects need to be released by the owner. That is, the copy method returns a new object that has a retain count of 1. In your situation, the culprit seems to be this line:
[tempData addObject:[tempDataHolder copy]];
Containers retain their elements, but the copied object already has a retain count of 1 before being inserted in the array. The copied object is therefore leaking.
Simply adding tempDataHolder in your array (not a copy) should solve it.
Also, tempDataHolder is an auto-released object and shouldn't be released explicitly.

Cocoa Webkit bug?

I have some code that gets the URL from an array and stores it as a string then uses that string to load up the URL.
NSString *first = [urls objectAtIndex:[sender clickedRow]];
NSLog(#"%#", first);
[[webview mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:first]]];
However, when I run this I get this error:
-[NSURL length]: unrecognized selector sent to instance 0x164dc0
The link in this case is http://www.digg.com
I know that the bug is in the line
NSString *first = [urls objectAtIndex:[sender clickedRow]];
because I tried setting the string's value directly to the URL instead of the array and it worked.
However, when I run this I get this error:
-[NSURL length]: unrecognized selector sent to instance 0x164dc0
First off, that's an exception, not an error.
When you get a message like this, read what it says:
-[NSURL
The object you sent this message to was an NSURL object.
length]:
The selector of the message was length.
Now, why would you send a length message to an NSURL object? You wouldn't, and you didn't do so yourself. Something else did.
But you would send a length message to a string object. So, you have an NSURL object, and you passed it somewhere that expected a string.
There's only one passage in the code you showed:
[NSURL URLWithString:first]
The exception tells you that first is already an NSURL; it is not a string. You do not need to create an NSURL from it, since it already is one, and trying to treat it as a string in any way will cause an exception.
You may be about to object to my claim on the grounds of this previous line:
NSString *first = [urls objectAtIndex:[sender clickedRow]];
Your objection would be that the declaration clearly says that first is a pointer to an NSString, so I must be wrong.
But that is not so. You declared first as a pointer to an NSString. That is to say, you told the compiler that the variable first would hold a pointer to an NSString.
But then you put a pointer to an NSURL into the variable.
In many cases, the compiler would warn you that you have lied to it, but it didn't in this case because the object came through objectAtIndex:, which returns id; thus, the compiler doesn't know what type of object you're putting into the variable. The compiler, assuming that you told the truth and really are putting an NSString here, does not warn for this initialization.
But you're not. The object is an NSURL, as you found out at run time.
The fix is two-fold:
Restore truth to the declaration, by declaring the variable as NSURL *, not NSString *.
Don't attempt to create an NSURL here, because you already have one.

How do I share a Core Data store between processes using NSDistributedNotifications?

Background
I've already posted a question about the basics of sharing a Core Data store between processes.
I'm trying to implement the recommendations given and I'm running into problems.
My Goal
I have two processes - the Helper App and the UI. They both share a single data store. I want the UI to update it's NSManagedObjectContext when the Helper App has saved new data to the store.
Current Program Flow
The Helper App Process writes data to the Store.
In the Helper App, I listen for NSManagedObjectContextDidSaveNotification notifications.
When the context is saved, I encode the inserted, deleted and updated objects using their URI representations and NSArchiver.
I send an NSNotification to the NSDistributedNotificationCenter with this encoded dictionary as the userInfo.
The UI Process is listening for the save notification. When it receives the notification, it unarchives the userInfo using NSUnarchiver.
It looks up all the updated/inserted/deleted objects from the URIs given and replaces them with NSManagedObjects.
It constructs an NSNotification with the updated/inserted/deleted objects.
I call mergeChangesFromContextDidSaveNotification: on the Managed Object Context of the UI Process, passing in the NSNotification I constructed in the previous step.
The Problem
Inserted objects are faulted into the UI Managed Object Context fine and they appear in the UI. The problem comes with updated objects. They just don't update.
What I've tried
The most obvious thing to try would
be to pass the save Notification
from the Helper App process to the
UI process. Easy, right? Well, no.
Distributed Notifications won't
allow me to do that as the userInfo
dictionary is not in the right
format. That's why I'm doing all the
NSArchiving stuff.
I've tried calling
refreshObject:mergeChanges:YES on
the NSManagedObjects to be updated,
but this doesn't seem to have any
effect.
I've tried performing the
mergeChangesFromContextDidSaveNotification:
selector on the main thread and the
current thread. Neither seems to
affect the result.
I've tried using
mergeChangesFromContextDidSaveNotification:
before between threads, which of
course is much simpler and it worked
perfectly. But I need this same
functionality between processes.
Alternatives?
Am I missing something here? I'm consistently getting the feeling I'm making this much more complex than it needs to be, but after reading the documentation several times and spending a few solid days on this, I can't see any other way of refreshing the MOC of the UI.
Is there a more elegant way of doing this? Or am I just making a silly mistake somewhere in my code?
The Code
I've tried to make it as readable as possible, but it's still a mess. Sorry.
Helper App Code
-(void)workerThreadObjectContextDidSave:(NSNotification *)saveNotification {
NSMutableDictionary *savedObjectsEncodedURIs = [NSMutableDictionary dictionary];
NSArray *savedObjectKeys = [[saveNotification userInfo] allKeys];
for(NSString *thisSavedObjectKey in savedObjectKeys) {
// This is the set of updated/inserted/deleted NSManagedObjects.
NSSet *thisSavedObjectSet = [[saveNotification userInfo] objectForKey:thisSavedObjectKey];
NSMutableSet *thisSavedObjectSetEncoded = [NSMutableSet set];
for(id thisSavedObject in [thisSavedObjectSet allObjects]) {
// Construct a set of URIs that will be encoded as NSData
NSURL *thisSavedObjectURI = [[(NSManagedObject *)thisSavedObject objectID] URIRepresentation];
[thisSavedObjectSetEncoded addObject:thisSavedObjectURI];
}
// Archive the set of URIs.
[savedObjectsEncodedURIs setObject:[NSArchiver archivedDataWithRootObject:thisSavedObjectSetEncoded] forKey:thisSavedObjectKey];
}
if ([[savedObjectsEncodedURIs allValues] count] > 0) {
// Tell UI process there are new objects that need merging into it's MOC
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:#"com.synapticmishap.lapsus.save" object:#"HelperApp" userInfo:(NSDictionary *)savedObjectsEncodedURIs];
}
}
UI Code
-(void)mergeSavesIntoMOC:(NSNotification *)notification {
NSDictionary *objectsToRefresh = [notification userInfo];
NSMutableDictionary *notificationUserInfo = [NSMutableDictionary dictionary];
NSArray *savedObjectKeys = [[notification userInfo] allKeys];
for(NSString *thisSavedObjectKey in savedObjectKeys) {
// Iterate through all the URIs in the decoded set. For each URI, get the NSManagedObject and add it to a set.
NSSet *thisSavedObjectSetDecoded = [NSUnarchiver unarchiveObjectWithData:[[notification userInfo] objectForKey:thisSavedObjectKey]];
NSMutableSet *savedManagedObjectSet = [NSMutableSet set];
for(NSURL *thisSavedObjectURI in thisSavedObjectSetDecoded) {
NSManagedObject *thisSavedManagedObject = [managedObjectContext objectWithID:[persistentStoreCoordinator managedObjectIDForURIRepresentation:thisSavedObjectURI]];
[savedManagedObjectSet addObject:thisSavedManagedObject];
// If the object is to be updated, refresh the object and merge in changes.
// This doesn't work!
if ([thisSavedObjectKey isEqualToString:NSUpdatedObjectsKey]) {
[managedObjectContext refreshObject:thisSavedManagedObject mergeChanges:YES];
[managedObjectContext save:nil];
}
}
[notificationUserInfo setObject:savedManagedObjectSet forKey:thisSavedObjectKey];
}
// Build a notification suitable for merging changes into MOC.
NSNotification *saveNotification = [NSNotification notificationWithName:#"" object:nil userInfo:(NSDictionary *)notificationUserInfo];
[managedObjectContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:saveNotification
waitUntilDone:YES];
}
I used the method in
http://www.mlsite.net/blog/?p=518
then every object is correctly faulted but the faults are fetch in cache so still no update
I had to do
[moc stalenessInterval = 0];
And it finally worked, with relationship.
You're looking for - (void)refreshObject:(NSManagedObject *)object mergeChanges:(BOOL)flag I believe.
This will refresh the object with the info in the persistent store, merging changes if you want.
I'd go with Mike's suggestion and just watch the store file for changes.
Though it may not be the most efficient, I've had success using - [NSManagedObjectContext reset] from a second process when there's a change to a store. In my case case, the code is fairly linear — all I do is run a fetch request for some data after resetting. I don't know how this will work with bindings and a complicated UI, but you may be able to post a notification to manually update things if it's not handled automatically.
I had this exact same issue with an iPhone app that I've been working on. In my case, the solution involved setting the Context's stalenessInterval to something suitably infinitesimal (e.g., 0.5 seconds).
This works, except for sandboxes apps. You can't send a notification with a user info dict. Instead consider some other IPC like XPC or DO.
On a side note, using NSDustributedNotificationCenter is not always 100% if the system is busy.
Setting stalenessInterval of managed object context works. My case involves multiple threads instead of process though.
Starting with iOS 9, you should now use mergeChangesFromRemoteContextSave:intoContexts:. See this for an explanation: https://www.innoq.com/en/blog/ios-writing-core-data-in-today-extension/

Resources