Properly pass Swift closure into another thread - swift

How do I properly (from multithreading point of view) pass a closure to another thread?
Consider a situation:
class NetManager {
...
var processingClosure : (Data, DispatchQueue, #escaping (Data?) -> ()) -> () = {
respData, complQueue, complClosure in
let resultData = // process respData according to some logic and get resultData
complQueue.async {
complClosure(resultData)
}
// PLEASE NOTE that there is no captured variables in this closure
}
...
func requestData1(..., complClosure) {
// this is main thread context
// make request to endpoint 1 somehow and process result in separate processing queue
...
let procClosure = self.processingClosure
// processingQueue is NOT main queue and not completion queue
request.processingQueue.async {
// Question HERE:
procClosure(data, DispatchQueue.main, complClosure)
// is such passing of the closure safe? Can I have issues with concurrency?
}
}
func requestData2(..., complClosure) {
// the same as requestData1 but gets data from endpoint 2
...
let procClosure = self.processingClosure
request.processingQueue.async {
procClosure(data, DispatchQueue.main, complClosure)
}
}
}
This seems a safe way to pass closure since it doesn't capture any variables. Will I have any concurrency issues with procClosure call?
Is there a better way to encapsulate a common functionality of data transformation to reuse in similar requests to different endpoints (I can encapsulate only data processing but not requesting)?

Related

How to cancel an `async` function with cancellable type returned from `async` operation initiation

I need to support cancellation of a function that returns an object that can be cancelled after initiation. In my case, the requester class is in a 3rd party library that I can't modify.
actor MyActor {
...
func doSomething() async throws -> ResultData {
var requestHandle: Handle?
return try await withTaskCancellationHandler {
requestHandle?.cancel() // COMPILE ERROR: "Reference to captured var 'requestHandle' in concurrently-executing code"
} operation: {
return try await withCheckedThrowingContinuation{ continuation in
requestHandle = requester.start() { result, error in
if let error = error
continuation.resume(throwing: error)
} else {
let myResultData = ResultData(result)
continuation.resume(returning: myResultData)
}
}
}
}
}
...
}
I have reviewed other SO questions and this thread: https://forums.swift.org/t/how-to-use-withtaskcancellationhandler-properly/54341/4
There are cases that are very similar, but not quite the same. This code won't compile because of this error:
"Reference to captured var 'requestHandle' in concurrently-executing code"
I assume the compiler is trying to protect me from using the requestHandle before it's initialized. But I'm not sure how else to work around this problem. The other examples shown in the Swift Forum discussion thread all seem to have a pattern where the requester object can be initialized before calling its start function.
I also tried to save the requestHandle as a class variable, but I got a different compile error at the same location:
Actor-isolated property 'profileHandle' can not be referenced from a
Sendable closure
You said:
I assume the compiler is trying to protect me from using the requestHandle before it’s initialized.
Or, more accurately, it is simply protecting you against a race. You need to synchronize your interaction with your “requester” and that Handle.
But I’m not sure how else to work around this problem. The other examples shown in the Swift Forum discussion thread all seem to have a pattern where the requester object can be initialized before calling its start function.
Yes, that is precisely what you should do. Unfortunately, you haven’t shared where your requester is being initialized or how it was implemented, so it is hard for us to comment on your particular situation.
But the fundamental issue is that you need to synchronize your start and cancel. So if your requester doesn’t already do that, you should wrap it in an object that provides that thread-safe interaction. The standard way to do that in Swift concurrency is with an actor.
For example, let us imagine that you are wrapping a network request. To synchronize your access with this, you can create an actor:
actor ResponseDataRequest {
private var handle: Handle?
func start(completion: #Sendable #escaping (Data?, Error?) -> Void) {
// start it and save handle for cancelation, e.g.,
handle = requestor.start(...)
}
func cancel() {
handle?.cancel()
}
}
That wraps the starting and canceling of a network request in an actor. Then you can do things like:
func doSomething() async throws -> ResultData {
let responseDataRequest = ResponseDataRequest()
return try await withTaskCancellationHandler {
Task { await responseDataRequest.cancel() }
} operation: {
return try await withCheckedThrowingContinuation { continuation in
Task {
await responseDataRequest.start { result, error in
if let error = error {
continuation.resume(throwing: error)
} else {
let resultData = ResultData(result)
continuation.resume(returning: resultData)
}
}
}
}
}
}
You obviously can shift to unsafe continuations when you have verified that everything is working with your checked continuations.
After reviewing the Swift discussion thread again, I see you can do this:
...
var requestHandle: Handle?
let onCancel = { profileHandle?.cancel() }
return try await withTaskCancellationHandler {
onCancel()
}
...

Realm Transaction without notifying tokens

I have Realm notifications on a background thread created with the following code (taken from Realm's website)
class BackgroundWorker: NSObject {
private let name: String
private var thread: Thread!
private var block: (()->Void)!
init(name: String) {
self.name = name
}
#objc internal func runBlock() {
block()
}
internal func start(_ block: #escaping () -> Void) {
self.block = block
if thread == nil {
createThread()
}
perform(
#selector(runBlock),
on: thread,
with: nil,
waitUntilDone: false,
modes: [RunLoop.Mode.default.rawValue]
)
}
private func createThread() {
thread = Thread { [weak self] in
while (self != nil && !self!.thread.isCancelled) {
RunLoop.current.run(
mode: RunLoop.Mode.default,
before: Date.distantFuture)
}
Thread.exit()
}
thread.name = name
thread.start()
}
func stop() {
thread.cancel()
}
}
And using the background worker like this
struct RealmBackGroundWorker {
static var tokens: [NotificationToken] = []
static let backgroundWorker = BackGroundWorker(name: "RealmWorker")
static func start() {
backgroundWorker.start {
self.tokens = ...
}
}
}
The background notifications work great. But I often need to save data to realm without notifying these transactions. From what I have found, it does not look like there is a way write data without notifying all tokens. You always have to specify the tokens you want to ignore.
How can I write data to the Realm without notifying these background tokens?
Let me preface this answer with a couple of things. The Realm website the OP got their code from was here Realm Notifications on Background Threads with Swift and part of the point of that code was to not only spin up a runloop on a background thread to handle Realm functions but to also handle notifications on that same thread.
That code is pretty old - 4+ years and is somewhat outdated. In essence, there are possibly better options. From Apple:
... newer technologies such as Grand Central Dispatch (GCD) provide a
more modern and efficient infrastructure for implementing concurrency
But to address the question, if an observer is added to a Realm results on thread A, then all of the notifications will also occur on thread A. e.g. the token returned from the observe function is tied to that thread.
It appears the OP wants to write data without receiving notifications
I do not want to sync local changes to the server, so I would like to
call .write(withouNotifying: RealmWorkBlock.tokens)
and
I want a way to write data to the realm database without notifying
these notifications.
Noting that those notifications will occur on the same thread as the runloop. Here's the code that we need to look at
static func start() {
backgroundWorker.start {
self.tokens = ...
}
and in particular this line
self.tokens = ...
because the ... is the important part. That ... leads to this line (from the docs)
self?.token = files.observe { changes in
which is where the observer is added that generates the notifications. If no notifications are needed then that code, starting with self?.token can be completely removed as that's is sole purpose - to generate notifications.
One thought is to add a different init to the background worker class to have a background worker with no notifications:
static func start() {
backgroundWorker.startWithoutNotifications()
}
Another thought is to take a more modern approach and leverage DispatchQueue with an autorelease pool which eliminates the need for these classes completely, will run in the background freeing up the UI ad does not involve tokens or notifications.
DispatchQueue(label: "background").async {
autoreleasepool {
let realm = try! Realm()
let files = realm.objects(File.self).filter("localUrl = ''")
}
}

How to use multithread in Swift

I have two tasks : task1 and task2. I want to execute task2 after task1 finishes.
let globalQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
dispatch_sync(globalQueueDefault){
self.activityIndicatorView.hidden = false
self.activityIndicatorView.startAnimating()
task1()
sleep(6)
dispatch_sync(globalQueueDefault) { () -> Void in
task2()
}
}
I searched in internet, I find NSLock,NSConditionLock and objc_sync_enter...I have try them, but it doesn't work...
let lock = NSLock()
let globalQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
dispatch_sync(globalQueueDefault){
self.activityIndicatorView.hidden = false
self.activityIndicatorView.startAnimating()
self.lock.lock()
task1()
self.lock.unlock()
sleep(6)
dispatch_sync(globalQueueDefault) { () -> Void in
self.lock.lock()
task2()
self.lock.unlock()
}
}
I also tried NSConditionLock and objc_sync_enter...It doesn't work. How I can use lock in swift ? Could you give me a example base on my code? Thank you.
PS: I don't want to use callback here...because I have tried it, I think multithread is more closer to my answer, Thank you.
I'm going out on a limp and making some guesses about your program structures. The first problem with your code is that it's trying to access a view on a background thread. GUI elements should always be accessed on the main thread. The second problem is sleep: don't use it to write concurrent code. It makes assumptions about how the asynchronous task is going take. You should treat that time as unknown and use a sync pattern or a call back.
Since you mentioned that task1() download JSON, it's likely asynchronous. Here's how I'd do it:
func task1(finish: () -> Void) {
// Set up your connection to the website
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
// Handle the response, parse the json, etc
...
// Now call the completion handler
finish()
}
}
func task2() {
// Do whatever here
}
// In the function that triggers the JSON download
func downloadJSON() {
self.activityIndicatorView.hidden = false
self.activityIndicatorView.startAnimating()
task1(task2)
}

RxSwift: chaining several actions

Lets imagine we have an array of AnObject instances and need to have following sequence of actions to execute:
send objects to backend via separate calls
after step 1 finishes store this array to DB in batch
after step 2 finishes do additional processing for each item
and we'd want to receive the signal only after all those steps were executed (or there was an error). What is the correct way to achieve this via RxSwift and is it actually possible?
Please find my prototype functions below. Unfortunately I didn't come up with a valid code sample for chaining, so there's nothing to demo.
func makeAPIRequest(object: AnObject) -> Observable<Void> {
...
}
func storeData(data: [AnObject]) -> Observable<Void> {
...
}
func additionalProcessing(object: AnObject) -> Observable<Void> {
...
}
func submitData()
{
let data: [AnObject] = ...;
let apiOperations = data.map{ makeAPIRequest($0) };
let storageOperation = storeData(data);
let processingOperations = data.map{ additionalProcessing($0) };
... // some code to chain steps 1-3
.subscribe { (event) -> Void in
// should be called when operations from step 3 finished
}.addDisposableTo(disposeBag);
}
Let's assume that both makeAPIRequest and additionalProcessing return an Observable<SomeNotVoidType>, and storeData takes an array as its argument and returns an Observable<Array>.
This way, you can do the following:
First, create an array of Observables representing sending individual objects to backend. Then use toObservable method, so the resulting signals can be transformed later on:
let apiOperations = data.map{ makeAPIRequest($0) }.toObservable()
then use merge operator which will make an Observable, that completes only when all of the API calls complete. You can also use toArray operator, which will put the API call results into one array:
let resultsArray = apiOperations.merge().toArray()
This will get you an Observable<Array<ApiResult>>, which will send one Next event when all API operations complete successfully. Now you can store the results in the database:
let storedResults = resultsArray.flatMap { storeDatabase($0) }
Then again you want to make Observables for each array element, so they'll represent additional processing. Note that you need to use flatMap and flatMapLates, otherwise you'll end up with nested observables like Observable<Observable<SomeType>>.
let additionalProcessingResults = storedResults.flatMap {
return $0.map(additionalProcessing).toObservable()
}.flatMapLatest { return $0 }
Then, you can subscribe for successful completion of the additional processing (or you can do something with its individual results):
additionalProcessingResults.subscribe { (event) -> Void in
// should be called when operations from step 3 finished
}.addDisposableTo(disposeBag);
Note that you don't need all the intermediate variables, I just left them to describe all the steps.

Wait for Parse Async functions to complete in Swift

I'm trying to wait for Parse async functions in Swift to reload my UITableView
I'm not sure if Completion Handler is useful in this case. or Dispatch Async.
I'm really confused ! Can someone help out with this
var posts = [PFObject]()
for post in posts {
post.fetchInBackground()
}
tableView.reloadData() // I want to execute that when the async functions have finished execution
You want to use fetchAllInBackground:Block I've had issues launching a bunch of parse calls in a loop where it will take a lot longer to return all of them than expected.
fetch documentation
It should look something like this:
PFObject.fetchAllInBackground(posts, block: { (complete, error) in
if (error == nil && complete) {
self.tableView.reloadData()
}
})
One thing to note is that in your example posts are empty and a generic PFObject. I'm assuming this is just for the example. Otherwise if you want to get all posts in Parse (as opposed to updating current ones) you will want to use PFQuery instead of fetching. query documentation
You need to use fetchInBackgroundWithBlock. Alternatively, if you want to wait until all have loaded and then update the UI, use PFObject's +fetchAllInBackground:block:. Note that this is a class method, and would therefore be called as PFObject.fetchAllInBackground(.... See documentation here.
Either way, because you're running in a background thread, you must update the UI on the main thread. This is normally done using dispatch_async.
The other thing to watch out for is if you run fetchInBackgroundWithBlock in a loop and collect all the results in an array, arrays are not thread safe. You will have to use something like dispatch_barrier or your own synchronous queue to synchronise access to the array. Code for the second option is below:
// Declared once and shared by each call (set your own name)...
let queue = dispatch_queue_create("my.own.queue", nil)
// For each call...
dispatch_sync(queue) {
self.myArray.append(myElement)
}
Here's a little class I made to help with coordination of asynchronous processes:
class CompletionBlock
{
var completionCode:()->()
init?(_ execute:()->() )
{ completionCode = execute }
func deferred() {}
deinit
{ completionCode() }
}
The trick is to create an instance of CompletionBlock with the code you want to execute after the last asynchronous block and make a reference to the object inside the closures.
let reloadTable = CompletionBlock({ self.tableView.reloadData() })
var posts = [PFObject]()
for post in posts
{
post.fetchInBackground(){ reloadTable.deferred() }
}
The object will remain "alive" until the last capture goes out of scope. Then the object itself will go out of scope and its deinit will be called executing your finalization code at that point.
Here is an example of using fetchInBackgroundWithBlock which reloads a tableView upon completion
var myArray = [String]()
func fetchData() {
let userQuery: PFQuery = PFUser.query()!
userQuery.findObjectsInBackgroundWithBlock({
(users, error) -> Void in
var userData = users!
if error == nil {
if userData.count >= 1 {
for i in 0...users!.count-1 {
self.myArray.append(userData[i].valueForKey("dataColumnInParse") as! String)
}
}
self.tableView.reloadData()
} else {
print(error)
}
})
}
My example is a query on the user class but you get the idea...
I have experimented a bit with the blocks and they seem to get called on the main thread, which means that any UI changes can be made there. The code I have used to test looks something like this:
func reloadPosts() {
PFObject.fetchAllIfNeededInBackground(posts) {
[unowned self] (result, error) in
if let err = error {
self.displayError(err)
}
self.tableView.reloadData()
}
}
if you are in doubt about whether or not the block is called on the main thread you can use the NSThread class to check for this
print(NSThread.currentThread().isMainThread)
And if you want it to be bulletproof you can wrap your reloadData inside dispatch_block_tto ensure it is on the main thread
Edit:
The documentation doesn't state anywhere if the block is executed on the main thread, but the source code is pretty clear that it does
+ (void)fetchAllIfNeededInBackground:(NSArray *)objects block:(PFArrayResultBlock)block {
[[self fetchAllIfNeededInBackground:objects] thenCallBackOnMainThreadAsync:block];
}