My app is heavily dependent on the data that is coming. I want it to run the activity indicator and disable user interaction on the view while the data is being downloaded.
Is there a way to check or return something when the completion handler is done?
typealias CompletionHandler = (success:Bool) -> Void
func downloadFileFromURL(url: NSURL,completionHandler: CompletionHandler) {
**download code**
let flag = true
true if download succeed,false otherwise
completionHandler(success: flag)
}
How to use it.
downloadFileFromURL(NSURL(string: "url_str")!, { (success) -> Void in
**When download completes,control flow goes here.**
if success {
} else {
}
})
Define a property, which keeps completion handler, and call it when all the data is obtained:
var didObtainAllData: (() -> Void)?
func obtainData() {
....
// When data is obtained.
didObtainAllData?()
}
You can write
func processingTask(condition: String, completionHandler:(finished: Bool)-> Void) ->Void {
}
Use
processingTask("test") { (finished) in
if finished {
// To do task you want
}
}
Related
I want to change the API request code written using the closure to RxSwift.
For example, I would like to make rxGetList() function using getList() function.
// This function cannot be modified.
func getList(success: #escaping ([String]) -> Void,
failure: #escaping (Error) -> Void) {
// Request to Server...
}
func rxGetList() -> Observable<String> {
// Using getList() function
// TODO
}
What code should I write in TODO section?
Please give me some advice.
The easiest way to meet your expectations is to use something like this:
func rxGetList() -> Observable<String> {
return Observable.create { observer in
getList(success: { result in
for everyString in result {
observer.onNext(everyString)
}
observer.onCompleted()
}, failure: { error in
observer.onError(error)
})
return Disposables.create() {
// specify any action to be performed on observable dispose (like cancel URL task)
}
}
}
Note that you have [String] specified as an input type of your success closure. If it's not a typo then above code fits. If you want one String instead, it's as simple as this:
func rxGetList() -> Observable<String> {
return Observable.create { observer in
getList(success: { result in
observer.onNext(result)
observer.onCompleted()
}, failure: { error in
observer.onError(error)
})
return Disposables.create() {
// specify any action to be performed on observable dispose (like cancel URL task)
}
}
}
Petr Grigorev's answer is the correct one, but if you want to have fun with some extreme function composition, here's a more advanced way to handle it:
let rxGetList = Observable.create(rx_(getList(success:failure:)))
.flatMap { Observable.from($0) }
func rx_<A>(_ fn: #escaping (#escaping (A) -> Void, #escaping (Error) -> Void) -> Void) -> (AnyObserver<A>) -> Disposable {
{
fn(singleObserve($0), $0.onError)
return Disposables.create()
}
}
func singleObserve<A>(_ observer: AnyObserver<A>) -> (A) -> Void {
{
observer.onNext($0)
observer.onCompleted()
}
}
I'm not sure about actually using the above, but if you have a lot of functions that you want to wrap, it may help reduce the amount of code you have to write.
I want to implement performFetchWithCompletionHandler in my appdelegate. I want to do something like this
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
DownloadManager.shared.startDownload(completion: { (done) in
if done{
completionHandler(.newData)
}else{
completionHandler(.noData)
}
})
I am not sure how to implement this on my DownloadManager. Its actual implementation looks like
class DownloadManager{
var pagesDownloadCompleted = false
var imagesDownloadCompleted = false
// Function to start the download
func startDownload(date: DataToDownload){
...
downloadPages(pages)
downloadImages(images)
}
}
// Function to download the pages
func downloadPages(pages: Pages){
....
if completed{
pagesDownloadCompleted = true
}
}
// Function to download the images
func downloadImages(images: Images){
....
if completed{
imagesDownloadCompleted = true
}
}
// Function to check if the doanload is done
func downloadCompleted() -> Bool{
return pagesDownloadCompleted == true && imagesDownloadCompleted == true
}
}
}
A possible solution is to start downloading the pages, when I am done I start downloading the images, and when the images are done I return true. But downloading the images and the pages will not be parallel. Do you see any other solution?
Should be something like this, you still need to handle failed and data flow though, try to use dispatch group is another solution also:
class DownloadManager {
var pagesDownloadCompleted = false
var imagesDownloadCompleted = false
// Function to start the download
func startDownload(date: String, completion: (_ completed: Bool)->()){
downloadPages(pages: page) { (completed) in
if completed {
pagesDownloadCompleted = true
}
self.downloadCompleted(completion: completion)
}
downloadImages(images: image) { (completed) in
if completed {
imagesDownloadCompleted = true
}
self.downloadCompleted(completion: completion)
}
}
// Function to download the pages
func downloadPages(pages: Pages, completion: (_ completed: Bool)->()){
completion(true) //When download finished
}
// Function to download the images
func downloadImages(images: Images, completion: (_ completed: Bool)->()){
completion(true) //When download finished
}
// Function to check if the doanload is done
func downloadCompleted(completion: (_ completed: Bool)->()){
if pagesDownloadCompleted == true && imagesDownloadCompleted == true {
completion(true)
}
}
}
IMO this kind of problem is best solved using DispatchGroup. Whenever you start a download, call enter, when that's finished call leave. group.notify is used to determine when all downloads are done.
class DownloadManager{
var group = DispatchGroup()
func startDownload(date: DataToDownload) {
downloadPages(pages)
downloadImages(images)
group.notify(queue: DispatchQueue.main) {
// all downloads completed
}
}
// Function to download the pages
func downloadPages(_ pages: Pages){
group.enter()
// do the download, when done, call:
group.leave()
}
// Function to download the images
func downloadImages(_ images: Images){
group.enter()
// do the download, when done, call:
group.leave()
}
}
I am trying to get user data from a server. The application does not have to show any views until the data is loaded.
I read about typealias and I don't understand how to use it.
What I want: when data is loaded, move on to next step. If failed, load data again.
Here's how I declare typealias
typealias onCompleted = () -> ()
typealias onFailed = () -> ()
Here is my request code
func getUserData(_ completed: #escaping onCompleted, failed: #escaping onFailed){
let fullURL = AFUtils.getFullURL(AUTHURL.getUserData)
AFNetworking.requestGETURL(fullURL, params: nil, success: {
(JSONResponse) -> Void in
if let status = JSONResponse["status"].string {
switch status{
case Status.ok:
completed()
break
default:
failed()
break
}
}
})
}
But how could I use this on my view controller when calling getUserData?
Assuming your custom AFNetworking.requestGETURLs completion handler is called on the main queue:
func viewDidLoad() {
super.viewDidLoad()
getUserData({
//do somthing and update ui
}) {
//handle error
}
}
Edit:
How I understand your comment, you actually want to name your completion and error block parameters. If so, change the method to :
func getUserData(completion completed: #escaping onCompleted, error failed: #escaping onFailed){ ... }
and call it like this:
getUserData(completion: {
//do somthing and update ui
}, error: {
//handle error
})
I have a need to implement a spinning wheel during a backend job. I have my backend-job in a separate class.
class ViewControllerA: UITableViewController {
// Code
var GetBackendRecordObj = GetBackendRecord(initparam:param);
// CODE TO START ANIMATION (SPINNING WHEEL)
self.view.addSubview(self.activityIndicator)
self.activityIndicator.bringSubview(toFront: self.view)
self.activityIndicator.startAnimating()
// CODE TO CALL THE BACKEND IS IN ANOTHER CLASS
GetBackendRecordObj.fetch_record()
}
class GetBackendRecord{
var transaction_id: String = ""
var current_email_id: String = ""
init(initparam: String) {
self.initparam = initparam
}
func fetch_record (){
do{
DispatchQueue.global(qos: .userInitiated).async {
//code
DispatchQueue.main.async { () -> Void in
//code to process response from backend
// NEED CODE TO STOP ANIMATION (SPINNING WHEEL)THAT WAS STARTED IN VIEWCONTROLLERA
})
}
}
}
How can I access the UITableViewcontroller after the backend call is done, so I can stop the animation. OR If there is a better way to start / stop animation when executing a backend-job (in a separate class) please let me know.
Add a completion handler to fetch_record:
func fetch_record(_ completionHandler: #escaping () -> Swift.Void) {
do{
DispatchQueue.global(qos: .userInitiated).async {
//code
DispatchQueue.main.async { () -> Void in
//code to process response from backend
completionHandler()
})
}
}
}
When calling it in your ViewController, you can specify what to do after completion:
GetBackendRecordObj.fetch_record() {
self.activityIndicator.stopAnimating()
}
If you need to know when the response returns so that you can stop the loader, you need to add a completion handler to the method that makes your internet call. You should generally have completion handlers on most methods that make internet calls, especially if you need UI things to happen only once you have gotten a response.
So for the fetchRecord function:
I have added a completion handler to this call. Preferably you would hand something off here just after #escaping(something like a dictionary or an array if it is a JSON response) and then process that response in another method. But if you want the code to process the response in this method with the threading that you've set up here, then I've written it accordingly.
func fetch_record(withCompletion comp: #escaping () ->()){
do{
DispatchQueue.global(qos: .userInitiated).async {
//code
DispatchQueue.main.async { () -> Void in
//code to process response from backend
//this tells whatever called this method that it is done
comp()
})
}
}
}
Then in your view controller when you call GetBackendRecordObj.fetch_record()
GetBackendRecordObj.fetch_record(withCompletion: { [weak self] () in
//when it hits this point, the process is done
self?.activityIndicator.stopAnimating()
}
Instead of activityIndicator better to use MBProgressHUD.
https://github.com/jdg/MBProgressHUD
To show MBProgressHUD
let loadingNotification = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
loadingNotification.mode = MBProgressHUDMode.Indeterminate
loadingNotification.labelText = "Loading"
To hide MBProgressHUD
DispatchQueue.main.async { () -> Void in
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
})
You can implement show MBProgressHUD in seperate class from where you initiate back end job and hide code once your back end process finish.
Say I have a function that has a completion handler, then calls another function, with a completion handler like this:
func register(withCompletion complete: #escaping (() -> Void)) {
self.sendRegisterRequest(withCompletion: { (error: Error?) -> () in
if error != nil {
self.performSegue(withIdentifier: "ErrorVCSegue", sender: nil)
}
else {
complete()
}
})
}
In the event of an error, it will segue away with calling complete().
Am I ok to segue away like this, without calling complete()? I do not need to return from this function as I'm now wanting to go to another View Controller.
Thanks.
This is a bad idea. A completion handler should be called no matter what. The caller is waiting for a response. It wants to know when it is done. That's the whole point of having a completion handler.
In your case (like many other cases), it would be much better if the completion handler accepted a boolean parameter (and/or an error parameter). This way the completion handler provides some basic information about the success or failure of the method.
Try like this
override func viewDidLoad() {
super.viewDidLoad()
register { (error) in
if error == nil {
// do what you want in success case
} else {
self.performSegue(withIdentifier: "ErrorVCSegue", sender: nil)
}
}
}
func register(withCompletion complete: #escaping ((_ error: Error?) -> Void)) {
self.sendRegisterRequest(withCompletion: { (error: Error?) -> () in
complete(Error)
})
}
Thanks:)