无夜之星辰
无夜之星辰

Reputation: 6158

How to return current method in GCD block?

Here is my code:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self testGCD];
}

- (void)testGCD {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"1");
        return;
    });
    NSLog(@"2");
}

The console printed 1 and 2.

What I want is only to print 1 the first time. I think maybe the return is not returning from the method, but instead from the block.

Is there any way I can return from the current method in this GCD block?

Upvotes: 2

Views: 92

Answers (5)

Inder Kumar Rathore
Inder Kumar Rathore

Reputation: 39978

If you see the definition of dispatch_once then you gonna see that they are using DISPATCH_EXPECT to compare the onceToken. You can also use if (onceToken != -1) but DISPATCH_EXPECT optimises the code by telling the compiler that the probability of onceToken == -1 is much much higher. This is called Branch Prediction

- (void)testGCD {
  static dispatch_once_t onceToken;
  if (DISPATCH_EXPECT(onceToken, ~0l) != ~0l) {
    dispatch_once(&onceToken, ^{
      NSLog(@"1");
      return;
    });
  }
  else {
    NSLog(@"2");
  }
}

Upvotes: 2

LGP
LGP

Reputation: 4333

Use a block to get around the problem.

- (void)testGCD {
    __block void(^codeBlock)(void) = ^{ NSLog(@"2"); };
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        codeBlock = ^{ NSLog(@"1"); };
    });
    codeBlock();
}

Upvotes: 3

Cy-4AH
Cy-4AH

Reputation: 4585

- (void)testGCD {
    static dispatch_once_t onceToken;
    __block NSString *text = @"2";
    dispatch_once(&onceToken, ^{
        text = @"1";
    });
    NSLog(@"%@", text);
}

Upvotes: 2

Matic Oblak
Matic Oblak

Reputation: 16774

Block is like a completely separate method. A return in it will only return from the block. What you effectively seem to need is:

- (void)testGCD {
    static BOOL didTrigger = NO;

    if(didTrigger) {
        NSLog(@"2");
    }
    else {
        didTrigger = YES;
        NSLog(@"1");
    }
}

You might try to have a simple lock in your case but I am not sure how safe it would be in this case:

- (void)testGCD {
    static dispatch_once_t onceToken;
    static BOOL didInvokeOnceBlock = NO;
    static BOOL didPassSkippedBlock = NO;
    dispatch_once(&onceToken, ^{
        NSLog(@"1");
        didInvokeOnceBlock = YES;
    });
    if(didInvokeOnceBlock && didPassSkippedBlock) {
        NSLog(@"2");
    }
    didPassSkippedBlock = YES;
}

But this still looks like it may have unstable results when multithreading. You might need to run this atomically for the result to be correct. What I see as potential issue is:

  • Thread A and thread B start executing the same method.
  • Thread A collects token and unlocks didInvokeOnceBlock
  • Thread B skips block and unlocks didPassSkippedBlock but skips NSLog(@"2");
  • Thread A invokes NSLog(@"2");

Upvotes: 0

Paulw11
Paulw11

Reputation: 114826

The return statement returns from the current enclosing scope, which in your case is the block. You cannot return from the outer enclosing scope.

You can use a simple boolean flag to determine if this is the first time that the code has been executing, with a serial dispatch queue to ensure that it is thread-safe.

Something like:

- (void)testGCD {
    static dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
    static bool firstRun = YES;

    dispatch_sync(queue, ^{
       if (firstRun) {
           NSLog(@"1);
           firstRun = NO;
       } else {
           NSLog(@"2");
       }
    });
}

By using a serial dispatch queue you ensure that there cannot be concurrent update of firstRun

Upvotes: 0

Related Questions