title: Unexpected behaviour using Block-based callbacks with CBCentralManager / CBCentralManagerDelegate and [NSThread sleepForTimeInterval]
body: <p>I am trying to write some code to scan for bluetooth devices for a while, and then return the array of discovered peripherals through a block-based callback.
  Blocking code should not be an issue as the code will be called asynchronously.</p>
  
  <p>After reading up on the API documentation my initial plan of attack was to write an implementation for CBCentralManagerDelegate, use an init method to give it a block-based callback to call once the CBManagerState is PoweredOn, and then initialize this class with a callback that triggers the scanning and extracts the discovered Peripherals.</p>
  
  <p>The issue is... it doesn't work. Except when it does.
  Now I could work out a workaround to reach my goal, but for the sake of learning and understanding I am very interested in where exactly the issue originates from.</p>
  
  <pre><code>typedef void (^SomeBlock)(CBCentralManager*);
  
  
  @interface TEST : NSObject &lt;CBCentralManagerDelegate&gt;
  
  @property CBCentralManager* manager;
  @property SomeBlock onPoweredOn;
  @property NSMutableArray&lt;CBPeripheral*&gt;* peripherals;
  
  - (void) init: (SomeBlock) onPoweredOn;
  
  - (void) startScan;
  - (void) stopScan;
  
  @end
  
  
  @implementation TEST
  
  - (void) init: (SomeBlock) onPoweredOn {
      NSLog(@"%@", @"init");
      self.onPoweredOn = onPoweredOn;
      self.manager = [CBCentralManager alloc];
  
      dispatch_queue_attr_t attr = DISPATCH_QUEUE_CONCURRENT;
      dispatch_queue_t queue =dispatch_queue_create("BTManagerHandler", attr);
      self.manager = [self.manager initWithDelegate: self queue: queue];
  }
  
  - (void) startScan {
      NSLog(@"%@", @"startScan");
      [self.manager scanForPeripheralsWithServices: nil options: nil];
  }
  
  - (void) stopScan {
      NSLog(@"%@", @"stopScan ");
      [self.manager stopScan];
  }
  
  - (void) centralManagerDidUpdateState: (nonnull CBCentralManager *) manager {
      NSLog(@"%@", @"centralManagerDidUpdateState:");
  
      switch (manager.state) {
          case CBManagerStateUnknown:
              NSLog(@"%@", @"CBManagerStateUnknown:");
              break;
          case CBManagerStateResetting:
              NSLog(@"%@", @"CBManagerStateResetting:");
              break;
          case CBManagerStateUnsupported:
              NSLog(@"%@", @"CBManagerStateUnsupported:");
              break;
          case CBManagerStateUnauthorized:
              NSLog(@"%@", @"CBManagerStateUnauthorized:");
              break;
          case CBManagerStatePoweredOff:
              NSLog(@"%@", @"CBManagerStatePoweredOff:");
              break;
          case CBManagerStatePoweredOn:
              NSLog(@"%@", @"CBManagerStatePoweredOn:");
              if (self.onPoweredOn != nil) self.onPoweredOn(manager);
              break;
      }
  }
  
  - (void) centralManager: (nonnull CBCentralManager*) central didDiscoverPeripheral: (nonnull CBPeripheral*) peripheral advertisementData: (nonnull NSDictionary&lt;NSString*, id&gt;*) advertisementData RSSI: (nonnull NSNumber*) RSSI {
      NSLog(@"%@", @"centralManager:didDiscoverPeripheral:advertisementData:RSSI:");
  
      if (self.peripherals == nil) self.peripherals = [NSMutableArray array];
  
      for (CBPeripheral* _peripheral in self.peripherals) {
          if (peripheral.identifier == _peripheral.identifier) return;
      }
  
      [self.peripherals addObject: peripheral];
  }
  
  @end
  
  
  + (void) discoverDevices {
      TEST* test = nil;
      @try {
          test = [TEST alloc];
  
          SomeBlock onPoweredOn = ^(CBCentralManager* manager) {
              NSLog(@"%@", @"_onPoweredOn_");
              [test startScan];
              [NSThread sleepForTimeInterval: 10.0];
              [managerHandler stopScan];
  
              NSArray&lt;CBPeripheral*&gt;* discoveredPeripherals = managerHandler.peripherals;
              // do stuff with discoveredPeripherals
          };
          [test init: onPoweredOn];
      } @catch(NSException* e) {
          // exception handling
      } @finally {
          // cleanup
      }
  }
  </code></pre>
  
  <p>I would expect the above code to work, but it doesn't.
  The 'onPoweredOn' callback and the 'startScan' method are called correctly, but the 'centralManager:didDiscoverPeripheral:advertisementData:RSSI:' method is never called.</p>
  
  <p>After some trial and error I found that the following works:</p>
  
  <pre><code>+ (void) discoverDevices {
      TEST* test = nil;
      @try {
          test = [TEST alloc];
  
          SomeBlock onPoweredOn = ^(CBCentralManager* manager) {
              NSLog(@"%@", @"_onPoweredOn_");
              [test startScan];
          };
          [test init: onPoweredOn];
  
          [NSThread sleepForTimeInterval: 10.0];
          [managerHandler stopScan];
  
          NSArray&lt;CBPeripheral*&gt;* discoveredPeripherals = managerHandler.peripherals;
              // do stuff with discoveredPeripherals
      } @catch(NSException* e) {
          // exception handling
      } @finally {
          // cleanup
      }
  }
  </code></pre>
  
  <p>After some more trial and error I narrowed it down to one line of code:</p>
  
  <pre><code>+ (void) discoverDevices {
      TEST* test = nil;
      @try {
          test = [TEST alloc];
  
          SomeBlock onPoweredOn = ^(CBCentralManager* manager) {
              NSLog(@"%@", @"_onPoweredOn_");
              [test startScan];
              [NSThread sleepForTimeInterval: 10.0]; // &lt;&lt;=== this line! ===
          };
          [test init: onPoweredOn];
  
          [NSThread sleepForTimeInterval: 10.0];
          [managerHandler stopScan];
  
          NSArray&lt;CBPeripheral*&gt;* discoveredPeripherals = managerHandler.peripherals;
          // do stuff with discoveredPeripherals
      } @catch(NSException* e) {
          // exception handling
      } @finally {
          // cleanup
      }
  }
  </code></pre>
  
  <p>This suggests that that using a [NSThread sleepForTimeInterval:] blocks the discovery of bluetooth devices... but tat seems illogical to me because the same code works without the block-based callback:</p>
  
  <pre><code>+ (void) discoverDevices {
      TEST* test = nil;
      @try {
          test = [TEST alloc];
  
          [test init: nil];
  
          [NSThread sleepForTimeInterval: 1.0];
          [test startScan];
          [NSThread sleepForTimeInterval: 10.0];
          [managerHandler stopScan];
  
          NSArray&lt;CBPeripheral*&gt;* discoveredPeripherals = managerHandler.peripherals;
          // do stuff with discoveredPeripherals
      } @catch(NSException* e) {
          // exception handling
      } @finally {
          // cleanup
      }
  }
  </code></pre>
  
  <p>Conclusion: combining CBCentralManager, block-based callbacks and [NSThread sleepForTimeInterval:] leads to unexpected behaviour??
  but why? what's so special about this specific combination?</p>