General principles | Mobile SDK | Urbi Documentation
iOS SDK

General principles

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)

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