"__block" variable results in nil value when go out of block - objective-c

I wanna use __block variable to get value in block. But when out of block, the __block variable seems to be nil. Why this would happen?
NSString *fileName = [Tools MD5Encode:url];
__block NSString *filePath = nil;
[fileList enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *aFileName = obj;
if ([aFileName isEqualToString:fileName]) {
NSString *path = [VERSIONS_INFO_DATA_DIRECTORY stringByAppendingPathComponent:aFileName];
filePath = path;
NSLog(#"filePath1 %#", filePath);
*stop = YES;
}
}];
//NSLog(#"filePath2 %#", filePath);
//filePath seems to be nil
return filePath;
When I change the code to [path copy], it works. But I have no idea whether this is a good idea. Any decision?
NSString *fileName = [Tools MD5Encode:url];
__block NSString *filePath = nil;
[fileList enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *aFileName = obj;
if ([aFileName isEqualToString:fileName]) {
NSString *path = [VERSIONS_INFO_DATA_DIRECTORY stringByAppendingPathComponent:aFileName];
filePath = [path copy];
NSLog(#"filePath1 %#", filePath);
*stop = YES;
}
}];
//NSLog(#"filePath2 %#", filePath);
return [filePath autorelease];

http://www.mikeash.com/pyblog/friday-qa-2011-09-30-automatic-reference-counting.html
Specifically:
Without ARC, __block also has the side effect of not retaining its contents when it's captured by a block. Blocks will automatically retain and release any object pointers they capture, but __block pointers are special-cased and act as a weak pointer. It's become a common pattern to rely on this behavior by using __block to avoid retain cycles.
Under ARC, __block now retains its contents just like other captured object pointers. Code that uses __block to avoid retain cycles won't work anymore. Instead, use __weak as described above.
So you need to copy.

It is ok here to use copy or retain on the path. The reason for your issue is that NSString objects are members of the convenience objects along with others like NSArray that you do not actually have to release and were already autoreleased by the system prior to the days of ARC. Personally, I didn't like that they did that cause it just caused confusion like this. Because the block finishes executing the system autoreleases the string object you allocated causing the leak.

Is the use of blocks even an issue here?
Seems to me that this sequence of code:
NSString *filePath = nil;
NSString *path = [VERSIONS_INFO_DATA_DIRECTORY stringByAppendingPathComponent:aFileName];
filePath = path;
return [filePath autorelease];
is over-releasing filePath (because you don't own the result of -stringByAppendingPathComponent:, you should not be (auto-)releasing it)

Related

dealing with autoreleased objects within dispatch_sync

What is the best solution to avoid bad access in this kind of situations ?
__block NSString* string;
dispatch_sync(dispatch_get_main_queue(), ^{
string = [NSString stringWithString:#"I'm autoreleased!"];
});
NSLog(#"My string is: %#", string);
I changed my code to this:
NSMutableString *string = [[NSMutableString alloc] init];
dispatch_sync(dispatch_get_main_queue(), ^{
[string appendString:#"I'm autoreleased!"];
});
NSLog(#"My string is: %#", string);
[string release];
but I was wondering if there no better solutions
Since you are calling release, you must be using manual reference counting. In manual reference counting, when you store something to a variable that will outlive the scope, you must retain it, and then if you will later assign something to that variable or that variable's lifetime will end, you need to release that variable first.
So you can do this:
__block NSString* string;
dispatch_sync(dispatch_get_main_queue(), ^{
string = [[NSString stringWithString:#"I'm autoreleased!"] retain];
// or equivalently:
// string = [[NSString alloc] initWithString:#"I'm autoreleased!"];
});
NSLog(#"My string is: %#", string);
[string release];

Sending "NSString *_strong*to parameter of type _unsafe_unretained id* "change retain/release properties of pointer

Im getting the following error
Sending "NSString *_strong*to parameter of type _unsafe_unretained id* "changes retain/release properties of pointer ...
in the following line: [theDict getObjects:values andKeys:keys];
Im trying to add an address from contacts to my app. Could someone explain to me what its complaining about? I think its an ARC issue, possibly to do with manual memory management? but im unsure how to fix it.
- (BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier
{
if (property == kABPersonAddressProperty) {
ABMultiValueRef multi = ABRecordCopyValue(person, property);
NSArray *theArray = (__bridge id)ABMultiValueCopyArrayOfAllValues(multi);
const NSUInteger theIndex = ABMultiValueGetIndexForIdentifier(multi, identifier);
NSDictionary *theDict = [theArray objectAtIndex:theIndex];
const NSUInteger theCount = [theDict count];
NSString *keys[theCount];
NSString *values[theCount];
[theDict getObjects:values andKeys:keys]; <<<<<<<<< error here
NSString *address;
address = [NSString stringWithFormat:#"%#, %#, %#",
[theDict objectForKey: (NSString *)kABPersonAddressStreetKey],
[theDict objectForKey: (NSString *)kABPersonAddressZIPKey],
[theDict objectForKey: (NSString *)kABPersonAddressCountryKey]];
_town.text = address;
[ self dismissModalViewControllerAnimated:YES ];
return YES;
}
return YES;
}
The docs for NSDictionary getObjects:andKeys: show it as:
- (void)getObjects:(id __unsafe_unretained [])objects andKeys:(id __unsafe_unretained [])keys
But the two values you are passing in are strong NSString references (local variables and ivars are strong by default. This is why there is the ARC error. Your parameters don't match the expected types.
Change:
NSString *keys[theCount];
NSString *values[theCount];
to:
NSString * __unsafe_unretained keys[theCount];
NSString * __unsafe_unretained values[theCount];
should fix the compiler issue.
This change means that none of the objects in your arrays are safely retained. But as long as 'theDict' doesn't go out scope before 'keys' and 'values' then you will be OK.
You're correct that it's an ARC error, its confused that you're trying to both assign NSArrays to NSStrings and you've tried to create an array of NSStrings, which I'm not sure will work in the way you intend.
I don't see where you're using them later, however you'll want to do
NSArray *keys, *values;
to get rid of the errors.

Objective C - NSString - Memory basics

I am trying to return an NSString that has been initialized from a plist.
If I comment out my release lines this code works. I would however like to release these objects from memory as I no longer need them.
I thought that 'initWithString' would copy the contents of the target string into my new string meaning I could safely release the NSMutableArray. But it isn't. Why not?
+ (NSString*) genImage {
NSString *path = [[NSBundle mainBundle] pathForResource:
#"Images" ofType:#"plist"];
NSMutableArray *arrayOfImages = [[NSMutableArray alloc] initWithContentsOfFile:path];
NSLog(#"%d", [arrayOfImages count]);
int indexToLoad = 0;
NSString *res = [[NSString alloc] initWithString:[arrayOfImages objectAtIndex:indexToLoad] ];
[arrayOfImages release];
[path release];
return res;
}
You do not retain the return value of -[NSBundle pathForResource:ofType:] (the path variable), so there is no need to release it (and doing so will cause a crash, most likely). However, you should autorelease res, as you do retain that. You can change your last line to
return [res autorelease];

TouchJson memory leak?

I'm using TouchJson to parse json data from facebooks graph api. I'm getting some memory leaks though, and I don't really understand why...
In my effort to find the leak, I've removed everything else, so the following code is what I'm left with. The leak is one NSCFString for each loop, and I understand that it comes from the assignement to myItem.date, but I don't understand why?
I'm using the latest version of TouchJson
NSError *error;
NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserializeAsDictionary:data error:&error];
NSArray *jsonArray = [jsonDictionary objectForKey:#"data"];
for (NSDictionary *jsonEntry in jsonArray) {
NSDictionary *fromDictionary = [jsonEntry objectForKey:#"from"];
NSString *userId = [fromDictionary objectForKey:#"id"];
// Continue if it is a post from Atlas
if (userId != nil && [userId isEqualToString:#"10465958627"]){
MyItem *myItem = [[MyItem alloc] init];
// This uncommented causes the leak, why?
myItem.date = [jsonEntry objectForKey:#"created_time"];
[myItem release];
}
}
Thank you for your help!
Edit: I forgot to mention that MyItem is just an object with a property like so
#property (nonatomic, copy) NSString *date;

release of previously deallocated object issue

I have a function which use for read one single line from a csv file.
But I got a release of previously deallocated object error, or sometimes the it is "double free" error.
I try to track down which object causes this error base on the error memory address, but I failed to do this.
Here's the code:
#interface CSVParser : NSObject {
NSString *fileName;
NSString *filePath;
NSString *tempFileName;
NSString *tempFilePath;
//ReadLine control
BOOL isFirstTimeLoadFile;
NSString *remainContent;
}
#property(nonatomic,retain) NSString *fileName;
#property(nonatomic,retain) NSString *filePath;
#property(nonatomic,retain) NSString *tempFileName;
#property(nonatomic,retain) NSString *tempFilePath;
#property(nonatomic,retain) NSString *remainContent;
-(id)initWithFileName:(NSString*)filename;
-(BOOL)checkAndCopyFile:(NSString *)filename;
-(BOOL)checkAndDeleteTempFile;
-(NSString*)readLine;
-(NSArray*)breakLine:(NSString*)line;
#end
#implementation CSVParser
#synthesize fileName;
#synthesize filePath;
#synthesize tempFileName;
#synthesize tempFilePath;
#synthesize remainContent;
-(id)initWithFileName:(NSString *)filename{
//ReadLine control
isFirstTimeLoadFile = TRUE;
self.fileName = filename;
self.tempFileName = [[NSString alloc] initWithFormat:#"temp_%#",fileName];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDir = [documentPaths objectAtIndex:0];
self.filePath = [documentDir stringByAppendingPathComponent:fileName];
self.tempFilePath = [documentDir stringByAppendingPathComponent:tempFileName];
if ([self checkAndCopyFile:fileName]) {
return self;
}else {
return #"Init Failure";
}
}
-(BOOL)checkAndCopyFile:(NSString *)filename{
BOOL isFileExist;
NSError *error = nil;
NSFileManager *fileManger = [NSFileManager defaultManager];
isFileExist = [fileManger fileExistsAtPath:filePath];
if (isFileExist) {
//Create a temp file for reading the line.
[fileManger copyItemAtPath:filePath toPath:tempFilePath error:&error];
return TRUE;
}else {
return FALSE;
}
}
-(NSString*)readLine{
NSError *error = nil;
//Read the csv file and save it as a string
NSString *tempFirstLine = [[[NSString alloc] init] autorelease];
NSString *stringFromFileAtPath = [[NSString alloc] init];
if (isFirstTimeLoadFile) {
NSLog(#"Into First Time");
stringFromFileAtPath = [NSString stringWithContentsOfFile:tempFilePath
encoding:NSUTF8StringEncoding
error:&error];
isFirstTimeLoadFile = FALSE;
}else {
NSLog(#"Not First Time");
NSLog(#"Not First Time count:%d",[remainContent retainCount]);
stringFromFileAtPath = remainContent;
remainContent = nil;
}
if ([stringFromFileAtPath isEqualToString:#""]) {
[stringFromFileAtPath release];
return #"EOF";
}
//Get the first line's range
NSRange firstLineRange = [stringFromFileAtPath rangeOfString:#"\n"];
//Create a new range for deletion. This range's lenght is bigger than the first line by 1.(Including the \n)
NSRange firstLineChangeLineIncludedRange;
if (stringFromFileAtPath.length > 0 && firstLineRange.length == 0) {
//This is the final line.
firstLineRange.length = stringFromFileAtPath.length;
firstLineRange.location = 0;
firstLineChangeLineIncludedRange = firstLineRange;
}else {
firstLineRange.length = firstLineRange.location;
firstLineRange.location = 0;
firstLineChangeLineIncludedRange.location = firstLineRange.location;
firstLineChangeLineIncludedRange.length = firstLineRange.length + 1;
}
//Get the first line's content
tempFirstLine = [stringFromFileAtPath substringWithRange:firstLineRange];
remainContent = [stringFromFileAtPath stringByReplacingCharactersInRange:firstLineChangeLineIncludedRange withString:#""];
[stringFromFileAtPath release];
error = nil;
return tempFirstLine;
}
And the following code shows how I use the class above:
CSVParser *csvParser = [[CSVParser alloc] initWithFileName:#"test.csv"];
BOOL isFinalLine = FALSE;
while (!isFinalLine) {
NSString *line = [[NSString alloc] init];
line = [csvParser readLine];
if ([line isEqualToString:#"EOF"]) {
isFinalLine = TRUE;
}
NSLog(#"%#",line);
[line release];
}
[csvParser release];
If I run the code, and finish the csv parsing, the App's main function will give me the double free error when it try to free the autorelease pool."* __NSAutoreleaseFreedObject(): release of previously deallocated object (0x6a26050) ignored"
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
Could someone help me solve this issue?
Thank you!
[pool release];
Do not use -retainCount.
The absolute retain count of an object is meaningless.
You should call release exactly same number of times that you caused the object to be retained. No less (unless you like leaks) and, certainly, no more (unless you like crashes).
See the Memory Management Guidelines for full details.
There are a few problems in your code:
you aren't following the correct init pattern. You should have a self = [super init...]; if (self) {...} in there somewhere.
tempFileName is a retain property and you assign it the result of alloc/init. It will be leaked.
An immutable empty string ([[NSString alloc] init]) is pretty much never useful. And, in fact, stringFromFileAtPath is being leaked (technically -- implementation detail wise there is an empty immutable singleton string and thus, no real leak, but.... still...)
Finally, the crash: your readLine method correctly returns an autoreleased object. Yet, your while() loop consuming the return value of readLine is also releaseing that return value, leading to a double-release and an attempt to free that which was already freed.
You should "build and analyze" your code. I bet the llvm static analyzer would identify most, if not all, of the problems I mentioned above (and probably some more I missed).
When building with the analyzer, do you have either "all messages" or "analyzer issues only" selected in the Build window? Because, looking at the code, I'm surprised the analyzer didn't catch the obvious problem with stringFromFileAtPath.
Excerpting the code, you have the following lines that manipulate stringFromFileAtPath:
NSString *stringFromFileAtPath = [[NSString alloc] init];
....
stringFromFileAtPath = [NSString stringWithContentsOfFile:tempFilePath
encoding:NSUTF8StringEncoding
error:&error];
....
stringFromFileAtPath = remainContent;
....
[stringFromFileAtPath release];
And remainContent is set by:
remainContent = [stringFromFileAtPath stringByReplacingCharactersInRange:firstLineChangeLineIncludedRange
withString:#""];
You are releasing an autoreleased object. By memory keeps going up, how are you measuring it? Don't use Activity Monitor as it is nearly as useless to developers as retainCount is misleading. Use Instruments.
Your tempFirstLine NSString object is declared with autorelease, and is returned as your NSString line, which is then released.
Try using this:
while (!isFinalLine) {
NSString *line = [csvParser readLine];
if ([line isEqualToString:#"EOF"]) {
isFinalLine = TRUE;
}
NSLog(#"%#",line);
}
Replac this:
NSString *stringFromFileAtPath = [[NSString alloc] init];
with this:
NSString *stringFromFileAtPath = nil;
and get rid of the [stringFromFileAtPath release] statements.
The first line creates a pointer to a new string object that you never use, because you immediately overwrite the pointer with a pointer to string objects from elsewhere, which you don't need to release because you don't own them/didn't create them. Since you are releasing them, you're getting a crash.
You make the same mistake with tempFirstLine.