Might be the last lion in the world to realize this or it might have been a change from SDK 8 to SDK 14, but the NRF52 softdevice has 4 buffers for transmitting to the phone. You have to fill all 4 buffers by calling sd_ble_gatts_hvx with separate ble_gatts_hvx_params_t structs to hit the maximum speed & only then start waiting for BLE_GATTS_EVT_HVN_TX_COMPLETE events to keep the buffers warm. min_conn_interval & max_conn_interval also have to be 0.
With the NRF_SDH_BLE_GATT_MAX_MTU_SIZE at 247, the notification size was 244 & it fired 154 times per second, giving 300kbits/sec. Not sure what the speed from IOS to the NRF is, but there was no evidence corebluetooth was firing nearly as fast. CBPeripheral.writeValue could be called 3 times between waits for didWriteValueFor:characteristic, but it wasn't nearly as fast.
CBPeripheral.writeValue is only critical for firmware updates, which are rare & don't have to be fast. The critical point is going from the device to the phone.