Compiler errors in UIKit based framework due to Swift 5.5 concurrency changes - concurrency

I noticed that since Swift 5.5 some UI based classes changed their behavior in how they handle concurrency. I have a specific framework that encapsulates some UIKit features/logic and I am able to build the framework without any warnings, but when I want to use the framework in my app some compiler warnings show up in 'arm64-apple-ios' file. Currently the compiler errors occur in some initializers which have a closure as parameter.
Here is a small excerpt:
#_Concurrency.MainActor(unsafe) public init(configurationForConnectingSceneSession: #escaping (UIKit.UIApplication, UIKit.UISceneSession, UIKit.UIScene.ConnectionOptions) -> UIKit.UISceneConfiguration = { _, connectingSceneSession, _ in
UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
/* COMPILER ERROR 1 -> Call to main actor-isolated initializer 'init(name:sessionRole:)' in a synchronous nonisolated context, COMPILER ERROR 2 -> Property 'role' isolated to global actor 'MainActor' can not be referenced from a non-isolated synchronous context*/
}, didDiscardSceneSessions: #escaping (UIKit.UIApplication, Swift.Set<UIKit.UISceneSession>) -> Oodin_Base.Executable = { _,_ in NoAction() })
Any ideas how to solve that issue?

I was able to solve the compile errors by adding the '#MainActor' to my closure.
public init(
configurationForConnectingSceneSession: #MainActor #escaping (UIApplication, UISceneSession, UIScene.ConnectionOptions) -> UISceneConfiguration = { _, connectingSceneSession, _ in
UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
},
didDiscardSceneSessions: #escaping (UIApplication, Set<UISceneSession>) -> Executable = { _,_ in NoAction() }
)

Related

How to fetch Coredata List in IntentHandler class (WidgetKit-SwiftUI)?

I am not able to fetch data in my IntentHandler class. My Goal is select Todo from the CoreData list and display it in Widget.
I am trying to display a list from CoreData in Widget Intent and I am expecting to resolve this issue.
extension IntentHandler : ConfigurationIntentHandling {
func provideTodoNameOptionsCollection(for intent: ConfigurationIntent, searchTerm: String?, with completion: #escaping (INObjectCollection<TodoData>?, Error?) -> Void) {
var arrTodoData = [TodoData]()
coreDH.getAllTodos().forEach { todos in
let todoIntent = TodoData(identifier: todos.id?.uuidString, display: todos.name ?? "")
arrTodoData.append(todoIntent)
}
let collection = INObjectCollection(items: arrTodoData)
completion(collection, nil)
}
}
class IntentHandler: INExtension{
let coreDH = CoreDataHandler.shared
override func handler(for intent: INIntent) -> Any {
// This is the default implementation. If you want different objects to handle different intents,
// you can override this and return the handler you want for that particular intent.
return self
}
}
If your IntentHandler doesn't get called:
Ensure your Widget uses IntentConfiguration not StaticConfiguration
Ensure your time line provider conforms to IntentTimelineProvider
Run the app scheme, then run the intent scheme, you should be able to debug and breakpoints would work.
Widgets Code-along, part 3: Advancing timelines (5:31)
Add Configuration and Intelligence to Your Widgets
If you need to share data between your app and extension:
App's data is sandboxed and is not accessible by extension
Configure App Groups and you could create core data file in the shared container to be able to access in your extension
If App and Extension use exactly the same data you could use the same sqlite file in the shared container (both app and extension would have access to it)
If App and Extension use different data and there is only a small portion that is common, then use History Tracking
Configuring App Groups
Consuming Relevant Store Changes

Swift Delegation: unexpectedly found nil while unwrapping an Optional value

Edit: Please note the question below discusses using delegation between
2 viewcontrollers that are also implemented in a UITabBarController.
I've done a fair bit of searching here and on YouTube, but haven't seen my issue replicated elsewhere. I'll keep it to the point.
I have 2 view controllers that I coded myself -not generated by XCode-; TabOneController, and TabTwoController
below are the coding for both...
import UIKit
class TabOneController: UIViewController{
private let instanceOfTabOneView = TabOneView()
var vc1Delegate: fromOneToTwo!
override func loadView() {
super.loadView()
view.addSubview(instanceOfTabOneView.buildTheVu())
view.backgroundColor = UIColor.white
runThisOnce()
}
func runThisOnce(){
vc1Delegate.passTheValue(heroNameIs: "pass this to TabTwoController")
}
}
protocol fromOneToTwo{
func passTheValue(heroNameIs: String)
}
as for tab 2...
import UIKit
class TabTwoController: UIViewController, fromOneToTwo{
private let instanceOfTabTwoView = TabTwoView()
override func loadView() {
super.loadView()
view.addSubview(instanceOfTabTwoView.buildTheVu())
assignDelegateToSelf()
}
func assignDelegateToSelf(){
let instanceTabOne = TabOneController()
instanceTabOne.vc1Delegate = self
}
func passTheValue(heroNameIs:String){
instanceOfTabTwoView.txtFld.text = heroNameIs
}
}
I'm getting the following error at runtime -the app builds successfully-...
fatal error: unexpectedly found nil while unwrapping an Optional value
on the following line...
vc1Delegate.passTheValue(heroNameIs: "pass this to TabTwoController")
When I comment out the above line, the app builds and runs successfully, but of course the app doesn't execute the delegation.
I kinda understand what the compiler is trying to tell me, that the
vc1Delegate
hasn't been instantiated -I guess-. But I searched under every rock, and can't seem to find how to get around this.
I'd appreciate any help or guidance. Sorry if my code seems immature, I'm new to Swift and programming in general. Thank you.
In a UITabBarController, the first tab is instantiating by default. The view controller initialization executes the loadView and finds a nil because the second tab did not initialize yet; this is normal behavior. My suggestion is making the delegate weak optional with the ? suffix and run the delegate code elsewhere. Also, always capitalize the first letter in class and protocol names.
weak var vc1Delegate: FromOneToTwo?
If this structure is mandatory, try with a custom notification observer instead.
First thing first, your error happens in line var vc1Delegate: fromOneToTwo! while you declared this delegate variable as not null but then calling passTheValue on it. A correct practice will be
var vc1Delegate: fromOneToTwo?
func runThisOnce(){
if let delegate = vc1Delegate {
vc1Delegate.passTheValue(heroNameIs: "pass this to TabTwoController")
}
}
Secondly, you are not using delegate correctly. In the assignDelegateToSelf() function, you are creating a new instance of TabOneController
and then assign delegate. Instead, you need to find out the existing TabOneController instance and assign delegate.
I try this and worked add delegate = self in table cellforRowAt
like this
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "ChapterPracticeTableViewCell") as! ChapterPracticeTableViewCell
cell.deligate = self
return cell
}

Alamofire.request does not map with custom object

I was trying to create a sample app in swift that maps with alamofire with alamofire object mapper. The whole idea of this sample app is to map json response with custom model that I make. But when I try to loop the object list, it says that custom object is not compatible and its a dictionary.
Here is my network layer code
// MARK: retrieve country list
func retrieveCountryList(completion: #escaping (_ result: NSArray) -> Void) {
Alamofire.request("https://restcountries.eu/rest/v2/all").
responseJSON{(response: DataResponse<Any>) in
let result = response.result.value;
let resultsArray = result as! NSArray
completion(resultsArray)
}
}
and model
class Country : Mappable {
var name:String?
var capital:String?
required init(map : Map) {
}
func mapping(map: Map) {
name <- map["name"]
capital <- map["capital"]
}
}
It seems that the response is doesn't directly maps with model. what might be the issue ?
alamofire version 4.3
You first need to install "AlamofireObjectMapper" .
For which you should add into your project by including "pod 'AlamofireObjectMapper'" followed by command "pod install" .
Now in your API call class "import AlamofireObjectMapper" and try typing ".responseObject" you'll get it.

I'm trying to import either GoogleAPIClient or GoogleAPIClientForREST

I'm trying to follow Google's tutorial on making their QuickStart app to learn how to make API calls with Swift. I followed the tutorial completely and ended up with this code
import GoogleAPIClient
import GTMOAuth2
import UIKit
class ViewController: UIViewController {
private let kKeychainItemName = "Drive API"
private let kClientID = "592019061169-nmjle7sfv8i8eahplae3cvto2rsj4gev.apps.googleusercontent.com"
// If modifying these scopes, delete your previously saved credentials by
// resetting the iOS simulator or uninstall the app.
private let scopes = [kGTLAuthScopeDriveMetadataReadonly]
private let service = GTLServiceDrive()
let output = UITextView()
// When the view loads, create necessary subviews
// and initialize the Drive API service
override func viewDidLoad() {
super.viewDidLoad()
output.frame = view.bounds
output.editable = false
output.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 20, right: 0)
output.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
view.addSubview(output);
if let auth = GTMOAuth2ViewControllerTouch.authForGoogleFromKeychainForName(
kKeychainItemName,
clientID: kClientID,
clientSecret: nil) {
service.authorizer = auth
}
}
// When the view appears, ensure that the Drive API service is authorized
// and perform API calls
override func viewDidAppear(animated: Bool) {
if let authorizer = service.authorizer,
let canAuth = authorizer.canAuthorize, canAuth {
fetchFiles()
} else {
presentViewController(
createAuthController(),
animated: true,
completion: nil
)
}
}
// Construct a query to get names and IDs of 10 files using the Google Drive API
func fetchFiles() {
output.text = "Getting files..."
let query = GTLQueryDrive.queryForFilesList()
query.pageSize = 10
query.fields = "nextPageToken, files(id, name)"
service.executeQuery(
query,
delegate: self,
didFinishSelector: "displayResultWithTicket:finishedWithObject:error:"
)
}
// Parse results and display
func displayResultWithTicket(ticket : GTLServiceTicket,
finishedWithObject response : GTLDriveFileList,
error : NSError?) {
if let error = error {
showAlert("Error", message: error.localizedDescription)
return
}
var filesString = ""
if let files = response.files(), !files.isEmpty {
filesString += "Files:\n"
for file in files as! [GTLDriveFile] {
filesString += "\(file.name) (\(file.identifier))\n"
}
} else {
filesString = "No files found."
}
output.text = filesString
}
// Creates the auth controller for authorizing access to Drive API
private func createAuthController() -> GTMOAuth2ViewControllerTouch {
let scopeString = scopes.joinWithSeparator(" ")
return GTMOAuth2ViewControllerTouch(
scope: scopeString,
clientID: kClientID,
clientSecret: nil,
keychainItemName: kKeychainItemName,
delegate: self,
finishedSelector: "viewController:finishedWithAuth:error:"
)
}
// Handle completion of the authorization process, and update the Drive API
// with the new credentials.
func viewController(vc : UIViewController,
finishedWithAuth authResult : GTMOAuth2Authentication, error : NSError?) {
if let error = error {
service.authorizer = nil
showAlert("Authentication Error", message: error.localizedDescription)
return
}
service.authorizer = authResult
dismissViewControllerAnimated(true, completion: nil)
}
// Helper for showing an alert
func showAlert(title : String, message: String) {
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: UIAlertControllerStyle.Alert
)
let ok = UIAlertAction(
title: "OK",
style: UIAlertActionStyle.Default,
handler: nil
)
alert.addAction(ok)
presentViewController(alert, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
My problem is that for
import GoogleAPIClient
I get the error "No such module GoogleAPIClient", which seems weird to me since GTMOAuth2 doesn't get an error, even though it's part of the same Pod I think (I'm new to this, so I'm probably butchering the terminology).
From researching the problem, I found that GoogleAPIClientForREST should be substituted for GoogleAPIClient. This document on GitHub says to just use GoogleAPIClientForREST in the code instead of GoogleAPIClient, but I get the same error with that as well.
Then I thought maybe I could re-install the pods with some changes to Google's tutorial. In the tutorial, it says to execute this code in Terminal
$ cat << EOF > Podfile &&
> platform :ios, '7.0'
> use_frameworks!
> target 'QuickstartApp' do
> pod 'GoogleAPIClient/Drive', '~> 1.0.2'
> pod 'GTMOAuth2', '~> 1.1.0'
> end
> EOF
> pod install &&
> open QuickstartApp.xcworkspace
So I thought maybe I could replace GoogleAPIClient for GoogleAPIClientForREST in the terminal code, but that landed me with the same error
As you can see in the screenshot, the framework is there on the left-hand side, but I'm still getting the "No such module" error.
Embedded Binaries and Linked Frameworks
Search Paths
I also found some suggestions here that I tried to follow, but I didn't completely understand the explanation. Nevertheless, I tried, and did this (if I did it wrong please tell me):
So I'm trying to get either GoogleAPIClient or GoogleAPIClientForREST to work. Thank you for your help
Use this for your Podfile:
platform :ios, '7.0'
use_frameworks!
target 'QuickstartApp' do
pod 'GoogleAPIClientForREST/Drive', '~> 1.1.1'
pod 'GTMOAuth2', '~> 1.1.0'
end
Change your import to
import GoogleAPIClientForREST
Then follow the instructions here to migrate the project:
Migrating from GoogleAPIClient to GoogleAPIClientForREST
This mostly involves changing GTL calls to GTLR calls with some word swapping. For example, GTLServiceDrive becomes GTLRDriveService.
Regarding framework search paths, this image shows the section you might need to change (note it works for me using the default):
Search paths can be per target, too. Here's an image showing the application target and the framework search paths:
So I followed the Quickstart tutorial exactly as well and was able to get it working. I moved the GoogleAPIClientForRest in Framework Search Paths above GTMOAuth2:
Screenshot
I ran into an error in the code after successfully including the module and had to change this line to get it to build and run:
if (result.files!.count to if (result.files!.count > 0).
Of course now, Google has deprecated GTMOAuth2 and replaced it with GTMAppAuth, which renders this app useless.
Although the solution towards which I am pointing you might be for other library, but it will help you for sure. https://stackoverflow.com/a/25874524/5032645 . Please try and let me know, if I should simplify it more for you.
First, look at the Pods_QuickstartApp.framework in the Frameworks group of your Quickstart project. If it is still red, as it is on your screenshot, then Xcode didn't build it. If Xcode didn't build the framework, Xcode can't import it for you.
Cocoapods builds a workspace including your app project, plus another project that assembles your individual pod frameworks into a larger framework.
It seems cocoapods built your workspace, and you did open the workspace instead of the project. That's good.
Check the contents of the file named "Podfile". It should match:
platform :ios, '7.0'
use_frameworks!
target 'QuickstartApp' do
pod 'GoogleAPIClient/Drive', '~> 1.0.2'
pod 'GTMOAuth2', '~> 1.1.0'
end
If it doesn't, fix it, exit Xcode, delete the .xcodeworkspace file, and then run
pod install
from the console. That may fix your dependencies so that Xcode builds the frameworks.
If you do get it to compile, your problems have just begun. Google has deprecated the OAAuth authorization from an embedded user-agent.

Creating mock cloud kit objects for unit testing

Trying to mock CKDatabase but it has no accessible initializer. Is there another way to create a fake CKDatabase object?
This code comes back with the "error: cannot override 'init' which has been marked unavailable"
class MockDatabase : CKDatabase
{
override func saveRecord(record: CKRecord!, completionHandler: ((CKRecord!, NSError!) -> Void)!) { }
override func deleteRecordWithID(recordID: CKRecordID!, completionHandler: ((CKRecordID!, NSError!) -> Void)!) { }
override func fetchRecordWithID(recordID: CKRecordID!, completionHandler: ((CKRecord!, NSError!) -> Void)!) { }
override func deleteSubscriptionWithID(subscriptionID: String!, completionHandler: ((String!, NSError!) -> Void)!) { }
override func saveSubscription(subscription: CKSubscription!, completionHandler: ((CKSubscription!, NSError!) -> Void)!) {}
}
The cloud kit framework seems like a perfect candidate for unit testing as there are a lot of possible errors that can come back from the server and making sure your app can handle them all consistently , with every iteration would be great. Has anyone found a way around this?
Since CKDatabase init is made unavailable, you will never be able to create an instance of it. So there is no point in creating your MockDatabase because you will never be able to create an instance of that. The only way to do some sort of Mocking is by creating your own DAO class wrapper around the CKDatabase functionality and only call CKDatabase functions using that DAO. Then you could mock that DAO. You could even add extra functionality to that DAO, but then you will not be able to test that.
All the CloudKit classes are #objc (they all derive from NSObject.) That means they use dynamic dispatching. In Objective-C you could mock CKDatabase by implementing a new Obj-C class MockCKDatabase that subclasses NSObject and implements all the public methods of CKDatabase (or at least all the ones you use.) Then you literally just instantiate one and cast the pointer from MockCKDatabase* to CKDatabase*.
I've never done this sort of thing in Swift, but I imagine that if you just did the same steps it should work.
as #Edwin pointed out, CKDatabase init is made unavailable, so you can't make instances of it. Neither can you subclass or use an extension to override CKContainer init (compiler will complain that 'init()' is unavailable). You can't even use a CKContainer with a mock of CKDatabase, since the logic that associates them is hidden within the CloudKit implementation. All that said, I built a framework called MockCloudKitFramework that is a drop-in replacement for a limited (but reasonable) subset of CloudKit capabilities. The MockCloudKitFramework (MCF) implements mock operations for CKContainer and CKDatabase, plus the main subclasses of CKDatabaseOperation.
MCF creates its own protocols (that contain the same signatures as their CloudKit counterparts) and extends both CloudKit and MCF mocks from this set of protocols. It was necessary to create these protocols since CKContainer and CKDatabase both inherit directly from NSObject as their common Protocol. So that IOC would force generic functions to be open to NSObject types - leaving functions wide open for injection of everything that inherits from NSObject.
So using MCF comes down to:
Extending CloudKit and MCF classes from these protocols.
Refactoring your code to use generics - typed on the MCF protocols.
Using IOC to inject the mocks of CKContainer and CKDatabase (MockCKContainer and MockCKDatabase, respectively).
See the MCF docs - examples are provided, along with unit and integration tests.
Using MCF
Lets say that you have a generic class that takes a type based on the CKContainerProtocol protocol (one of the MCF protocols). It contains a method that accepts either a CKFetchRecordsOperation or a MockCKFetchRecordsOperation (the MCF mock of CKFetchRecordsOperation) based on their common MCF protocol, CKFetchRecordsOperationProtocol:
import CloudKit
/// Example Class to handle iCloud related transactions.
class CloudController<T: CKContainerProtocol> {
let cloudContainer: T
let database: T.DatabaseType
init(container: T, databaseScope: CKDatabase.Scope) {
self.cloudContainer = container
self.database = container.database(with: databaseScope)
}
/// Check if a record exists in iCloud.
/// - Parameters:
/// - cKFetchRecordsOperation: the fetch operation
/// - recordId: the record id to locate
/// - completion: closure to execute on caller
/// - Returns: success(true) when record is located, success(false) when record is not found, failure if an error occurred.
func checkCloudRecordExists<O: CKFetchRecordsOperationProtocol> (
cKFetchRecordsOperation: O,
recordId: CKRecord.ID,
_ completion: #escaping (Result<Bool, Error>) -> Void) {
var dbOperation = cKFetchRecordsOperation
dbOperation.recordIDs = [recordId]
var record: CKRecord?
dbOperation.desiredKeys = ["recordID"]
// perRecordResultBlock doesn't get called if the record doesn't exist
dbOperation.perRecordResultBlock = { _, result in
// success iff no partial failure
switch result {
case .success(let r):
record = r
case .failure:
record = nil
}
}
// fetchRecordsResultBlock always gets called when finished processing.
dbOperation.fetchRecordsResultBlock = { result in
// success if no transaction error
switch result {
case .success:
if let _ = record { // record exists and no errors
completion(.success(true))
} else { // record does not exist
completion(.success(false))
}
case .failure(let error): // either transaction or partial failure occurred
completion(.failure(error))
}
}
database.add(dbOperation)
}
}
We can build a test without MCF that validates that the record does (or doesn't) exist, but first we have to:
Populate (or depopulate) the CloudKit test environment with the data to set the test up.
Make sure that we are using the same CKDatabase scope (public, private, or shared) as where the data is.
Hope that there are no network hiccups or data transfer errors that will inject unanticipated errors or interruptions.
But still, we can't test what the error conditions might be since we have no control over them.
MCF allows us to test all of this, using a local mock instance of CKDatabase (MockCKDatabase) that we can manipulate directly or through CloudKit modification operations like (Mock)CKModifyRecordsOperation. MockCKContainer is used to access the MockCKDatabase instance of the appropriate scope using the same accessors as CKContainer does. Here is an example of a test case setup:
import XCTest
import CloudKit
#testable import OurProject // required for access to CloudController
import MockCloudKitFramework // import MCF only into test environment
class MockCloudKitTestProjectIntegrationTest: XCTestCase {
var cloudContainer: MockCKContainer!
var cloudDatabase: MockCKDatabase!
// NOTE that CloudController class is a Generic and that both CKContainer and MockCKContainer extend CKContainerProtocol
var cloudController: CloudController<MockCKContainer>!
// called before each test
override func setUpWithError() throws {
try? super.setUpWithError()
// reset state for each test. Each test must set up exactly the state they require.
MockCKContainer.resetContainer()
// get the default container from MCF
cloudContainer = MockCKContainer.default()
// get the MockCKDatabase of CKDatabase.Scope of private
cloudDatabase = cloudContainer.privateCloudDatabase
// we pass in a MockCKContainer to the generic function expecting a class that extends MCF CKContainerProtocol
cloudController = CloudController(container: cloudContainer, databaseScope: .private)
}
}
Now we can test the checkCloudRecordExists() method by adding the record that we expect to fetch directly to MockCKDatabase and calling the function, passing in our mock CKFetchRecordsOperation:
func test_checkCloudRecordExists_success() {
let expect = expectation(description: "CKDatabase fetch")
let record = makeCKRecord() // function that makes a CKRecord
// add the record to MockCKDatabase
cloudDatabase.addRecords(records: [record])
let operation = MockCKFetchRecordsOperation()
// pass in our mock CKFetchRecordsOperation
cloudController.checkCloudRecordExists(cKFetchRecordsOperation: operation, recordId: record.recordID) { exists in
XCTAssertTrue(exists)
expect.fulfill()
}
waitForExpectations(timeout: 1)
}
If we wanted to test an error condition from the call, we just need to add an error using the setError property of MockCKDatabaseOperation (or any of its subclasses). Now the test will fail, even though the record exists. This simulates an internal error that was the result of a CloudKit transaction:
// call checkCloudRecordExists() when the record is present but error is set
func test_checkCloudRecordExists_error() {
let expect = expectation(description: "CKDatabase fetch")
let record = makeCKRecord()
cloudDatabase.addRecords(records: [record])
let operation = MockCKFetchRecordsOperation()
// set an error on operation
let nsErr = createNSError(with: CKError.Code.internalError)
operation.setError = nsErr
cloudController.checkCloudRecordExists(cKFetchRecordsOperation: operation, recordId: record.recordID) { result in
switch result {
case .success:
XCTFail("should have failed")
expect.fulfill()
case .failure(let error):
XCTAssertEqual(error.createCKError().code.rawValue, nsErr.code)
expect.fulfill()
}
}
waitForExpectations(timeout: 1)
}
We can even test our function logic for partial failures to make sure that we handle the scenario of when a record might be found but some CKError occurred so we cannot be sure. All we need to do is set the setRecordErrors property on the operation to the set of record ids that should fail (MCF picks a random CKError to fail with):
// test for partial failures
func test_checkCloudRecordExists_partial_failure() {
let expect = expectation(description: "CKDatabase fetch")
let record = makeCKRecord()
cloudDatabase.addRecords(records: [record])
let operation = MockCKFetchRecordsOperation()
// set an error on operation
operation.setRecordErrors = [record.recordID]
cloudController.checkCloudRecordExists(cKFetchRecordsOperation: operation, recordId: record.recordID) { result in
switch result {
case .success:
XCTFail("should have failed")
case .failure(let error):
let ckError = error.createCKError()
XCTAssertEqual(ckError.code.rawValue,
CKError.partialFailure.rawValue,
"The transaction error should always be set to CKError.partialFailure when record errors occur")
if let partialErr: NSDictionary = error.createCKError().getPartialErrors() {
let ckErr = partialErr.allValues.first as? CKError
XCTAssertEqual("CKErrorDomain", ckErr?.toNSError().domain)
expect.fulfill()
}
}
}
waitForExpectations(timeout: 1)
}