I'm scanning for Bluetooth devices on macOS 13.1 using IOBluetoothDeviceInquiry. The scanning itself works, but only the deviceInquiryDeviceFound delegate method gets called. For the remaining methods:
deviceInquiryStarted never fires, even though the inquiry obviously starts;
deviceInquiryComplete never fires, and indeed the inquiry never seems to end (the program keeps outputting Found peer... console logs);
When updateNewDeviceNames is set to true, the delegate methods related to updating device names are nonetheless never called.
Setting inquiryLength has no effect. The documentation states:
if you have the inquiry object updating device names for you, the
whole inquiry process could be much longer than the specified length...
If you -must- have a strict inquiry length, disable name updates.
But scanning continues indefinitely even when I set updateNewDeviceNames to false.
Here's my code:
import IOBluetooth
class Inquiry: NSObject {
private lazy var inquiry: IOBluetoothDeviceInquiry = {
let inquiry: IOBluetoothDeviceInquiry = IOBluetoothDeviceInquiry()
inquiry.delegate = self
inquiry.updateNewDeviceNames = false
return inquiry
}()
lazy var foundDevices: [Any]! = self.inquiry.foundDevices()
private var continuation: CheckedContinuation<Void, Never>?
func start() async -> () {
await withCheckedContinuation { continuation in
self.continuation = continuation
self.inquiry.start()
}
}
}
extension Inquiry: IOBluetoothDeviceInquiryDelegate {
func deviceInquiryStarted(_ sender: IOBluetoothDeviceInquiry!) {
print("inquiry started")
}
func deviceInquiryComplete(_ sender: IOBluetoothDeviceInquiry!, error: IOReturn, aborted: Bool) {
print("inquiry complete")
continuation?.resume()
}
func deviceInquiryDeviceFound(_ sender: IOBluetoothDeviceInquiry!, device: IOBluetoothDevice!) {
print("device found: \(device.addressString!)")
}
}
let inquiry: Inquiry = Inquiry()
await inquiry.start()
print(inquiry.foundDevices!)
As I'm writing a command line tool, I wrap the start method in a continuation. Other than that it's pretty much identical to other examples of IOBluetoothDeviceInquiry usage I've found (e.g. this one, which has the same problems when I try running it).
I'm really at a loss as to why this isn't working, any help would be greatly appreciated!
Related
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 3 years ago.
I’m using the FileProvider library to look for files on my FTP server and Swift 5’s basic functionality to look for files in the “Documents“ folder of the device:
func lookForFiles() { //runs on a background thread
var ftpExists = false
var localExists = false
let ftpFile:FileObject = lookForFTPFile()
let localFile:FileObject = lookForLocalFile()
//Compare,... files
}
func lookForFTPFile() -> FileObject? {
var found:FileObject?
ftpProvider?.contentsOfDirectory(path: mypath, completionHandler: { (contents, error) in
//Look for ftp file
}) //This is run in an async task according to documentation
return found
}
This of course always returns "nil" because of the task in "contentsOfDirectory" (I also can't return the file from within).
Question: How do I wait for lookForFTPFile to finish before returning the result (which might be nil because it simply didn't find anything) - without just setting up a timer?
I'd prefer to not mess with how the library sets up its asynchronous work.
Something like
var waitingbool = false
var found:FileObject?
func lookForFiles() { //runs on a background thread
//Rest of code
lookForFTPFile()
while !waitingbool {}
//Use "found"
}
func lookForFTPFile() {
ftpProvider?.contentsOfDirectory(path: mypath, completionHandler: { (contents, error) in
//Look for ftp file and save result in "found"
self.waitingbool = true
})
}
looks like might work but at the same time it seems to be breaking a lot of unwritten rules.
Everyone who hasn't done async in Swift runs into the same problem. If you return a value from a method without a closure (as you are doing it), it must return sync. Since your completion handler runs async as you have noticed, we have a problem. You shouldreturn a value from a async method with a completion handler block.
I would have rewritten your method as follows:
func find(content: #escaping (FileObject?) -> ()) {
var found: FileObject?
// init the found variabel somewhere
ftpProvider?.contentsOfDirectory(path: mypath, completionHandler: { (contents, error) in
// You are in a closure completion block here also!
// send a callback to our waiting function...
content(contents)
})
// If ftpProvider is nil, please call the content completion handler block with nil also!
}
Calling side:
find { contents in // Capture self as unowned/weak maybe?
// Use contents.
}
I have a viewMode that determines if iCloud is enabled or disabled with the result being a prompt to the User to login to iCloud or not.
Is there a way to progamatically login/logout to iCloud from an XCTest to reliable test all paths?
Here is my test
func testShowLoginButtonForiCloud() {
let viewModel = OnboardingViewModel()
let expectation = XCTestExpectation(description: "Wait for CKContainer auth check")
var iCloudEnabled: Bool?
viewModel.shouldShowiCloudLogin { result, error in
iCloudEnabled = result
expectation.fulfill()
}
wait(for: [expectation], timeout: 5.0)
XCTAssertNotNil(iCloudEnabled)
XCTAssertFalse(iCloudEnabled!)
}
Here is my ViewModel
typealias Completion = (Bool, Error?) -> Void
final class OnboardingViewModel {
func shouldShowiCloudLogin(completion: #escaping Completion) {
CKContainer.default().accountStatus { (status, error) in
switch status {
case .available :
completion(true, nil)
default :
completion(false, error)
}
}
}
}
Can we programmatically log in to CloudKit for unit testing? This is inadvisable, because even if we could, the tests would be slow & fragile. Instead, treat CloudKit as an architectural boundary. Unit tests can go right up to this boundary. And we can pretend stuff comes back from the boundary. In this way, we can test all paths.
To program this boundary into your code, use a protocol. This protocol will be a slice containing only those CKContainer methods you want. (This is the Interface Segregation Principle in action.) Since CKContainer already implements this method, we can attach it as an empty extension.
protocol CKContainerProtocol {
func accountStatus(completionHandler: #escaping (CKAccountStatus, Error?) -> Void)
}
extension CKContainer: CKContainerProtocol {}
Then add a property to your view model:
var cloudKitContainer: CKContainerProtocol = CKContainer.default()
The default value means your code will continue to use the real CKContainer unless told otherwise. Change your code to call cloudKitContainer instead of CKContainer.default().
Then in test code, you can provide a different implementation of CKContainerProtocol. This will let you do stubbing and mocking. You can confirm that accountStatus() is called exactly once. And you can exercise its closure with different CKAccountStatus values to confirm how your Completion closure is called.
I am working on testing an API through Alamofire. I need to make a single call to the server to prepare it for the integration test. Once that is done, I am ready to start running tests.
The usual override setUp() is run for every test, so I do not want to do that.
I have therefore chosen to override the class setUp() as described here: https://developer.apple.com/reference/xctest/xctestcase
That's all well and good, but now, I no longer can use the standard waitForExpectations. (In the class override setUp()) I get several compiler errors that tell me that I am no longer calling the same waitForExpectations because I am in a class method, not a test case.
To try to get around this, I wanted to use a semaphore like so:
class ServiceLayerTests: XCTestCase {
static var apiService: APIService = APIService()
let sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: ["http://localhost:3000/": .disableEvaluation]))
static var companyManger: UserWebResource?
static var companyManagerID = -1
override class func setUp() {
apiService.baseURL = "http://localhost:3000/"
beginIntegrationTests()
}
class func beginIntegrationTests() {
var semaphore = DispatchSemaphore(value: 0)
apiService.beginIntegrationTests(completion: {resource, error in
if let resource = resource {
if let manager = resource as? UserWebResource {
companyManger = manager
companyManagerID = manager.id
semaphore.signal()
}
}
})
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
}
}
This does not work. Under the hood, there is an alamo fire call to the server and it responds with the user to use for the integration tests. I do see the server spinning, so I know that the actual communication is happening, but I never get into the completion closure.
I suspect I am not understanding how Swift does semaphores and that I have created a deadlock somehow. If somebody has a better solution, I'd be more than happy to hear it.
I get several compiler errors that tell me that I am no longer calling
the same waitForExpectations because I am in a class method, not a
test case
That makes sense. What you probably want is to refactor so that you are in a test case:
override class func setUp() {
apiService.baseURL = "http://localhost:3000/"
}
func testIntegrationTests() {
let urlExpectation = expectation(description: "INTEGRATION TEST")
apiService.beginIntegrationTests(completion: {resource, error in
// ...
urlExpectation.fulfill()
})
// not sure what an acceptable timeout would be, I chose this at random
waitForExpectations(timeout: 25) { error in
if let error = error {
print("Error: \(error.localizedDescription)")
}
}
}
One of the best resources with some good test examples can be found here: http://nshipster.com/xctestcase/
You can create the expectation as a lazy var that executes your one-time set up and fulfills on completion.
At the beginning of your per-test setUp() function you can wait for that expectation.
None of your tests will run until it is fulfilled, and the initial setup will run only once.
class WaitForSetup_Tests: XCTestCase {
lazy var initialSetupFinished: XCTestExpectation = {
let initialSetupFinished = expectation(description: "initial setup finished")
initialSetupTask() { // your setup task that executes this closure on completion
initialSetupFinished.fulfill()
return
}
return initialSetupFinished
}()
override func setUp() {
wait(for: [initialSignOutFinished], timeout: 2.0)
// do your per-test setup here
}
}
Note: This solution avoids using the override class function setUp() class method, because I couldn't figure out how to use the expectations except for in an instance.
I've inherited some Swift 3 code which uses RxSwift to manage a store. The basic layout of the class is:
class UserActivityStore {
fileprivate lazy var internalDataCache: Variable<Set<NSUserActivity>?> = Variable(nil)
func addAction(_ actionToAdd: NSUserActivity) {
var content = internalDataCache.value ?? Set<NSUserActivity>()
content.insert(actionToAdd)
internalDataCache.value = content
}
func resolveAction(_ action: NSUserActivity) {
var content = internalDataCache.value
_ = content?.remove(action)
internalDataCache.value = content
}
func expectActions(_ filter: #escaping ((NSUserActivity) -> Bool)) -> Observable<NSUserActivity> {
let observable = Observable<NSUserActivity>.create { (observer) -> Disposable in
return self.internalDataCache.asObservable().subscribeNext { (newActions) in
guard let newActions = newActions else { return }
for eachAction in newActions where filter(eachAction) {
observer.onNext(eachAction)
self.resolveAction(eachAction)
}
}
}
return observable
}
}
When an action is added to this, it adds the item to the set correctly. However, the observer (in expectActions) catches that change and resolves it. Since this is all in a single thread, the error "Warning: Recursive call or synchronization error!" is triggered.
I think this is a perfectly legitimate error and that RxSwift is probably correct in its handling. However, I can't help thinking that this is a bad model. The code is essentially handling a queue of NSUserActivity objects itself.
Is this genuinely a modelling error / abuse of RxSwift or is my limited understanding of RxSwift misunderstanding this? As a hacky solution, I've tried replacing the resolveAction function with a single line internalDataCache.value?.remove(action) but that still triggers the observable and hence the bug.
Changing the observable to use a different queue (Serial or Concurrent dispatch) fixes the problem but I'm not convinced its the correct fix.
I'm currently trying to debug crashes in a JavascriptCore implementation of an interface for native code to perform some work on behalf of the javascript code in the WebView.
The crash sometimes occurs very soon after launching the application, other times it may take a few minutes of executing hundreds of calls to the native code for it to occur.
These are the top two lines for the backtrace of every crash:
(lldb) thread backtrace
* thread #1: tid = 0x37960c, 0x00007fff8de6ecca JavaScriptCore`sanitizeStackForVMImpl + 15, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x700001a2c000)
frame #0: 0x00007fff8de6ecca JavaScriptCore`sanitizeStackForVMImpl + 15
Here is a simplified version of my view controller:
class MyViewController: NSViewController, WebFrameLoadDelegate {
let worker = Worker()
// other setup code...
func webView(webView: WebView!, didCreateJavaScriptContext context: JSContext!, forFrame frame: WebFrame!) {
context.setObject(worker, forKeyedSubscript: "ClientWorker")
}
}
The JSExport protocol itself, and implementation of the code performing work. For testing I removed the actual work and just return a dictionary with dummy data, and the crash still occurs.
#objc protocol WorkerJSExports: JSExport {
func doWork(params: [String:AnyObject], callback: JSValue)
}
#objc class Worker: NSObject, WorkerJSExports {
func doWork(params: [String:AnyObject], callback: JSValue) {
executeBackground(callback) {
return [
"test": "data"
]
}
}
private func executeBackground(callback: JSValue!, f: ()->([String:AnyObject])) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
let result = f()
dispatch_sync(dispatch_get_main_queue()) {
self.executeCallback(callback, result: result)
}
}
}
private func executeCallback(callback: JSValue!, result: AnyObject) {
callback.context.evaluateScript("setTimeout").callWithArguments([callback, 0, result])
}
}
The executeBackground and executeCallback methods are helper functions, and executeCallback is making use of setTimeout in response to what I read in this SO post/answer: JavaScriptCore -- Passing a function as a parameter to ObjC and it seems to have resolved other crashes related to locking.
When I swap out executeBackground for the following function that runs just on the main thread, I have not been able to replicate the crash:
private func executeMain(callback: JSValue!, f: ()->([String:AnyObject])) {
dispatch_async(dispatch_get_main_queue()) {
self.executeCallback(callback, result: f())
}
}
It seems that there is some sort of issue that occurs when passing data created in a background thread into the WebView, but after pouring through the documentation I'm uncertain that what could be. The only taboo I found mentioned was passing data between multiple JSVirtualMachine instances, which doesn't seem applicable since I'm only interacting with a single WebView instance. Any assistance in figuring this out is greatly appreciated!
Update
I seem to have solved the issue by switching out the use of Grand Central Dispatch directly for NSOperationQueues. After changing executeBackground to the following, the crashes have not recurred.
private let workerQueue = NSOperationQueue()
private func executeAsync(callback: JSValue!, f: ()->([String:AnyObject])) {
self.workerQueue.addOperationWithBlock({
let result = f()
NSOperationQueue.mainQueue().addOperationWithBlock({
self.executeCallback(callback, result: result)
})
})
}
Though I can't really prove that this has fixed the crash, we've done fairly extensive testing of this functionality and haven't seen it again. The reason I didn't post this an an answer to my own question is that I'm at a loss as to why exactly this is different and/or better. If anyone has insight into what the change to NSOperationQueues may have solved, it would be very much appreciated!