Skip to main content

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)
}