Passing Blocks Between View Controllers

Iv searched a lot for this and couldn't find an answer to my specific question. But basically my question is, can I pass the completion block to another view controller and override it in the new view controller.

So, for example, in controller A there is a way to do the load with a completion block. First, I create a block property in controller A view.

@property (copy)void (^downloadCompleteBlock)(NSArray *downloadItems);

      

I tried changing this to strong rather than copy, but that didn't solve my problem.

Then I define the completion block as follows.

self.downloadCompleteBlock = ^(NSArray *downloadItems) {

    NSLOG(@"download complete in view controller A";
};

      

Then I call the load method going through that completion block.

[self download:self.downloadCompleteBlock];

      

However, if this completion handler is not called by the time that view controller exits (unless the download is complete), then I would like the completion block to do something different on the next view controller. So in my preparation for the segue, I tried to walk through this block to view controller B.

[controllerB setCompletionBlock:self.downloadCompleteBlock];

      

And this method in controller B's view then overrides what happens when this completion block is called.

- (void)setCompletionBlock:(void(^)(NSArray *downloadItems))downloadFinishedBlock {

downloadFinishedBlock = ^(NSArray *downloadItems) {

    self.collectionData = downloadItems;
    [self.collectionView reloadData];
};

      

}

However, the original block in the view controller is still called when the download ends, unlike the controller in the block. Anyone know how to have a completion block in controller B's view called if that view is loaded before loading exits? I know I can use a notifier, but I'm curious if I can do this with blocks.

thank

+3


source to share


1 answer


This is a rather difficult problem. At its core is the problem of how to block a block after the first view controller leaves. Your current code solves this problem unwittingly if the block references self

. The vc is kept at that link, which is good news if it should be around when the request ends, but that's bad news because now the vc and the block will hold each other forever. (Google "keep the loop".)

So how do we get a long running process that runs a block on completion and can outlive two or more view managers? To begin with, break this process into its own object. The interface of this object will look like this:

@interface DownloadThingy

@property (copy)void (^downloadCompleteBlock)(NSArray *);  // note, no need for dummy param names here
- (id)initWithRequestParams:(id)whateverIsNeededToStart;
- (void)start;

@end

      

Now the view controller that wants to run this can declare a strong property for it, create it, give it a completion block (see ** below) and run it. When it's time for a segue, it can pass to downloadThingy

another vc, which can give it another completion block.

** Since the request object is stored as a property in one or more vcs, and since it stores the block, you still need to look for the save loop: (VC-> downloadThingy-> block-> VC)

In VcA, do the following:

- (void)startADownloadThingy {
    self.downloadThingy = [[DownloadThingy alloc] initWithRequestParams:someParams];
    __weak VcA *weakSelf = self;
    self.downloadThingy.downloadCompleteBlock = ^(NSArray *downloadItems) {
        // don't use self in here, use weakSelf
    }
}

      



VcB will be called on segue; he may or may not have to follow the same precaution. The difference is whether this second vc retains the property downloadThingy

. If he doesn't plan on passing it to any other vc, he can skip the property and thus skip worrying about the save loop.

// another vc is handing off a running downloadThingy
- (void)heresARunningDownloadThingy:(DownloadThingy *)downloadThingy {
    // if we have our own property, then
    self.downloadThingy = downloadThingy;
    // and we need to do the weakSelf trick
    __weak VcA *weakSelf = self;
    self.downloadThingy.downloadCompleteBlock = ^(NSArray *downloadItems) {
        // don't use self in here, use weakSelf
    }
}

      

Or...

// another vc is handing off a running downloadThingy
- (void)heresARunningDownloadThingy:(DownloadThingy *)downloadThingy {
    // we do not have our own property
    downloadThingy.downloadCompleteBlock = ^(NSArray *downloadItems) {
        // feel free to use self in here
    }
}

      

One last thing: it is good practice downloadThingy

to aggressively nil

exit your block after him by calling him. So when the request is done, do this ...

// DownloadThingy.m
// request is complete
self.downloadCompleteBlock(arrayFullOfResults);
self.downloadCompleteBlock = nil;

      

+1


source







All Articles