質問

I am trying to store images from a remote location to an NSMutableArray using a GCD block. The following code is being called in viewDidLoad, and the images are to be populated in a UICollectionView:

dispatch_apply(self.count, dispatch_get_global_queue(0, 0), ^(size_t i){

    NSString *strURL = [NSString stringWithFormat:@"%@%zu%@", @"http://theURL.com/popular/", i, @".jpg"];
    NSURL *imageURL = [NSURL URLWithString:strURL];
    NSData *imageData = [NSData dataWithContentsOfURL: imageURL];
    UIImage *oneImage =[UIImage imageWithData:imageData];
    if(oneImage!=nil){
        [self.imageArray addObject:oneImage];
    }

    });

PROBLEM: the images are not being linearly stored. eg.

[self.imageArray objectAtIndex:2] is not 2.jpg Even though it is setting first and last image correct, rest are all jumbled up.

Another way to do this (What I basically need, minus the time consumed and memory overhead):

for (int i=0; i<=[TMAPopularImageManager sharedInstance].numberOfImages-1; i++){

        NSString *strURL = [NSString stringWithFormat:@"%@%d%@", @"http://theURL.com/popular/", i, @".jpg"];
    NSURL *imageURL = [NSURL URLWithString:strURL];
    NSData *imageData = [NSData dataWithContentsOfURL: imageURL];
    UIImage *oneImage =[UIImage imageWithData:imageData];
    if(oneImage!=nil){
        [self.imageArray addObject:oneImage];
    }

}

Is there a better way to implement the GCD block in this case? I need the images in the array sequentially named.

役に立ちましたか?

解決

I ran this about 10 times and it did not print in order once:

NSInteger iterations = 10;
dispatch_apply(iterations, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long) NULL), ^(size_t index) {
    NSLog(@"%zu", index);
});

My suggestion, and what I have done in the past, is to just run a for loop inside the block of a background thread:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long) NULL);
dispatch_async(queue, ^{
    int iterations = 0;
    for (int x = 0; x < iterations; ++x) {
        // Do stuff here
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        // Set content generated on background thread to main thread
    });
});

When using background threads, it is important to make sure that either the the objects you initialized on the main thread are thread-safe or that you initialize objects in the background and then set the main thread's objects with the background-thread-created objects like in the above example. This is especially true with Core Data.

EDIT:

Seems like the iterations using dispatch_apply return immediately so they will probably execute out of order when doing anything meaningful. If you run these two, you will see that printf always runs in order but NSLog does not:

NSInteger iterations = 10;
dispatch_apply(iterations, the_queue, ^(size_t idx) {
    printf("%zu\n", idx);
});

dispatch_apply(iterations, the_queue, ^(size_t idx) {
    NSLog(@"%zu\n", idx);
});

In my opinion, it would be best to run a standard for statement in a background thread rather than dispatch_apply if the order is important.

EDIT 2:

This would be your implementation:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long) NULL);
dispatch_async(queue, ^{
    int iterations = 0;
    NSMutableArray *backgroundThreadImages = [NSMutableArray array];
    for (int i = 0; i < iterations; ++i) {
        NSString *strURL = [NSString stringWithFormat:@"%@%i%@", @"http://theURL.com/popular/", i, @".jpg"];
        NSURL *imageURL = [NSURL URLWithString:strURL];
        NSData *imageData = [NSData dataWithContentsOfURL: imageURL];
        UIImage *oneImage =[UIImage imageWithData:imageData];
        if(oneImage!=nil){
            [backgroundThreadImages addObject:oneImage];
        }
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        self.imageArray = backgroundThreadImages;
    });
});

他のヒント

Not sure if applicable to your situation, but if the images are going to be displayed in a UICollectionView, it is better to load them when required. That is, when the delegate methods (cellFor...) are invoked. That way you only load what is needed, when it's needed.

If you need to have the images in an array for some other purpose, ignore this answer.

two ways: sort the array afterwards

or

// assume self.imageArray is empty
for (int i = 0; i < self.count; ++i) // fill array with NSNull object
    [self.imageArray addObject:[NSNull null]]];

dispatch_apply(self.count, dispatch_get_global_queue(0, 0), ^(size_t i){

    NSString *strURL = [NSString stringWithFormat:@"%@%zu%@", @"http://theURL.com/popular/", i, @".jpg"];
    NSURL *imageURL = [NSURL URLWithString:strURL];
    NSData *imageData = [NSData dataWithContentsOfURL: imageURL];
    UIImage *oneImage =[UIImage imageWithData:imageData];
    if(oneImage!=nil){
        self.imageArray[i] = oneImage;
    }

});

I am not sure it is thread-safe to operate NSMutableArray this way, you may need

@synchronized(self.imageArray) {
    self.imageArray[i] = oneImage;
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top