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.
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.
Is it wrong to call async from Swift object initializer such as this one
let serialQueue = DispatchQueue(label: "com.myApp.SerialQueue")
private let property1:Int?
public override init()
{
super.init()
/* Initialize properties */
setupProperties()
serialQueue.async { [unowned self] in
self.nonBlockingSetup()
}
}
private func setupProperties() {
self.property1 = 1
}
private func nonBlockingSetup() {
//Some non-blocking code that shouldn't run on main thread
}
Some people say async call is problematic before init returns. Need to know what Swift language says about it.
EDIT: Is there any difference if I modify the code as follows:
public override init()
{
super.init()
/* Initialize properties */
setupProperties()
callNonBlockingCodeAsync()
}
private func callNonBlockingCodeAsync() {
serialQueue.async { [unowned self] in
self.nonBlockingSetup()
}
}
To answer your question, I tried out the simple example.
Errors are very much self explanatory, in the initialisation process dispatchQueue are capturing self reference right before it's actual initialisation.
You are running into the concurrency problem where initialisation of object is necessary before using it.
dispatchQueue uses closures to provide DispatchWorkItem and as you know closures captures values surrounding it's scope.
Update
One work around would be to give default values to your properties but
I am not sure if that will help you.
In general, a constructor should not do any meaningful work.
Having a constructor that executes code delayed (because it's async) will be unexpected for anyone using that class (quite possibly including you in 6 months), and can therefore lead to bugs. In such cases it's usually better to have a separate initialization method, which makes it clear to an api user that there is something more going on.
If you absolutely want to make sure the initialization method is called, I usually make the constructor private and add a class method for construction. Again this signals api users that there is something going on behind the scenes.
Let me show a simplified example of the problem I'm struggling with:
class CarService {
func getCars() -> Single<[Car]> {
return Single.create { observer in
// Here we're using a thread that was defined in subscribeOn().
someCallbackToAPI { cars in
// Here we're using main thread, because of the someCallbackToAPI implementation.
observer(.success(cars))
}
}
}
}
class CarRepository {
func syncCars() -> Completable {
return CarService().getCars()
.flatMapCompletable { cars in
// Here we're using main thread, but we want some background thread.
saveCars(cars)
}
}
}
class CarViewController {
func loadCar() {
CarRepository().syncCars()
.subscribeOn(someBackgroundScheduler)
.observeOn(MainThread)
.subscribe()
}
}
From the bottom: CarViewController wants to sync all the cars from some external API. It defines what thread should be used for the sync with subscribeOn - we don't want to block the UI thread. Unfortunately, underneath, the CarService has to use some external library methods (someCallbackToAPI) that always returns the result in a main thread. The problem is that after receiving the result, all methods below like e.g. saveCars are called in the same main thread. saveCars may block the UI thread because it saves data to database. Of course I could add observeOn between threads between CarService().getCars() and flatMapCompletable, but I want the CarRepository to be dump and know nothing about the threads. It is the CarViewController responsibility to define working thread.
So my question is, is it a way I could get the scheduler passed in subscribeOn method and switch back to the scheduler after receiving the result from someCallbackToApi?
The short answer is no.
As you surmise, the problem is that your someCallbackToAPI is routing to the main thread which is not what you wanted and there's nothing you can do about that short of re-writing someCallbackToAPI. If you are using Alamofire or Moya, I think they have alternative methods that won't call the closure on the main thread but I'm not sure. URLSession does not switch to the main thread so one idea would be to use it instead.
If you want the saveCars to happen on a background thread, you will have to use observeOn to push the computation back onto a background thread from main. The only thing subscribeOn will do is call someCallbackToAPI(_:) on a background thread, it cannot dictate what thread the function will call its closure on.
So something like:
func syncCars() -> Completable {
return CarService().getCars()
.observeOn(someBackgroundScheduler)
.flatMapCompletable { cars in
// Now this will be on the background thread.
saveCars(cars)
}
}
As a final note, an empty subscribe is a code smell. Any time you find your-self calling .subscribe() for anything other than testing purposes, you are likely doing something wrong.
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
}
}
Many posts seem to advise against notifications when trying to synchronize functions, but there are also other posts which caution against closure callbacks because of the potential to inadvertently retain objects and cause memory issues.
Assume inside a custom view controller is a function, foo, that uses the Bar class to get data from the server.
class CustomViewController : UIViewController {
function foo() {
// Do other stuff
// Use Bar to get data from server
Bar.getServerData()
}
}
Option 1: Define getServerData to accept a callback. Define the callback as a closure inside CustomViewController.
Option 2: Use NSNotifications instead of a callback. Inside of getServerData, post a NSNotification when the server returns data, and ensure CustomViewController is registered for the notification.
Option 1 seems desirable for all the reasons people caution against NSNotification (e.g., compiler checks, traceability), but doesn't using a callback create a potential issue where CustomViewController is unnecessarily retained and therefore potentially creating memory issues?
If so, is the right way to mitigate the risk by using a callback, but not using a closure? In other words, define a function inside CustomViewController with a signature matching the getServerData callback, and pass the pointer to this function to getServerData?
I'm always going with Option 1 you just need to remember of using [weak self] or whatever you need to 'weakify' in order to avoid memory problems.
Real world example:
filterRepository.getFiltersForType(filterType) { [weak self] (categories) in
guard let strongSelf = self, categories = categories else { return }
strongSelf.dataSource = categories
strongSelf.filteredDataSource = strongSelf.dataSource
strongSelf.tableView?.reloadData()
}
So in this example you can see that I pass reference to self to the completion closure, but as weak reference. Then I'm checking if the object still exists - if it wasn't released already, using guard statement and unwrapping weak value.
Definition of network call with completion closure:
class func getFiltersForType(type: FilterType, callback: ([FilterCategory]?) -> ()) {
connection.getFiltersCategories(type.id).response { (json, error) in
if let data = json {
callback(data.arrayValue.map { FilterCategory(attributes: $0) } )
} else {
callback(nil)
}
}
}
I'm standing for closures in that case. To avoid unnecessary retains you just need to ensure closure has proper capture list defined.