Iv searched a lot for this and cant find an answer to my specific question. But basically my question is, can i pass a completion block into another view controller and redefine it in the new view controller.
So for example in view controller A i have a method to perform a download with a completion block. First i create my block property in view controller A.
#property (copy)void (^downloadCompleteBlock)(NSArray *downloadItems);
I tried changing this to strong as opposed to copy but this did not 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 my download method passing in this completion block.
[self download:self.downloadCompleteBlock];
However, if this completion handler is not called by the time i leave this view controller (if the download isn't complete) I would want the completion block to perfrom something different on the next view controller. So in my prepare for segue i attempted to pass in this block to view controller B.
[controllerB setCompletionBlock:self.downloadCompleteBlock];
And this method in view controller B then redefines what happens when this completion block gets called.
- (void)setCompletionBlock:(void(^)(NSArray *downloadItems))downloadFinishedBlock {
downloadFinishedBlock = ^(NSArray *downloadItems) {
self.collectionData = downloadItems;
[self.collectionView reloadData];
};
}
However, the original block in view controller a still gets called when the download finishes as opposed to the block in view controller B. Anyone know how to have the completion block in view controller B called if that view is loaded before the download completes? I know i could use a notifier but i'm curious if i can do this with blocks.
Thanks
This is kind of a tough problem. At its heart is the problem of how to keep the block around after the first view controller goes away. Your current code solves that problem unwittingly by having the block refer to self. The vc is retained by that reference, which is good news if it needs to be around when the request finishes, but it's bad news because now the vc and the block will retain each other forever. (Google 'retain cycle'.)
So how do we get a long-running process that runs a block on completion and might outlive two or more view controllers? For starters, break that process into its own object. The interface of that object would look like:
#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 want to start this can declare a strong property to it, create one, give it a completion block (see below**), and start it. When it's time for a segue, it can pass the downloadThingy to another vc, who can give it a different completion block.
** Since the request object is being kept as a property in one or more vcs, and since it retains the block, you still need to look out for a retain cycle:
(vc->downloadThingy->block->vc)
In VcA, do this:
- (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 get called on the segue; it might or might not need to follow the same precaution. The distinction is whether this second vc retains a downloadThingy property. If it doesn't plan to hand it off to any other vc, it can skip the property, and thereby skip the worry about a retain cycle.
// 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's a good practice for the DownloadThingy to aggressively nil out its block after it's through invoking it. So when the request is done, have it do this...
// DownloadThingy.m
// request is complete
self.downloadCompleteBlock(arrayFullOfResults);
self.downloadCompleteBlock = nil;
Related
I have a viewcontroller class and another class of NSObject. I call from the viewcontroller class with the following method the NSObject class.
SubmitContentViewController class
#implementation SubmitContentViewController
-(void)viewDidLoad{
[self callUploadQueueClass];
}
-(void)callUploadQueueClass{
UploadQueueClass *queue = [UploadQueueClass new];
[self generateIDforImage];
}
#end
UploadQueueClass
#implementation UploadQueueClass
-(void)generateIDforImage{
#weakify(self)
[[[ApiServicesProvider shared] userService] getCreatorsContentID:^(NSDictionary * _Nullable result, NSError * _Nullable error) {
#strongify(self)
if(nil==error){
NSString* ccID = result.creatorsContentId;
self.creatorsContentID = ccID;
NSLog(#"creatorsContentID %#",self.creatorsContentID);
[self getImageUploadURL:ccID withNumberOfAttempts:10];
}
else{
self.isUploading = NO;
}
}];
}
#end
at this line
NSLog(#"creatorsContentID %#",self.creatorsContentID);
creatorsContentID is null although at this line
self.creatorsContentID = ccID;
ccID is not null so self.creatorsContentID should not be null.
Moreover at this line
[self getImageUploadURL:ccID withNumberOfAttempts:10];
is never get called.
What am i missing?
You are creating your UploadQueueClass instance as a local variable *queue in callUploadQueueClass.
This local variable holds a strong reference to the UploadQueueClass instance.
As soon as the function returns, that local variable is released and it no longer holds the strong reference.
This happens before getCreatorsContentID has completed its work. Before the completion handler block is called.
You have used #weakify so that the self captured by the block does not hold a strong reference to the UploadQueueClass instance. The local variable has been released and the block self doesn't hold a strong reference to the instance. Nothing does, so it is released.
The self in the block is now nil. Using #Strongify won't help you here; the object has already gone away;
In this case you don't need to use #weakify; There is no danger of a circular reference causing a memory leak; The blocks capture of self only lasts until the completion handler has done its work.
However, removing #weakify doesn't seem like it would really help since there doesn't seem to be any way for the UploadQueueClass instance to communicate its results back to the calling view controller.
It would be more typical for the view controller to provide the completion handler block to the function it is calling, or at least provide some block to be executed. This is where you could use #weakify since the view controller instance would be self, but the block doesn't need to hold a strong reference to it to keep it around; The view controller hierarchy is doing that.
Since you don't want this object to report back to the view controller, simply remove the #weakify/#strongify. Then the block itself will hold a strong reference to the UploadQueueClass instance until it returns and then the object will be released by ARC.
So somehow my weakSelf variable is being deallocated before my block has a chance to execute. This only happens in one specific scenario, the other times I hit this block it works fine. Here's what my code looks like:
__weak typeof(self) weakSelf = self;
DBTEligibleAccountFetcher *accountFetcher = [[DBTEligibleAccountFetcher alloc] init];
NSArray *eligibleDepositAccounts = [accountFetcher fetchDepositEligibleAccounts];
if(eligibleDepositAccounts.count == 1) {
DBTDepositAmountLimitsHandler *limitChecker = [[DBTDepositAmountLimitsHandler alloc] init];
[limitChecker handleRequest:self.navigationController bankAccount:eligibleDepositAccounts.firstObject completionBlock:^(DBDepositCheckAccountLimits *limitDetails) {
containerController.limitDetails = limitDetails;
[weakSelf.navigationController handleNewRootPush:containerController withCompletion:completionBlock animated:YES];
}];
} else {
[self.navigationController handleNewRootPush:containerController withCompletion:completionBlock animated:YES];
}
By the time it gets to the weakSelf.navigationController... line, weakSelf is nil. I'm not sure how to even debug this or how the variable could get deallocated before it is even used.
Does anyone know any scenarios that could be going on right now? Or any tips on how I might go about debugging this issue?
Let's say you have a view controller, the user taps a button, therefore you send some information to the server asynchronously and you are prepared to handle when the request comes back. Meanwhile your user switches to another view and your view controller gets deallocated before your method returns.
First consider what you want to happen. Sometimes a request has become pointless if the view controller is gone. Say you have a site that translates German to French. If the translation comes back and your view controller is gone, you just ignore the result. But if you have to process the result, whether the view controller is there or not, then you need to rearrange your code so that it works even if the view controller is nil because the user switched to a different view.
I call a heartBeats method per 10ms in a specific thread(not main thread), how to call another method at any time in this same thread?
I subclass NSThread like this
#implementation MyThread
{
NSTimeInterval _lastTimeInterval;
}
- (void)main
{
while (true) {
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970]*1000;
if (timeInterval - _lastTimeInterval > 10)
{
[self heartBeats];
_lastTimeInterval = timeInterval;
}
}
}
- (void)heartBeats
{
NSLog(#"heart beats thread: %#", [NSThread currentThread].description);
}
#end
and run it like this
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"main thread: %#", [NSThread currentThread].description);
MyThread *myThread = [[MyThread alloc]init];
[myThread start];
}
- (void)someMethod
{
// do somthing
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#end
Now,here is the question, how to run - (void)someMethod in myThread?
The main method of your NSThread subclass is everything that runs on that thread. You cannot interrupt it to run other code without the main method's cooperation.
What you really should do is throw out that entire loop and replace it with NSRunLoop and NSTimer.
NSRunLoop keeps the thread alive as long as there's something it will need to do, but also sleeps the thread until it needs to do something.
NSTimer does something on a repeating interval as long as it's scheduled on a run loop.
You need your thread to do two things:
send the MyThread object a heartBeats message (you're doing this)
send the view controller a someMethod message (this is what you asked about)
For the latter, you need one additional thing: A run loop source.
So, clear out your main method and replace it with the following:
Get the current NSRunLoop and store it in a local variable.
Create an NSTimer with a 10-second interval, whose target is self and selector is heartBeats. (Slightly cleaner version: Have another method that takes an NSTimer *but ignores it, so your timer calls that method and that method calls heartBeats. The proper way to set up a timer is to give it a method that expects to be called with a timer, but, in practice, giving it a method that takes no arguments works, too.)
If you didn't create the timer using scheduledTimerWith…, add it to the run loop. (The scheduledTimerWith… methods do this for you.)
Create a run loop source and add it to the run loop.
Call [myRunLoop run].
Step 4 bears explaining:
Core Foundation (but not Foundation; I don't know why) has something called “run loop sources”, which are custom objects that can be added to a run loop and will call something once signaled.
Sounds like what you want, to call your view controller method!
First, in the view controller, change myThread from a local variable in viewDidLoad to an instance variable. Rename it _myThread to make that clear.
Next, add a delegate property to MyThread. This should be weak and have type id <MyThreadDelegate>. You'll also need to define a MyThreadDelegate protocol, which should have one method taking no arguments and returning nothing (void).
You should now be able to set _myThread.delegate = self from the view controller, and implement in the view controller the method that you declared in the protocol. The view controller is now the delegate of its MyThread.
In -[MyThread main], create a version-0 CFRunLoopSource. The Create function takes a “context” structure, containing, among other things, the version (0), an “info” pointer (set this to self, i.e., the MyThread) and a Perform callback (a function, which will be called with the info pointer as its only argument).
In your perform callback, you'll need to do something like this:
MyThread *self = (__bridge MyThread *)info;
[self fireDelegateMessage];
In MyThread, implement that fireDelegateMessage method. In there, send self.delegate the message you declared in your protocol.
Next, add a public method to MyThread (i.e., declare it in MyThread.h as well as implementing it in MyThread.m) named something like “requestDelegateMessage”. In this method, call CFRunLoopSourceSignal on the run loop source. (The documentation for that function suggests that you also need to call CFRunLoopWakeUp on the thread's CFRunLoop. Try it without first.)
Lastly, when the view controller wants someMethod to be called on that thread, call [_myThread requestDelegateMessage].
So:
the view controller calls requestDelegateMessage
requestDelegateMessage signals the run loop source (and wakes up the run loop, if that is needed)
the run loop source calls the perform callback on the MyThread's thread
the perform callback calls fireDelegateMessage on the MyThread's thread
fireDelegateMessage calls the view controller's implementation of the delegate method on the MyThread's thread
the view controller calls someMethod on the MyThread's thread
I have view controller 1 let's say VC1 and I am pushing my another view controller let's say VC2.
In VC1 I have type defined my block as follow
#define typedef void(^Myblock)();
In VC2 I have declared a block as property as follow
#property(nonatomic, weak) MyBlock myBlock;
Before push VC2 I am assigning a block literal as follow
-(void)pushVC2
{
__weak VC1 *weakSelf = self;
VC2 *vc2 = [self.storyboard instantiateViewControllerWithIdentifier:#"VC2"];
[vc2 setMyBlock:^{
NSLog(#"Block executed");
}];
}
My problem is, the block is not executed if I called my block from VC2. if I declared the property as copy then block is executed. Any one has the explanation for this.
Hope this will clear you concept :-
Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior. For more information, see Blocks Programming Topics.
Source : - https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
I have got two UIViewController:
MainVC
PickerVC
In main view controller I have IBAction method:
- showPickerView
In this method I need to create block that will present PickerVC view and wait me while I choose something value on PickerVC view and press Done button.
In this block I need to implement callback that will invoke method in MainVC after I press on button Done.
So, I have used block before, but I don't know how to implement it by myself.
I think first part will be look like this:
- (IBAction)showPickerView {
__block PickerVC *pickerVC = [[PickerVC alloc] init];
[pickerVC setFinishBlock:^{
// Do something after user press on Done button
}];
[pickerVC setFailedBlock:^{
// Do something if something wrong (but this method optional)
}];
[pickerVC showPicker];
}
Add in the header of PickerVC two typedefs
typedef void (^FinishBlock)();
typedef void (^FailedBlock)();
and your declaration of setFinishedBlock takes the FinishBlock
- (void)setFinishBlock:(FinishBlock)finishBlock;
- (void)setFailedBlock:(FailedBlock)failedBlock;
Make an iVar for each block
#interface PickerVC : UIViewController
{
FinishBlock _finishBlock;
FailedBlock _failedBlock;
}
In your definition of setFinishedBlock: and setFailedBlock: set the parameter to the iVars
As soon as PickerVC fails or finishes call _failedBlock or _finishedBlock.
The __block statement is used for variables to stay in memory if they are used in a block. So you don't need it in the above code