[self performSelector:#selector(stopPulling) withObject:nil afterDelay:0.01];
The code is fine. I just think that using NSOperation and block should be the way to go for the future.
I am familiar with NSOperation. I just want to do the same thing with block and NSOperation.
I can do this with GCD already:
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
<#code to be executed on the main queue after delay#>
});
C'mon. There is something that can be done in GCD that can't be done more easily in NSOperation?
NSOperationQueue does not provide a mechanism for delayed execution. Use GCD or NSTimer.
I ended up making this:
#import "BGPerformDelayedBlock.h"
#implementation BGPerformDelayedBlock
+ (void)performDelayedBlock:(void (^)(void))block afterDelay:(NSTimeInterval)delay
{
int64_t delta = (int64_t)(1.0e9 * delay);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delta), dispatch_get_main_queue(), block);
}
+(void)performSlightlyDelayedBlock:(void (^)(void))block
{
[self performDelayedBlock:block afterDelay:.1];
}
#end
It's based on an answer in How do you trigger a block after a delay, like -performSelector:withObject:afterDelay:?
I think it shouldn't be a category.
Strange that I ended up using GCD.
However, using it is simple. I just do:
[BGPerformDelayedBlock performSlightlyDelayedBlock:^{
[UIView animateWithDuration:.3 animations:^{
[self snapToTheTopOfTheNonHeaderView];
}];
}];
Your code is similar to, using a NSTimer setting a selector after 0.01sec with no repeats. This will be called on the main thread.
NSOperation or blocks are used to perform operations in background. These you can use instead of performSelectorInBackground.
If your need is to work in background then go for it. There are many tutorials available to learn 'NSOperationusing 'NSOperationQueue and blocks.
Related
I am developing an app that contacts a RESTful server to get some data and then with the returned JSON response to display that data.
Using UniRest calls and all is working well. The main call is 'runUnirestRequest'
The uni rest call is an async GCD dispatch call. My problem is that because I am testing locally the call is so quick I can't see the activity indicator rolling. It simply disappears before I can see it.
The GCD block occur within the viewController viewDidLoad call.
What I need to achieve: Have the async unirest call take several seconds to simulate a server response that is slow (Dont want to actually stop the iOS app in its tracks).
Please excuse any coding errors/bad habits, only been doing objective c for a week but am happy for any additional constructive crit. :)
I have tried
sleep(5); // But bad idea as far as I can see.
Also tried
[NSThread sleepForTimeInterval:5.0]; // but this doesn't seem to do anything.
viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
[self createActivityIndicator];
NSLog(#"viewDidLoad");
NSLog(#"viewDidLoad->thread: %#", [NSThread currentThread]);
[messageLabel setText:#""];
unirestQueue = dispatch_queue_create("com.simpleweb.pbs.dayDataUnirestRequest", NULL);
// Do any additional setup after loading the view from its nib.
daySalesFigures = [[PBSDaySales alloc] init];
responseVal = [[HttpJsonResponse alloc] init];
// Use Grand Central Dispatch to run async task to server
dispatch_async(unirestQueue, ^{
[self runUnirestRequest:self.requestUrl];
});
dispatch_after(unirestQueue, dispatch_get_main_queue(), ^(void){
[activityIndicator stopAnimating];
});
}
runUniRestRequest function
- (void) runUnirestRequest:(NSString*)urlToGet
{
[NSThread sleepForTimeInterval:5.0];
NSLog(#"runUnirestRequest called");
HttpJsonResponse* response = [[Unirest get:^(SimpleRequest* request) {
[request setUrl:#"http://x.x.x.x:9000/Sales/Day/2013-02-14"];
}] asString];
NSString *jsonStr = [response body];
SBJsonParser *jsonParser = [SBJsonParser new];
id response2 = [jsonParser objectWithString:jsonStr];
[self deserializeJsonPacket:(NSDictionary*)response2];
}
dispatch_after's first parameter is time. You are passing in unirestQueue, which is dispatch_queue_t queue according to
unirestQueue = dispatch_queue_create("com.simpleweb.pbs.dayDataUnirestRequest", NULL);
proper code for dispatch_after, i.e. performing block after some delay, is like this:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Do whatever you want
});
Edit: Oh, I probably see what you are trying to accomplish :-) You thought the dispatch_after means "do something after this queue" right? Nope, it's "do something after some time"
Edit 2: You can use code like below to do something time consuming in background and update UI when its done
// Start block on background queue so the main thread is not frozen
// which prevents apps UI freeze
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do something taking a long time in background
// Here we just freeze current (background) thread for 5s
[NSThread sleepForTimeInterval:5.0];
// Everything in background thread is done
// Call another block on main thread to do UI stuff
dispatch_async(dispatch_get_main_queue(), ^{
// Here you are in the main thread again
// You can do whatever you want
// This example just stops UIActivityIndicatorView
[activityIndicator stopAnimating];
});
});
Edit 3: I recommend this great article about GCD at raywenderlich.com for more detailed info
I need to add a delay between the execution of two lines in a(same) function. Is there any favorable option to do this?
Note: I don't need two different functions to do this, and the delay must not affect other functions' execution.
eg:
line 1: [executing first operation];
line 2: Delay /* I need to introduce delay here */
line 3: [executing second operation];
You can use gcd to do this without having to create another method
// ObjC
NSTimeInterval delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"Do some work");
});
// Swift
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("Do some work)
}
You should still ask yourself "do I really need to add a delay" as it can often complicate code and cause race conditions
You can use the NSThread method:
[NSThread sleepForTimeInterval: delay];
However, if you do this on the main thread you'll block the app, so only do this on a background thread.
or in Swift
NSThread.sleepForTimeInterval(delay)
in Swift 3
Thread.sleep(forTimeInterval: delay)
This line calls the selector secondMethod after 3 seconds:
[self performSelector:#selector(secondMethod) withObject:nil afterDelay:3.0 ];
Use it on your second operation with your desired delay. If you have a lot of code, place it in its own method and call that method with performSelector:. It wont block the UI like sleep
Edit: If you do not want a second method you could add a category to be able to use blocks with performSelector:
#implementation NSObject (PerformBlockAfterDelay)
- (void)performBlock:(void (^)(void))block
afterDelay:(NSTimeInterval)delay
{
block = [block copy];
[self performSelector:#selector(fireBlockAfterDelay:)
withObject:block
afterDelay:delay];
}
- (void)fireBlockAfterDelay:(void (^)(void))block
{
block();
}
#end
Or perhaps even cleaner:
void RunBlockAfterDelay(NSTimeInterval delay, void (^block)(void))
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*delay),
dispatch_get_current_queue(), block);
}
I have a couple of turn-based games where I need the AI to pause before taking its turn (and between steps in its turn). I'm sure there are other, more useful, situations where a delay is the best solution. In Swift:
let delay = 2.0 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) { self.playerTapped(aiPlayView) }
I just came back here to see if the Objective-C calls were different.(I need to add this to that one, too.)
[checked 27 Nov 2020 and confirmed to be still accurate with Xcode 12.1]
The most convenient way these days: Xcode provides a code snippet to do this where you just have to enter the delay value and the code you wish to run after the delay.
click on the + button at the top right of Xcode.
search for after
It will return only 1 search result, which is the desired snippet (see screenshot). Double click it and you're good to go.
If you're targeting iOS 4.0+, you can do the following:
[executing first operation];
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[executing second operation];
});
Like #Sunkas wrote, performSelector:withObject:afterDelay: is the pendant to the dispatch_after just that it is shorter and you have the normal objective-c syntax. If you need to pass arguments to the block you want to delay, you can just pass them through the parameter withObject and you will receive it in the selector you call:
[self performSelector:#selector(testStringMethod:)
withObject:#"Test Test"
afterDelay:0.5];
- (void)testStringMethod:(NSString *)string{
NSLog(#"string >>> %#", string);
}
If you still want to choose yourself if you execute it on the main thread or on the current thread, there are specific methods which allow you to specify this. Apples Documentation tells this:
If you want the message to be dequeued when the run loop is in a mode
other than the default mode, use the
performSelector:withObject:afterDelay:inModes: method instead. If you
are not sure whether the current thread is the main thread, you can
use the performSelectorOnMainThread:withObject:waitUntilDone: or
performSelectorOnMainThread:withObject:waitUntilDone:modes: method to
guarantee that your selector executes on the main thread. To cancel a
queued message, use the cancelPreviousPerformRequestsWithTarget: or
cancelPreviousPerformRequestsWithTarget:selector:object: method.
Many, if not most, web services have a rate limit for clients. Delicious says a client can make one request per second; Twitter has limits per end-point; I'm sure Facebook and Flickr and Foursquare have their own idea.
You can easily limit an iOS application to a single request at a time using an NSOperationQueue.
But how do you limit an application to making, say, only one request per second?
I've looked at the sample code by Apple, AFNetworking, ASINetwork and a few others, and none seem to solve this problem. This seems odd to me. I'll concede that I could be missing something very obvious...
Some parameters:
Assume I have an NSOperationQueue for network operations and the request is an NSOperation (could also be a GCD queue I suppose, but this is what I've mostly been working with)
The same rate limit is used for each request in the queue
I'm looking for a solution in iOS, but general ideas might be useful
Possible solutions:
sleep statement in the NSOperation (it's a queue/thread so this wouldn't block anything else)
NSTimer in the NSOperation
performSelector: in the NSOperation (I patched ASINetworking to use this approach, though I'm not using it and didn't push the change upstream)
Start/stop the queue (using KVO?) to make sure the rate limit is not exceeded
Special "sleep" NSOperation. This would be a task that the next network operation would be dependent upon
Completely ignore the rate limit and just pause a bit when you get the "exceeded rate limit" error response
These all seem quite messy. Operations that sleep would likely prevent forms of "priority" queue. Starting/stopping the queue seems fragile. Ignoring the limit is rude.
To be clear, I have solved this problem. But the solution seems "messy" and somewhat fragile. I'd like to know if there's a better, cleaner option.
Ideas?
#implementation SomeNSOperationSubClass {
BOOL complete;
BOOL stopRunLoop;
NSThread *myThread;
}
-(void) rateLimitMonitor:(NSTimer *)theTimer {
[theTimer invalidate];
}
-(void) main {
myThread = [NSThread currentThread];
NSTimer *myTimer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(rateLimitMonitor:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSDefaultRunLoopMode];
[self doAsyncThing];
while ((!stopRunLoop || [myTimer isValid]) && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
complete = YES;
}
-(void) internalComplete {
stopRunLoop = YES;
}
-(void) setComplete {
[self performSelector:#selector(internalComplete) onThread:myThread withObject:nil waitUntilDone:NO];
}
-(BOOL) isFinished {
return complete;
}
#end
and in your async callback
[myNSOperationSubClass setComplete];
New in iOS 13, this functionality is built in. Pass your communication trigger through the Combine framework's debounce operator and you're all set.
This is a fix for the almost working solution provided by Edwin
- (void)main {
for (double delay = 0.0; delay < 10.0; delay+=1) {
[self networkCallWithDelay:delay];
}
}
- (void)networkCallWithDelay:(double)delay {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Your asynchronous network call goes here
});
}
Possible solution, assuming you've already implemented your network module using other technologies other than NSOperation.
Use GCD to solve this problem. The following code introduces a 1 second delay to each network call. Pay close attention to the parameter for popTime
- (void)main {
for (NSInteger index = 0; index < 10; index++) {
[self networkCallWithDelay:1*index];
}
}
// Your network code goes here
- (void)networkCallWithDelay:(double)delay {
double delayInSeconds = delay / 10.0f;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Your asynchronous network call goes here
});
}
I'm working on an iOS app with Cocos2D and I'm running into a lot of situations where I want to do something with a slight delay so I use a line of code like this:
[self scheduleOnce:#selector(do_something) delay:10];
The stuff that happens in do_something is only one line of code though.
Is there a way for me to define the function right in that line where I schedule it?
When I used to program with jQuery this is similar in what I'm trying to achieve:
$("a").click(function() {
alert("Hello world!");
});
See how function() is defined right there? Is there a way to do this in Objective-C?
Also, is there a name for this? For future searches? Because I find this hard to explain.
You can use dispatch_after to execute a block after a certain amount of time.
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
/* code to be executed on the main queue after delay */
});
I would refer to it as a time dispatched block.
EDIT: How to dispatch it only once.
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
/* code to be executed once */
});
So in your case:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
/* code to be executed on the main queue after delay */
})
});
Since you're using Cocos2D you can also take advantage of the CCDelayTime method and combine it in a CCSequence to achieve your desired effect. Something along the lines of:
id delayAction = [CCDelayTime actionWithDuration:10];
id callSelector = [CCCallFunc actionWithTarget: self selector: #selector(do_something)];
[self runAction:[CCSequence actionOne:delayAction two:callSelector]];
Or you can also use the CCCallBlock so you don't have to write a separate method to do_something, just put it in a block.
[self runAction:[CCSequence actionOne:delayAction two:[CCCallBlock actionWithBlock:^{
// do something here
}]]];
I guess you need to declare the method "do_something" as
-(void)do_something {
//Your implementation here
}
In this case you can add as many line as possible for do_something method.
#selector(do_something) is a command to execute a method in your class.
Disclaimer: This question is meant to be purely theoretical, so please don't ask me why I'm doing this.
If I have the following code:
- (void) beginCatastrophe {
double delayInSeconds = 3.5;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_global_queue(0, 0), ^(void){
Class cls = [self class];
IMP replacement = class_getMethodImplementation(cls, #selector(fooReplacement:));
Method fooMethod = class_getInstanceMethod(cls, #selector(foo:));
method_setImplementation(fooMethod, replacement);
});
[self foo:1];
}
- (void) fooReplacement:(unsigned) x {}
- (void) foo:(unsigned) x {
[self foo:++x];
}
And somewhere else in my code, I call -beginCatastrophe
This results in a "too much recursion" error. Why?
I have confirmed that the swizzling code works after 2 seconds, but not any more than
that.
However, if I do something like this:
- (void) beginCatastrophe {
double delayInSeconds = 3.5;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_global_queue(0, 0), ^(void){
Class cls = [self class];
IMP replacement = class_getMethodImplementation(cls, #selector(fooReplacement:));
Method fooMethod = class_getInstanceMethod(cls, #selector(foo:));
method_setImplementation(fooMethod, replacement);
});
[self foo:nil];
}
- (void) fooReplacement:(id) x {
printf("%s", _cmd);
}
- (void) foo:(id) x {
[self performSelector:_cmd withObject:x afterDelay:0.00001];
}
This, of course works fine no matter how long I make the delayInSeconds.
This is only a guess, but I would guess that your stack is being exhausted well before that background task fires. You have it set to fire 3.5 seconds from now, then you continue on and recursively call foo. 3.5 seconds will put a ton of frames on the stack and will exhaust it before the method is swizzled.
If it's not this, then perhaps it is an issue with how this dispatch works with your runloop. You never do exit that beginCatastrophe method so the runloop never gets a chance to turn once you call it. Perhaps the swizzling thread never gets called? If you put a log statement in fooReplacement: does it get called?