I'm currently implementing against an internal SDK with delegation in swift.
The current process for performing an action
1.) Initiate 'start' which will call the delegate
2.) My implemented delegate method will make an API call for a token
3.) The token is then used to make subsequent requests within my action.
The issue that I have is that I can't pass a closure/callback into the start process which is obviously async but then how do i await the completion of the delegate method all within the same function?
I'm thinking notifications might be the answer but I'm not a swift ninja.
So... (pseudocode)
func performAction() {
internalSDK.start()
// calls my implemented delegate
// sets the token on self
doActionUsingTheTokenRetrievedInMyDelegateMethod(token: self.token)
}
It feels like I need some kind of await or an observer which is then removed at the end of the call.
It should also be noted that the delegate method is generic so I can't implement the code within the method itself.
I would try to make do with the simplest possible tools available, e.g. a property observer on the token, like so:
class Foo {
var token: Token {
didSet {
doActionUsingTheTokenRetrievedInMyDelegateMethod(token: self.token)
}
}
func performAction() {
internalSDK.start()
// calls my implemented delegate
// sets the token on self
}
}
Related
I am developing an API with its own delegate. I provide the caller a property to chose their own callback queue for the delegate methods.
The structure of my API class looks like:
class MyAPI {
weak var delegate: APIDelegate!
let delegateDispatchQueue: DispatchQueue
init(delegate: APIDelegate, delegateDispatchQueue: DispatchQueue) {
self.delegate = delegate
self.delegateDispatchQueue = delegateDispatchQueue
}
// public method definitions ...
}
While mostly I can call the delegate methods asynchronously, in some cases I need to call them synchronously. And that's where I seem to run into problems. If the user of my API calls my methods on the main thread, and they give the delegateDispatchQueue as the main queue, I get a crash when I try to call delegate methods synchronously.
Here is the helper class I'm using to dispatch my delegate calls to hopefully add a bit more flesh to this issue:
// Calls SyncServerDelegate methods on the `delegateDispatchQueue` either synchronously or asynchronously.
class Delegator {
private weak var delegate: SyncServerDelegate!
private let delegateDispatchQueue: DispatchQueue
init(delegate: SyncServerDelegate, delegateDispatchQueue: DispatchQueue) {
self.delegate = delegate
self.delegateDispatchQueue = delegateDispatchQueue
}
// All delegate methods must be called using this, to have them called on the client requested DispatchQueue. If sync is true, delegate method is effectively called synchronously on the `delegateDispatchQueue`. If sync is false, delegate method is called asynchronously on the `delegateDispatchQueue`.
func call(sync: Bool = false, callback: #escaping (SyncServerDelegate)->()) {
if sync {
// This is crashing with: Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
// seemingly because I am doing a sync dispatch on the main thread when I'm already on the main thread. The problem is, I can't compare threads/queues. https://stackoverflow.com/questions/17489098
delegateDispatchQueue.sync { [weak self] in
guard let self = self else { return }
callback(self.delegate)
}
}
else {
delegateDispatchQueue.async { [weak self] in
guard let self = self else { return }
callback(self.delegate)
}
}
}
}
My initial thought on a solution was to internally dispatch methods to another queue. Such as:
class MyAPI {
// ...
private let startQueue = DispatchQueue(label: "SyncServer", qos: .background)
public myAPIMethod() throws {
startQueue.async {
try myAPIMethodAux() // syntax error
}
}
}
but this is currently a non-starter because I am doing error handling in much of my code by throwing errors and the above pattern immediately generates a syntax error. I could re-write code without this form of error handling, but that's a big effort I'm not quite ready to take on.
Thoughts?
Update
I've not solved this yet, but am working around it. I've split my delegate methods into two parts. The main group of them I can call back asynchronously on delegateDispatchQueue. The other group, where I need to call them synchronously, I make no promises about what queue I call them on-- and just use the same queue that my API is currently running on.
I'm learning RxSwift and I've come across the following pattern when creating Observables:
return Observable.create { observer in
let disposable = Disposables.create()
// Do some stuff with observer here
return disposable
}
As far as I can tell the returned Disposable doesn't actually do anything, does it serve a purpose other than to meet the requirements of the API to return a Disposable?
Is there any scenario where you might need to return a configured Disposable?
I suppose the thing that's confusing me the most is that the returned Disposable seems separate from the implementation of the Observable being created, i.e. it's not assigned to any properties or passed anywhere it's just created and returned.
There are two variations of the create method in relation to Disposables.
The first one, as Daniel mentioned, is used when you create a new Observable; you'll use the Disposables.create { ... } closure to "do cleanup", basically.
This is highly useful when using flatMapLatest, as your previous request will be disposed when a new ones comes in. Whenever it would be disposed, that "clean up" block will be called.
Observable<Int>.create { observer in
let someRequest = doSomeLongRunningThing { result in
observer.onNext(result)
observer.onCompleted()
}
return Disposables.create {
// How can I "cleanup" the process?
// Cancel the request, for example.
someRequest.cancel()
}
}
The second variation of Disposables.create is used for an entirely different purpose - grouping several Disposable objects as a single disposable object (a CompositeDisposable).
For example:
let disposable1 = someAction()
let disposable2 = someOtherAction()
let compositeDisposable = Disposables.create(disposable1, disposable2)
The Disposables.create function takes an optional closure. You should put any cancelation code in that closure. If you don't have any way to cancel, then the code is empty.
A good example is the wrapper around URLSession's dataTask method. In non-Rx code when you call URLRequest.shared.dataTask it returns a URLSessionDataTask object which can be used to cancel the network call. That object's cancel function gets called in the disposable.
Another common use is when you subscribe to some other observable from within your create closure. You then have to pass the disposable from that/those subscriptions by returning a Disposables.create(myDisposable) So that those subscriptions will get canceled properly when your Observable is disposed of.
This might be very basic. But, I am not very sure if the delegates are necessary in the following scenario?
Are delegates used in synchronous ways? If yes, is it good to call a delegate method in a function called by a caller who is a delegate[Like the example below]?
class FooViewController: UIViewController {
func login() {
let loginHelper = LoginHelper()
loginHelper.fooDelegate = self
loginHelper.shouldEnableLogin()
}
func enableLogin() {
// Do some UI updates
}
func reset() {
// Clear some values in the views
}
}
class LoginHelper {
weak var delegate: fooDelegate?
func shouldEnableLogin() {
//clear some text views
delegate.reset()
//do some validation, synchronous
delegate.enableLogin()
}
}
Delegates are a design pattern that allows one object to send messages to another object when a specific event happens. Imagine an object A calls an object B to perform an action. Once the action is complete, object A should know that B has completed the task and take necessary action, this can be achieved with the help of delegates!
I think the crux here is your question "Are delegates used in synchronous ways?".
The fundamental delegate mechanism is synchronous: I.e. the called delegate method will be on the same thread as the caller. So if the caller is your object then you control what thread this occurs on.
However the caller could create a new thread and then call the delegate method from that. So if the caller is not yours, check the documentation for it carefully before relying on the call being on the same thread.
I want to be able to make calls App-To-App using Sinch. I have followed the steps and converted the code into Swift. For some reason the method didReceiveIncomingCall is never called. I will paste some of my code so you can see how I initiated the SinchClient
in my viewDidLoad method I have.
sinchClient = Sinch.clientWithApplicationKey("key", applicationSecret: "secret", environmentHost: "sandbox.sinch.com", userId: "idOfUser")
sinchClient.setSupportCalling(true)
sinchClient.delegate = self
sinchClient.start()
sinchClient.startListeningOnActiveConnection()
Then I make a call from user1 to user2 using the following.
if(clientStarted){
let callClient = sinchClient.callClient()
callClient.callUserWithId("user2")
sinchCall.delegate = self
}
This is my delegate methods for the SINCallClientDelegate
extension CallViewController: SINCallClientDelegate{
func client(client: SINCallClient!, didReceiveIncomingCall call: SINCall!) {
print("GOT ME AN INCOMING CALL")
sinchCall = call
sinchCall.delegate = self
sinchCall.answer()
}
}
For some reason, the method didRecieveIncomingCall never gets called. Sometimes I also get the following error:
WARNING: Delegate for SINCallClient not assigned? Delegate should handle incoming call. (see -[SIN allClient Delegate notifyDelegate fIncomingCall:])
I think maybe it might have something to do with sinchClient.startListeningOnActiveConnection() but I am not sure.
couple things to note is that I do know when my sinchClient is started because I listen for it in my delegate. When clientDidStart is called, I change the variable for clientStarted: Bool. I also have three delegates in one file (SINCallClientDelegate, SINCallDelegate, and SINClientDelegate). It waits for sinchClient to be started before making any calls.
So my solution was so simple. I never set the sinchClient.callClient().delegate = self. Please read the comments on the question.
sinchClient.callClient().delegate = self
sinchClient.setSupportCalling(true)
sinchClient.delegate = self
sinchClient.start()
sinchClient.startListeningOnActiveConnection()
I want to use blocks as callback handler, but I am not sure what I am doing is proper or not because my app is crashing.
Here is what I am doing:
In my FirstViewController I am calling method of class FirstModel to get data from server as follows:
//In FirstViewController.m
[aFirstModelObj retreiveDataWithCallBackHandler:^(NSDictionary *responseDict){
//Data is received so we can proceed...
}];
//In FirstModel.m
typedef void(^newBlock)(NSDictionary *);
newBlock theBlock;
-(void)retreiveDataWithCallBackHandler:(void(^)(NSDictionary *))aBlock
{
//Saving "aBlock" for further use..
theBlock = aBlock;
//Server Processor will retrieve data using URL asynchronously,
//initializing ServerProcessor object and providing FirstModel its delegate,so that when data is received in ServerProcessor class FirstModel's receivedResponse method will get called.
serverProcessorObj.delegate = self;
}
-(void)receivedResponse:(NSDictionary *)responseDict
{
//once data is received call block,
theBlock(responseDict);
}
My app is crashing because I am loosing delegate. When I call [delegate receivedResponse:response] from ServerProcessor it says exc bad access. Can anyone tell what I am doing wrong?
Thanks in advance!
Besides checking that the block is not nil (as per comments), perhaps it has something to do with:
The above approach will only allow you to process one asynchronous request at a time. Are you trying to process concurrent requests? A subsequent request will override the block for a previous request. This could cause problems.
If you need to support concurrent requests, you'll need retain each of the blocks for a currently running request, so that the can be invoked upon completion of the request.
If you were passing on object in, such as:
retreiveDataForCustomer:(Customer*)customer onSuccess:(void (^)(NSDictionary*))success
. . . then I would suggest create a property or associative reference on the customer object to retain the block. However as your call has no parameters, you'll have to find another way of tracking which block goes with which delegate invocation.
I've corrected your code.
-(void)retreiveDataWithCallBackHandler:(void(^)(NSDictionary *))aBlock
{
//Saving "aBlock" for further use..
theBlock = Block_copy(aBlock);
//Server Processor will retrieve data using URL asynchronously,
//initializing ServerProcessor object and providing FirstModel its delegate,so that when data is received in ServerProcessor class FirstModel's receivedResponse method will get called.
serverProcessorObj.delegate = self;
}
-(void)receivedResponse:(NSDictionary *)responseDict
{
//once data is received call block,
if (theBlock)
theBlock(responseDict);
}