How can I create dependency for functions in swift to run one after another? In other words, I want one function to wait until another one completes its task successfully.
var sales : NSMutableArray! = NSMutableArray()
var items : NSMutableArray! = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
This is where I need help, the below dispatch method is not working properly, I get the “index 0 beyond bounds for empty array” error since the array is not populated yet I guess. Also I am not sure that if this is the right method to handle this…
/*
dispatch_async(queue, {
dispatch_sync(queue, {
self.downloadItems()
})
dispatch_sync(dispatch_get_main_queue(), {
self.displayItems()
})
})
*/
}
func downloadItems() {
let urlPath = "……………………………"
let url = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
if (error != nil) {
println(error)
} else {
let jsonResult: AnyObject = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil)!
dispatch_async(dispatch_get_main_queue()) {
for var i = 0; i < jsonResult.count; i++ {
self.sales[i] = jsonResult[i]["daily_sales"] as NSString
}
}
}
})
task.resume()
}
func displayItems() {
self.items.addObject(["amount": self.sales[0]])
}
Use 'continuation passing style'. Your operation downloadItems() will take an argument with a function to perform once downloadItems() completes - the argument is similar to a continuation (which some languages expose, see Scheme and ML for details). So it looks like:
func downloadItems (continuation: (Void -> Void)!) {
let urlPath = // ...
// ...
if nil != continuation { continuation() }
}
and then you'd use it with
self.downloadItems { [self unowned] in self.displayItems() }
Related
I am new iOS Developer
I want to change the websiteLogo API with a textfield to change the URL.
how can I change the line with the ***
with a var and a textfield in my viewcontroller?
With screenshoot it's will be easier to understand what I want? Thank you !!! Guys. OneDriveLink. 1drv.ms/u/s!AsBvdkER6lq7klAqQMW9jOWQkzfl?e=fyqOeN
private init() {}
**private static var pictureUrl = URL(string: "https://logo.clearbit.com/:http://www.rds.ca")!**
private var task: URLSessionDataTask?
func getQuote(callback: #escaping (Bool, imageLogo?) -> Void) {
let session = URLSession(configuration: .default)
task?.cancel()
task = session.dataTask(with: QuoteService.pictureUrl) { (data, response, error) in
DispatchQueue.main.async {
guard let data = data, error == nil else {
callback(false, nil)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
callback(false, nil)
return
}
let quote = imageLogo(image: data)
callback(true, quote)
print(data)
}
}
task?.resume()
}
First, please don't use screenshots do show your code. If you want help, others typically copy/paste your code to check whats wrong with it.
There are some minor issues with your code. Some hints from me:
Start your types with a big letter, like ImageLogo not imageLogo:
Avoid statics
Avoid singletons (they are almost statics)
Hand in the pictureUrl into getQuote
struct ImageLogo {
var image:Data
}
class QuoteService {
private var task: URLSessionDataTask?
func getQuote(from pictureUrl:URL, callback: #escaping (Bool, ImageLogo?) -> Void) {
let session = URLSession(configuration: .default)
task?.cancel()
task = session.dataTask(with: pictureUrl) {
(data, response, error) in
DispatchQueue.main.async {
guard let data = data, error == nil else {
callback(false, nil)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
callback(false, nil)
return
}
let quote = ImageLogo(image: data)
callback(true, quote)
print(data)
}
}
task?.resume()
}
}
Store an instance of QuoteService in your view controller
Call getQuote on that instance, handing in the pictureUrl
class ViewController : UIViewController {
var quoteService:QuoteService!
override func viewDidLoad() {
self.quoteService = QuoteService()
}
func toggleActivityIndicator(shown:Bool) { /* ... */ }
func update(quote:ImageLogo) { /* ... */ }
func presentAlert() { /* ... */ }
func updateconcept() {
guard let url = URL(string:textField.text!) else {
print ("invalid url")
return
}
toggleActivityIndicator(shown:true)
quoteService.getQuote(from:url) {
(success, quote) in
self.toggleActivityIndicator(shown:false)
if success, let quote = quote {
self.update(quote:quote)
} else {
self.presentAlert()
}
}
}
/* ... */
}
Hope it helps.
I think you want to pass textfield Text(URL Enter By user) in Web Services
Add a parameter url_str in getQuote function definition first and pass textfield value on that parameters
fun getQuote(url_str : String, callback : #escaping(Bool, ImgaeLogo/)->void){
}
I am using swift 3.0 and have created a function that returns an Array of Integers. The arrays of Integers are very specific and they are gotten from a database therefore the HTTP call is asynchronous . This is a function because I use it in 3 different controllers so it makes sense to write it once . My problem is that the Async code is returned after the return statement at the bottom therefore it is returning nil . I have tried the example here Waiting until the task finishes however it is not working mainly because I need to return the value . This is my code
func ColorSwitch(label: [UILabel]) -> [Int] {
for (index, _) in label.enumerated() {
label[index].isHidden = true
}
// I need the value of this variable in the return
// statement after the async is done
var placeArea_id = [Int]()
let urll:URL = URL(string:ConnectionString+"url")!
let sessionn = URLSession.shared
var requestt = URLRequest(url: urll)
requestt.httpMethod = "POST"
let group = DispatchGroup()
group.enter()
let parameterr = "http parameters"
requestt.httpBody = parameterr.data(using: String.Encoding.utf8)
let task = sessionn.dataTask(with:requestt, completionHandler: {(data, response, error) in
if error != nil {
print("check check error")
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any]
DispatchQueue.main.async {
if let Profiles = parsedData?["Results"] as? [AnyObject] {
if placeArea_id.count >= 0 {
placeArea_id = [Int]()
}
for Profiles in Profiles {
if let pictureS = Profiles["id"] as? Int {
placeArea_id.append(pictureS)
}
}
}
group.leave()
}
} catch let error as NSError {
print(error)
}
}
})
task.resume()
group.notify(queue: .main) {
// This is getting the value however can't return it here since it
// expects type Void
print(placeArea_id)
}
// this is nil
return placeArea_id
}
I already checked and the values are returning inside the async code now just need to return it any suggestions would be great .
You will want to use closures for this, or change your function to be synchronous.
func ColorSwitch(label: [UILabel], completion:#escaping ([Int])->Void) {
completion([1,2,3,4]) // when you want to return
}
ColorSwitch(label: [UILabel()]) { (output) in
// output is the array of ints
print("output: \(output)")
}
Here's a pretty good blog about closures http://goshdarnclosuresyntax.com/
You can't really have your function return a value from an asynchronous operation within that function. That would defeat the purpose of asynchronicity. In order to pass that data back outside of your ColorSwitch(label:) function, you'll need to also have it accept a closure that will be called on completion, which accepts an [Int] as a parameter. Your method declaration will need to look something like this:
func ColorSwitch(label: [UILabel], completion: #escaping ([Int]) -> Void) -> Void {
for (index, _) in label.enumerated() {
label[index].isHidden = true
}
var placeArea_id = [Int]()
let urll:URL = URL(string:ConnectionString+"url")!
let sessionn = URLSession.shared
var requestt = URLRequest(url: urll)
requestt.httpMethod = "POST"
let group = DispatchGroup()
group.enter()
let parameterr = "http parameters"
requestt.httpBody = parameterr.data(using: String.Encoding.utf8)
let task = sessionn.dataTask(with:requestt, completionHandler: {(data, response, error) in
if error != nil {
print("check check error")
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any]
DispatchQueue.main.async {
if let Profiles = parsedData?["Results"] as? [AnyObject] {
if placeArea_id.count >= 0 {
placeArea_id = [Int]()
}
for Profiles in Profiles {
if let pictureS = Profiles["id"] as? Int {
placeArea_id.append(pictureS)
}
}
}
group.leave()
completion(placeArea_id) // This is effectively your "return"
}
} catch let error as NSError {
print(error)
}
}
})
task.resume()
}
Later on, you can call it like this:
ColorSwitch(label: []) { (ids: [Int]) in
print(ids)
}
The following functions makes an API call which downloads JSON data and
passes it into an another function imp, which in turn creates the arrays!
func previewer(var spotify_id : [String])
{
var serial_number = 0
for var x in spotify_id
{
let spotify_url = "https://api.spotify.com/v1/tracks/"
let url_with_pars = spotify_url + x
let myurl_1 = NSURL(string: url_with_pars)
let timeout = 15
let request_1 = NSMutableURLRequest(URL: myurl_1!, cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 15.0)
let queue = NSOperationQueue()
request_1.HTTPMethod = "GET"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request_1, completionHandler: { (data, response, error) in
//NSURLConnection.sendAsynchronousRequest(request_1, queue: queue, completionHandler: { (reponse, data, error) in
if error != nil
{
print("Error!")
}
else
{
do
{
if let data_1 = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSDictionary
{
self.imp(data_1)
}
else
{
print("error!")
}
}
catch
{
print("error")
}
}
})
//task.resume()
}
print(artist.count)
do_table_refresh()
}
func do_table_refresh()
{
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
return
})
}
//FUNCTION WHICH CREATES ARRAY
func imp(var data_1:NSDictionary)
{
if let artist_name = data_1["artists"]![0]["name"] as? String
{
artist.append(artist_name)
}
else
{
print("artist error")
}
if let song_name = data_1["name"] as? String
{
print(song_name)
songs.append(song_name)
}
else
{
print("song error")
}
if let url = data_1["preview_url"] as? String
{
if let url_1 = data_1["id"] as? String
{
url_list.append([url, url_1])
}
else
{
print("url error")
}
}
else
{
var url_2 = data_1["id"] as? String
url_list.append(["null", url_2!])
}
}
Where exactly would you deal with the asynchronous problem any suggestion?
You should note that all the API calls are asynchronous. You are doing a loop of these so, according to your code, you could have several API calls all happening simultaneously.
// Put this outside the loop
let spotify_url = "https://api.spotify.com/v1/tracks/"
for x in spotify_id {
let url_with_pars = spotify_url + x
// Be careful. The following could be nil
let myurl_1 = NSURL(string: url_with_pars)
// Watch out for ! in the following statement.
let request_1 = NSMutableURLRequest(URL: myurl_1!, cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 15.0)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request_1, completionHandler: { (data, response, error) in
// handle data here
// refresh table here.
})
task.resume()
}
// When your code gets here, the data may not have come back yet.
// The following WILL NOT WORK
print(artist.count) // Remove me
do_table_refresh() // Remove me
Here's the problem with this approach. The table will be refreshed each time one of the API calls comes back. If you had 10 API calls, that would be 10 refreshes.
Is there a reason you use NSDictionary? Parsing JSON returns [AnyObject] which you can cast as needed.
I am using a Master Detail Application. Master Screen is a Dashboard and on selecting an item, moves to the detailed screen where I trigger an Alamofire request in the backend
Below is the snippet
class APIManager: NSObject {
class var sharedManager: APIManager {
return _sharedManager
}
private var requests = [Request]()
// Cancel any ongoing download
func cancelRequests() {
if requests.count > 0 {
for request in requests {
request.cancel()
}
}
}
func getData(completion: (dataSet: [Data]?, error: NSError?) -> Void) {
let request = Alamofire.request(.GET, "http://request")
.response { (request, response, data, error) in
dispatch_async(dispatch_get_main_queue(), {
if(error == nil) {
if let response = data, data = (try? NSJSONSerialization.JSONObjectWithData(response, options: [])) as? [NSDictionary] {
var dataSet = [Data]()
for (_, dictionary) in data.enumerate() {
let lat = dictionary["Latitude"]
let lng = dictionary["Longitude"]
let id = dictionary["ID"] as! Int
let data = Data(lat: lat!, long: lng!, id: shuttleID)
dataSet.append(data)
}
completion(dataSet: dataSet, error: nil)
}
} else { completion(dataSet: nil, error: error) }
})
}
requests.append(request)
}
}
I have a singleton API manager class and from the detail view controller I call getData() function. Everything works fine.
But, when I push and pop repeatedly, I see rapid increase in the memory and after 10-15 attempts, I get memory warning. However in the AppDelegate I am managing it to show an Alert message and adding a delay timer for 8 seconds. But however after 20-25 attempts app crashes due to memory warning.
In viewWillDisappear(), I cancel any ongoing requests also. But I couldn't able to stop memory warning issue. I commented the part where I call the request, I see no issues, even memory consumption is less.
I welcome ideas.
The problem is you are never removing the requests that you append to the member variable 'requests'.
You will need to ensure to remove the request when you either cancel it or when the request completes successfully.
Do the following modifications-
func cancelRequests() {
if requests.count > 0 {
for request in requests {
request.cancel()
}
}
requests.removeAll() //Delete all canseled requests
}
also
func getData(completion: (dataSet: [Data]?, error: NSError?) -> Void) {
let request = Alamofire.request(.GET, "http://request")
.response { (request, response, data, error) in
dispatch_async(dispatch_get_main_queue(), {
if(error == nil) {
if let response = data, data = (try? NSJSONSerialization.JSONObjectWithData(response, options: [])) as? [NSDictionary] {
var dataSet = [Data]()
for (_, dictionary) in data.enumerate() {
let lat = dictionary["Latitude"]
let lng = dictionary["Longitude"]
let id = dictionary["ID"] as! Int
let data = Data(lat: lat!, long: lng!, id: shuttleID)
dataSet.append(data)
}
requests.removeObject(request)
completion(dataSet: dataSet, error: nil)
}
} else {
requests.removeObject(request)
completion(dataSet: nil, error: error) }
})
}
requests.append(request)
}
Add this Handy extension on Array to remove item to your code:
// Swift 2 Array Extension
extension Array where Element: Equatable {
mutating func removeObject(object: Element) {
if let index = self.indexOf(object) {
self.removeAtIndex(index)
}
}
mutating func removeObjectsInArray(array: [Element]) {
for object in array {
self.removeObject(object)
}
}
}
On analysis, I found that the memory warning was not due to the Alamofire request. It was due to MKMapView. Loading a MKMapView, zooming in and zooming out consumes more memory. So, in viewWillDisappear I did the fix.
override func viewWillDisappear(animated:Bool){
super.viewWillDisappear(animated)
self.applyMapViewMemoryFix()
}
func applyMapViewMemoryFix(){
switch (self.mapView.mapType) {
case MKMapType.Hybrid:
self.mapView.mapType = MKMapType.Standard
break;
case MKMapType.Standard:
self.mapView.mapType = MKMapType.Hybrid
break;
default:
break;
}
self.mapView.showsUserLocation = false
self.mapView.delegate = nil
self.mapView.removeFromSuperview()
self.mapView = nil
}
Courtesy - Stop iOS 7 MKMapView from leaking memory
In my app I have this class to get data from my server:
class Api{
func loadOffers(completion:(([Offers])-> Void), offer_id: String, offerStatus:String){
let myUrl = NSURL(string: "http://www.myServer.php/api/v1.0/offers.php")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let postString = "offer_id=\(offer_id)&offerStatus=\(dealStatus)&action=show"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{ data, response, error in
if error != nil {
println("error\(error)")
}else{
var err:NSError?
let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil)
if let dict = jsonObject as? [String: AnyObject] {
if let myOffers = dict["offers"] as? [AnyObject] {
var offers = [Offers]()
for offer in myOffers{
let offer = Offers(dictionary: offer as! NSDictionary)
offers.append(offer)
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0 )){
dispatch_async(dispatch_get_main_queue()){
completion(offers)
}
}
}
}
}
}
}
task.resume()
}
}
then in my View Controller I load the model:
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var offers: [Offers]!
func loadModel() {
let loadingNotification = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
loadingNotification.mode = MBProgressHUDMode.Indeterminate
loadingNotification.labelText = "updating your offers..."
offers = [Offers]()
let api = Api()
api.loadOffers(didLoadOffers , offer_id: dealIdent!, offerStatus: "open")
}
func didLoadOffers(offers:[Offers]){
self.offers = offers
self.tableView.reloadData()
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
self.refreshControl.endRefreshing()
}
override func viewWillAppear(animated: Bool) {
loadModel()
}
}
Everything works except that when the JSON dictionary is empty, meaning that there no offers the MBProgressHUD keep spinning.
I would like stop the activity indicator adding a subview instead which says that there are no offers. Any Suggestion would be greatly appreciated.
I tried:
if offers.isEmpty{
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
}
and also
if offers == 0 {
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
}
but it's not working
Thanks
This is happening because you set the HUD in main queue but are trying to remove from another one. All UI related changes should be done in main_queue()
Try using this code
dispatch_async(dispatch_get_main_queue(), {
// your code to modify HUD here
});
I recommend small redesign of code. I modified your original code a little. Here is API class:
class Api {
func loadOffers(offerID : String, offerStatus : String, completionHandler : ([Offer]?, NSError?) -> Void) {
let myURL = NSURL(string: "http://www.myServer.php/api/v1.0/offers.php")!
var request = NSMutableURLRequest(URL: myURL)
request.HTTPMethod = "POST"
let postString = "offer_id=\(offerID)&offerStatus=\(offerStatus)&action=show"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (result : NSData!, response : NSURLResponse!, error : NSError!) -> Void in
if let existingError = error {
NSLog("error \(existingError.code) - \(existingError.localizedDescription)")
completionHandler(nil, existingError)
} else {
var parseError : NSError?
if let dictionary = NSJSONSerialization.JSONObjectWithData(result, options: .allZeros, error: nil) as? [String : AnyObject] {
if let myOffers = dictionary["offers"] as? [NSDictionary] {
var parsedOffers = [] as [Offer]
for jsonOffer in myOffers {
parsedOffers.append(Offer(dictionary: jsonOffer))
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
completionHandler(parsedOffers, nil)
}
}
} else {
NSLog("JSON parsing failed")
parseError = NSError(domain: "MyApp", code: 1, userInfo : nil)
completionHandler(nil, parseError)
}
}
}).resume()
}
}
Change is added calling of completion handler even in case error with network communication and in case of failure of JSON parsing.
Corresponding implementation of VC follows:
class ViewController: UITableViewController {
private var _offers : [Offer]?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
loadModel()
}
private func loadModel() {
var hud = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
hud.mode = MBProgressHUDMode.Indeterminate
hud.labelText = "updating your offers"
Api().loadOffers("1", offerStatus: "1") { (offers : [Offer]?, error : NSError?) -> Void in
self._offers = offers
// TODO: Correct handling of error state ;)
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
hud.hide(true)
}
}
}
}
From my point of view is using closure better, but depends on your implementation. You can use your implementation with method instead of closure and just define hud variable on instance level.
I hope it helps you to solve your problem (I've tested on simulator and iPhone and works well with some testing stub framework).
if myOffers = nil cannot do complete(offers). so HUD could not stop. You can try this:
if let myOffers = dict["offers"] as? [AnyObject] {
var offers = [Offers]()
for offer in myOffers{
let offer = Offers(dictionary: offer as! NSDictionary)
offers.append(offer)
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0 )){
dispatch_async(dispatch_get_main_queue()){
completion(offers)
}
}
}
}
} else {
dispatch_async(dispatch_get_global_queue(priority, 0 )){
dispatch_async(dispatch_get_main_queue()){
completion([Offers]())
}
}
Every path in loadOffers needs to call the completion closure. If you have an if let statement, you need to consider the else case. If the optional is nil, you still need to call completion. You can pass an empty array in that case, or you can add an error parameter to your completion block so the view controller will know more information about why it's not getting anything back.