General principles
Deferred results
Some SDK methods (e.g., those that access a remote server) return deferred results (Future). To process a deferred result, you can specify two callback functions: completion and error. To move the execution to the main thread, you can use DispatchQueue.
For example, to get information from object directory, you can process Future like so:
// Create an object for directory search.
let searchManager = SearchManager.createOnlineManager(context: sdk.context)
// Get object by identifier.
let future = searchManager.searchByDirectoryObjectId(objectId: object.id)
// Process the search result in the main thread.
// Save the result to a property to prevent garbage collection.
self.searchDirectoryObjectCancellable = future.sink(
receiveValue: {
[weak self] directoryObject in
guard let directoryObject = directoryObject else { return }
DispatchQueue.main.async {
self.handle(directoryObject)
}
},
failure: { error in
DispatchQueue.main.async {
self.handle(error)
}
}
)
To simplify working with deferred results, you can create an extension:
extension DGis.Future {
func sinkOnMainThread(
receiveValue: @escaping (Value) -> Void,
failure: @escaping (Error) -> Void
) -> DGis.Cancellable {
self.sink(on: .main, receiveValue: receiveValue, failure: failure)
}
func sink(
on queue: DispatchQueue,
receiveValue: @escaping (Value) -> Void,
failure: @escaping (Error) -> Void
) -> DGis.Cancellable {
self.sink { value in
queue.async {
receiveValue(value)
}
} failure: { error in
queue.async {
failure(error)
}
}
}
}
self.searchDirectoryObjectCancellable = future.sinkOnMainThread(
receiveValue: {
[weak self] directoryObject in
guard let directoryObject = directoryObject else { return }
self.handle(directoryObject)
},
failure: { error in
self.handle(error)
}
)
Or use the Combine framework:
// Extension to convert DGis.Future to Combine.Future
extension DGis.Future {
func asCombineFuture() -> Combine.Future<Value, Error> {
Combine.Future { [self] promise in
// Save the Cancellable object until the callback function is called.
// Combine does not support cancelling Future directly.
var cancellable: DGis.Cancellable?
cancellable = self.sink {
promise(.success($0))
_ = cancellable
} failure: {
promise(.failure($0))
_ = cancellable
}
}
}
}
// Create Combine.Future
let combineFuture = future.asCombineFuture()
// Process the search result in the main thread.
combineFuture.receive(on: DispatchQueue.main).sink {
[weak self] completion in
switch completion {
case .failure(let error):
self?.handle(error)
case .finished:
break
}
} receiveValue: {
[weak self] directoryObject in
self?.handle(directoryObject)
}.store(in: &self.subscriptions)
Data channels
Some SDK objects provide data channels (see the Channel class). To subscribe to a data channel, you need to create and specify a handler function.
For example, you can subscribe to a visible rectangle channel, which is updated when the visible area of the map is changed:
// Choose a data channel.
let visibleRectChannel = map.camera.visibleRectChannel
// Subscribe to the channel and process the results in the main thread.
// It is important to prevent the connection object from getting garbage collected to keep the subscription active.
self.cancellable = visibleRectChannel.sink { [weak self] visibleRect in
DispatchQueue.main.async {
self?.handle(visibleRect)
}
}
When the data processing is no longer required, it is important to close the connection to avoid memory leaks. To do this, call the cancel()
method:
self.cancellable.cancel()
You can create an extension to simplify working with data channels:
extension Channel {
func sinkOnMainThread(receiveValue: @escaping (Value) -> Void) -> DGis.Cancellable {
self.sink(on: .main, receiveValue: receiveValue)
}
func sink(on queue: DispatchQueue, receiveValue: @escaping (Value) -> Void) -> DGis.Cancellable {
self.sink { value in
queue.async {
receiveValue(value)
}
}
}
}
self.cancellable = visibleRectChannel.sinkOnMainThread { [weak self] visibleRect in
self?.handle(visibleRect)
}