سؤال

I am fetching a list of images that I want to download from a server. After filtering the list , i add all my network calls to an array of signals and merge them. The thing is that the network calls start, but meanwhile the racdisposable is destroyed and in the destroy method I call cancel on the download task. Calling cancel stops the request and the image is not pulled. Any suggestions on how I should do this? I am pretty new to IOS and ReactiveCocoa

This method starts the requests

- (void)fetchTagsFromServer{

[self CreateIfNotExistsTagsFolder];

//define URL object
NSString *urlString = [NSString stringWithFormat:@"http://studiotest.cloudapp.net:5508/api/sideviewapi/gettags"];
NSURL *url = [NSURL URLWithString:urlString];

//fetch tag list from server. After the json is here, start filtering and pulling the images
//from the server
[[self fetchJSONFromURL:url] subscribeNext:^(id x){

    NSDictionary* json = (NSDictionary*)x;

    //filter and pull images
    [[self handleTagsFromJson:json] subscribeNext:^(id x) {
        initTagsDone = false;

    } error:^(NSError *error) {
        NSLog([error description]);
    }] ; 
 }];
}

This ia a generic method to pull json from server

- (RACSignal *)fetchJSONFromURL:(NSURL *)url {
NSLog(@"Fetching: %@",url.absoluteString);

// RAC signal
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    //Create session task
    NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (! error) {

            NSError *jsonError = nil;
            id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
            if (! jsonError) {
                //if json returned with success sent to subscriber
                [subscriber sendNext:json];
            }
            else {
                // send json error to subscriber
                [subscriber sendError:jsonError];
            }
        }
        else {
            // send general error
            [subscriber sendError:error];
        }

        // send request completed
        [subscriber sendCompleted];
    }];

    //Starts the the network request once someone subscribes to the signal.
    [dataTask resume];

    // Creates and returns an RACDisposable object which handles any cleanup when the signal when it is destroyed.
    return [RACDisposable disposableWithBlock:^{
        [dataTask cancel];
    }];
}] doError:^(NSError *error) {
    // log error
    NSLog(@"%@",error);
}] ;
}

Here I combine all the signals

(RACSignal *) handleTagsFromJson:(NSDictionary*) json {

//hold tags dto's
NSMutableArray*  tags = [[NSMutableArray alloc] init];

NSMutableArray* signals = [[NSMutableArray alloc]init];

//create dto object from json
for (NSDictionary * item in json) {

    TagsDTO* dto =  [[TagsDTO alloc]init];
    dto.Id = [[item valueForKey:@"Id"] intValue];
    dto.BigIcon = [item valueForKey:@"BigIcon"];
    dto.SmallIcon = [item valueForKey:@"SmallIcon"];
    dto.SmallIconRaster = [item valueForKey:@"SmallIconRaster"];
    dto.Date = [TagsDTO dateWithJSONString:[item valueForKey:@"Modified"]];
    dto.Type = [[item valueForKey:@"Type"] intValue];

    [tags addObject:dto];

}

//foreach dto do stuff with db
for (TagsDTO* tagDTO in tags){

    //get DB tag by external ID
    TAG* dbTag = [self getTagsByExternalID:tagDTO.Id];

    //create holder strings for image names
    NSString* pickerTag;
    NSString* overlay;
    NSString* raster;

    //if db tag is null create null,else set values from server
    if(dbTag == NULL){

        TAG* tag = [NSEntityDescription insertNewObjectForEntityForName:@"TAG" inManagedObjectContext:self.managedObjectContext];

        tag.externalId = [NSNumber numberWithInt:tagDTO.Id];
        tag.pickerITag = pickerTag =  tagDTO.BigIcon;
        tag.overlayTag = overlay = tagDTO.SmallIcon;
        tag.rasterTag = raster = tagDTO.SmallIconRaster;
        tag.modified = tagDTO.Date;
        tag.type = [NSNumber numberWithInt:tagDTO.Type];
    }else{
        dbTag.externalId =  [NSNumber numberWithInt:tagDTO.Id];
        dbTag.pickerITag = pickerTag = tagDTO.BigIcon;
        dbTag.overlayTag = overlay = tagDTO.SmallIcon;
        dbTag.rasterTag = raster = tagDTO.SmallIconRaster;
        dbTag.modified = tagDTO.Date;
        dbTag.type = [NSNumber numberWithInt:tagDTO.Type];
    }
    NSError *error ;
    if (![self.managedObjectContext save:&error]) {
        NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);

    }

    //start downloading images
    //because there are 3 different types for each images, download from coressponding serveer folder

     //get picker picture
    [signals addObject:  [self fetchTagImage:pickerTag area:@"picker"]];
    //get overlay picture
    [signals addObject: [self fetchTagImage:pickerTag area:@"overlay"]];
    //get raster picture
    [signals addObject:[self fetchTagImage:pickerTag area:@"raster"]];

}

return [RACSignal combineLatest:signals];
}

And this is the signal for pulling the images from the server

(RACSignal *) fetchTagImage:(NSString*)tag area: (NSString*) area {

//create Url object, area is the folder on the server
NSString *urlStringIcon = [NSString stringWithFormat:@"http://studiotest.cloudapp.net:5508/content/uploads/%@/%@",area,tag];
NSURL *urlIcon = [NSURL URLWithString:urlStringIcon];

NSLog(@"Fetching: %@",urlIcon.absoluteString);

// RAC signal
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    //Create session task
     NSLog(@"internal image fetch: %@",urlIcon.absoluteString);

    NSURLSessionDownloadTask* dataTask = [self.session downloadTaskWithURL:urlIcon completionHandler:^(NSURL*location, NSURLResponse *response, NSError *error) {


        if(error == nil || error.code == NSURLErrorCancelled)
        {
            NSLog(@"Temporary file =%@",location);

            NSError *err = nil;

            //create file mananger
            NSFileManager *fileManager = [NSFileManager defaultManager];

            //get app document folder path
            NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];

            //create actual store path
            NSString *dataPath = [docsDir stringByAppendingPathComponent:@"/tags"];

            //compile filename
            NSString* filename =   [NSString stringWithFormat:@"%@-%@",area,[response suggestedFilename]];

            //create URL to move temporary item to
            NSURL *docsDirURL = [NSURL fileURLWithPath:[dataPath stringByAppendingPathComponent:filename]];
            if ([fileManager moveItemAtURL:location
                                     toURL:docsDirURL
                                     error: &err])
            {
                [subscriber sendNext:filename];
                NSLog(@"File is saved to =%@",docsDir);
            }
            else
            {
                [subscriber sendError:err];
                NSLog(@"failed to move: %@",[err userInfo]);
            }

        }else {
            // send general error
            [subscriber sendError:error];
        }

        // send request completed
        [subscriber sendCompleted];
    }];

    //Starts the the network request once someone subscribes to the signal.
    [dataTask resume];

    // Creates and returns an RACDisposable object which handles any cleanup when the signal when it is destroyed.
    return [RACDisposable disposableWithBlock:^{
        [dataTask cancel];
    }];
}] doError:^(NSError *error) {
    // log error
    NSLog(@"%@",error);
}];

}
هل كانت مفيدة؟

المحلول

Let's check a couple things first:

racdisposable is destroyed

It's OK for a RACDisposable to be deallocated. Note that that doesn't call the disposable's dispose block.

I don't see any obvious reason that the disposable would be disposed of. Is the signal erroring? Could you set a breakpoint and see the stack trace that calls the cancel?

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top