I'd like to understand why this crashes with an EXC_BAD_ACCESS error. It returns from the method call fine, but then crashes immediately afterwards on the [self runMethodThatAssignsError:&error] .
I've found a similar post here, but it doesn't explain what is going on, and is rather old.
- (void)checkError {
NSError *error;
[self runMethodThatAssignsError:&error]; // crashes after returning
NSLog(#"success");
}
- (BOOL)runMethodThatAssignsError:(NSError **)error {
[#[#1] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
*error = [NSError errorWithDomain:#"1" code:7 userInfo:#{}];
}];
return NO;
}
Running your example code in Instruments, it appears that -[NSArray enumerateObjectsUsingBlock:] is wrapping its block in an autorelease pool. Since NSError ** pointers are, by default, implicitly assumed to be __autoreleasing, your NSError object is autoreleased when it is assigned to *error, and consequently reaped by -[NSArray enumerateObjectsUsingBlock:]'s autorelease pool.
There are two ways to fix this. The first one is to use a local variable outside of the block, to cause ARC to retain it until after the enumeration has finished:
- (BOOL)runMethodThatAssignsError:(NSError **)error {
__block NSError *_error = nil;
[#[#1] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
_error = [NSError errorWithDomain:#"1" code:7 userInfo:#{}];
}];
if (error) *error = _error;
return NO;
}
Alternatively, you can just declare the error parameter as __strong, which will prevent the NSError from being put in the autorelease pool in the first place. Note that this should only be done if the clients of this method are always going to be using ARC, because otherwise it will probably cause the errors to leak as the clients will not expect to have to release them, due to this approach being unconventional.
- (BOOL)runMethodThatAssignsError:(NSError * __strong *)error {
[#[#1] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (error) *error = [NSError errorWithDomain:#"1" code:7 userInfo:#{}];
}];
return NO;
}
Related
I have several methods that have the following structure:
- (void) doSomethingWithCompletion: (void (^)(NSError *error)) completion {
__block NSError *fetchError = nil;
dispatch_group_t dispatchGroup = dispatch_group_create();
for (Item* item in self.items)
{
dispatch_group_enter(dispatchGroup);
// fetchError = fetch online data
}
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(),^{
if (completion)
completion(fetchError);
});
}
My goal is to run several doSomethings after each other, so I could so something like this:
[self doSomethingAWithCompletion: ^(NSArray *results NSError *error) {
if (error == nil) {
[self doSomethingBWithArray: results withCompletion: ^(NSError *error) {
if (error == nil) {
[self doSomethingCWithCompletion: ^(NSError *error) {
if (error == nil) {
// done!!
}
}];
}];
}];
What I am struggling with is the second code block (no pun); is nesting all the methods the way to go, or are there other solutions?
The important thing is, is that doSomethingBWithCompletion cannot begin before doSomethingAWithCompletion is done, and doSomethingCWithCompletion needs to wait until doSomethingBWithCompletion is complete, etc.
Also, doSomethingBWithCompletion uses data that is generated in doSomethingAWithCompletion, etc.
EDIT: After a lot of thinking, refactoring, and simplifying my code, I was able to end up with only two functions, using the nested approach as I outlined above and with a #property for the results array.
The important thing is, is that doSomethingBWithCompletion cannot begin before doSomethingAWithCompletion is done, and doSomethingCWithCompletion needs to wait until doSomethingBWithCompletion is complete, etc.
According to the comments:
The Results of the block are not depending on the result of the first aren't they?
And
Yes they are. For instance, in the first doSomething I determine which items are outdated, in the second doSomething I download and parse the updated items, and in the third doSomething I save them to the store.
(BTW: You should really add this information to your Q.)
If an action depends on the result (not only execution) of a previous action, you have to nest the blocks. Your code does not look like this, because there is no data passed to the completion blocks.
If you do not have such a dependency, you could use a private serial dispatch queue. However, this is a solution in your case, too, if you have akin of a manager class holding the data passed from block to block. But this seems to be highly anticonceptual.
There may be community attempt to add promises to objective-c, and it would be nice to have, because that's just what's needed here. Without committing to a whole new library, you can handle the nesting (which I agree is a bummer) by doing the async tasks recursively... something like this for your example code:
Start with an operation that takes no params and results in an array...
- (void)firstOpWithCompletion:(void (^)(NSArray *, NSError *))completion {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSArray *components = [#"this is an array of strings from the FIRST op" componentsSeparatedByString:#" "];
if (completion) {
completion(components, nil);
}
});
}
Here are a couple that take an array param and result in an array...
- (void)secondOpWithParam:(NSArray *)array completion:(void (^)(NSArray *, NSError *))completion {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
if (completion) {
NSArray *components = [#"these strings are from the SECOND op" componentsSeparatedByString:#" "];
NSArray *result = [array arrayByAddingObjectsFromArray:components];
if (completion) {
completion(result, nil);
}
}
});
}
- (void)thirdOpWithParam:(NSArray *)array completion:(void (^)(NSArray *, NSError *))completion {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
if (completion) {
NSArray *components = [#"these strings are from the THIRD op" componentsSeparatedByString:#" "];
NSArray *result = [array arrayByAddingObjectsFromArray:components];
if (completion) {
NSLog(#"we did it. returning %#", result);
completion(result, nil);
}
}
});
}
// ...as many as these as you need
Now, as in my answer prior to this edit, we just add a param pass initially and in the intermediate calls...
- (void)doSeveralThingsInSequence:(NSArray *)todo param:(NSArray *)param {
if (todo.count == 0) return;
// you could generalize further here, by passing a "final" block and run that before the return
NSString *nextTodo = todo[0];
SEL sel = NSSelectorFromString(nextTodo);
IMP imp = [self methodForSelector:sel];
void (*func)(id, SEL, NSArray *, void (^)(NSArray *, NSError *)) = (void *)imp;
func(self, sel, param, ^(NSArray *result, NSError *error) {
if (!error) {
NSArray *remainingTodo = [todo subarrayWithRange:NSMakeRange(1, todo.count-1)];
[self doSeveralThingsInSequence:remainingTodo param:result];
}
});
}
Stepping through the code: this method bails if there's nothing to do, otherwise it takes the next selector name from the passed array, gets the C function implementation for it and invokes it, placing a completion block on the call stack that starts the process over for the remaining selectors.
Finally, doEverything calls the first operation to get started, then starts running a list of operations (which can be an arbitrarily long list) passing the array output from one as the array input to the next. (You could generalize this further by passing id's along the chain
- (void)doEverything {
[self firstOpWithCompletion:^(NSArray *array, NSError *error) {
NSArray *todo = #[ #"secondOpWithParam:completion:", #"thirdOpWithParam:completion:" ];
[self doSeveralThingsInSequence:todo param:array];
}];
}
I tested this exactly as posted and saw the expected output:
(
this,
is,
an,
array,
of,
strings,
from,
the,
FIRST,
op,
these,
strings,
are,
from,
the,
SECOND,
op,
these,
strings,
are,
from,
the,
THIRD,
op
)
I understand that this function first return "images" then "findObjectsInBackgroundWithBlock" retrieve data that's why results is nil.
1 - how to return array from block?
2 - how to put this block not in main thread?
+(NSMutableArray *)fetchAllImages{
__block NSMutableArray *images = [NSMutableArray array];
PFQuery *query = [PFQuery queryWithClassName:#"Photo"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *object in objects) {
PFFile *applicantResume = object[#"imageFile"];
NSData *imageData = [applicantResume getData];
NSString *imageName = [ImageFetcher saveImageLocalyWithData:imageData FileName:object.objectId AndExtention:#"png"];
[images addObject:imageName];
// here images is not empty
}
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
// here images is empty
return images;
}
The method performs its work asynchronously, and the caller needs to know that. So,
Do not:
+(NSMutableArray *)fetchAllImages{
return an array, because the array is not ready at the time of return.
Do:
+ (void)fetchAllImages {
return nothing, because that's what you have when the method finishes execution.
But how to give the images to the caller? The same way that findObjectsInBackgroundWithBlock does, with a block of code that runs later....
Do:
+ (void)fetchAllImagesWithBlock:(void (^)(NSArray *, NSError *)block {
Then, using your code from within the findBlock:
[images addObject:imageName];
// here images is not empty
// good, so give the images to our caller
block(images, nil);
// and from your code, if there's an error, let the caller know that too
NSLog(#"Error: %# %#", error, [error userInfo]);
block(nil, error);
Now your internal caller calls this method just like your fetch code calls parse:
[MyClassThatFetches fetchAllImagesWithBlock:^(NSArray *images, NSError *error) {
// you can update your UI here
}];
Regarding your question about the main thread: you want the network request to run off the main, and it does. You want the code that runs after it finishes to run ON the main, so you can safely update the UI.
It doesn't work that way.
You are calling an asynchronous method. You can't wait for the result of an asynchronous method and return the result (well, you can, but not if you are asking how to do it on stackoverflow). With an asynchronous block, you trigger an action, and it is up to the completion block to deliver the results where they are needed.
There are gazillions of examples how to do this on stackoverflow. Looking for them is your job.
Here is what I want to achieve. 1. Searching all the files 2. find all .jpg files during the searching 3. save all .jpg file paths into NSMutableArray
Here are the codes:
Created the NSMutableArray:
NSMutableArray *jpgFiles = [[[NSMutableArray alloc]init]autorelease];
Searching all the parent folders under (/Users/) path (Start NSThread in here):
NSString* filePath = [url path];
NSArray *dirFiles = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:filePath error:nil];
if([dirFiles count]!=0)
{
for (int j=0; j<[dirFiles count]; j++) {
NSString* pathExtension = [[dirFiles objectAtIndex:j] pathExtension];
//if extension is null, we forwards to next level.
if ([pathExtension isEqualTo:#""])
{
#autoreleasepool {
[NSThread detachNewThreadSelector:#selector(searchingPicture:) toTarget:self withObject:[filePath stringByAppendingPathComponent:[dirFiles objectAtIndex:j]]];
}
}
else
{
//if find jpg in this level, save into array
if([pathExtension isEqualTo:#"JPG"])
{
[jpgFiles addObject:[filePath stringByAppendingPathComponent:[dirFiles objectAtIndex:j]]];
}
}
}
}
Keep searching the rest of sub folders and save proper file path into array:
-(void)searchingPicture:(NSString*)path
{
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
NSURL *directoryURL = [NSURL URLWithString:[path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSArray *keys = [NSArray arrayWithObject:NSURLIsDirectoryKey];
NSDirectoryEnumerator *enumerator = [fileManager
enumeratorAtURL:directoryURL
includingPropertiesForKeys:keys
options:0
errorHandler:^(NSURL *url, NSError *error) {
// Handle the error.
// Return YES if the enumeration should continue after the error.
return YES;
}];
for (NSURL *url in enumerator) {
NSError *error;
NSNumber *isDirectory = nil;
if (! [url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {
// handle error
}
else if (! [isDirectory boolValue]) {
// No error and it’s not a directory; do something with the file
if([[[url path] pathExtension]isEqualTo:#"JPG"])
{
//This line gives me error !!!
[jpgFiles addObject:[url path]];
}
}
}
}
Error: (At beginning, it works fine and save many different files into array, but after saved around 50 files it starts to give me error and crash at the end).
Here is the correct element adds into array:
/Users/NAME/Documents/Graduate Books/IMG_2517.JPG
Here is the error message:
-[NSPathStore2 addObject:]: unrecognized selector sent to instance 0x10011d4d0
However, even this error occurs, it still keeps saving some of paths into array and then it will throw another error:
An uncaught exception was raised
Could you guys tell me how to fix it?? Thanks !!
First, trying to increase performance by randomly spawning threads is guaranteed failure. Concurrency must be considered and controlled.
Secondly, trying to decrease execution time of code that is accessing a slow resource (like the filesystem) by concurrently accessing said resource without constraint will be slower than serialized access. I/O to filesystems is relatively slow and linear I/O is always faster than concurrent, conflicted, random I/O.
Finally, NSMutableDictionary is not thread safe. Nor are the other mutable collection classes. If you are shoving stuff into collections from multiple threads, you'll see undefined behavior (typically crashes).
NSMutableArray is not thread safe. It is thread safe when you guard it correctly -- so that no more than one thread is able to use it any time.
To illustrate:
- (void)addPath:(NSString *)pPath
{
[self.lock lock];
[self.files addObject:pPath];
[self.lock unlock];
}
- (NSUInteger)countPaths
{
[self.lock lock];
const NSUInteger count = self.files.count;
[self.lock unlock];
return count;
}
- (NSArray *)copyPaths
{
[self.lock lock];
NSArray * paths = [self.files copy];
[self.lock unlock];
return paths;
}
And as bbum pointed out, directory enumeration as seen in your example is not a problem which lends itself well to parallelization -- Parallelization hurts in this scenario. A more practical approach would be to enumerate from just one thread. If you want to immediately load some of the images, just load them from the "I/O thread" as well.
can anyone tell me please why returnSet is returning as nil when there are lowercase characters in 'program'
I have stepped through and the NSLog is definitely picking the variables out but when it addObject: it just doesn't?
+ (NSSet *)variablesUsedInProgram:(id)program
{
NSMutableSet *returnSet = [[NSMutableSet alloc]init];
if ([program isKindOfClass:[NSArray class]]) {
[program enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop)
{
if ([obj isKindOfClass:[NSString class]]) {
if ([obj rangeOfCharacterFromSet:[NSCharacterSet lowercaseLetterCharacterSet]].location != NSNotFound) {
NSLog(#"Variable: %#", obj);
[returnSet addObject:obj];
}
}
}];
}
return returnSet;
}
The posted code has no bug. It cannot return a value of nil.
Your error is elsewhere.
I'm guessing that your problem is an ARC memory management problem. The code you posted returns a non-owning reference to the set it creates. Unless you save it to a strong instance variable, it will be deallocated.
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)