0

I'm using the Twitter API to retrieve a list of tweets. The method should return the list of tweets, but the problem is that it returns before the request is over, which results in an empty list being returned. How do I fix this? Here's the source code of the method:

// Call to search through twitter with a query
func searchQuery(query: String) -> [Tweet] {
    var tweets: [Tweet] = []
    var query_URL = query.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
    if let query_URL = query_URL {
        TwitterClient.sharedInstance.GET("https://api.twitter.com/1.1/search/tweets.json?q=\(query_URL)", parameters: nil, success: { (operation: AFHTTPRequestOperation!, response: AnyObject!) -> Void in
            tweets = parseJSON(response)
            println(tweets.count)
            }, failure: { (operation: AFHTTPRequestOperation!, error: NSError!) -> Void in

        })
    }
    println("ret: \(tweets.count)")
    return tweets
}

In the above code, the output would be

ret: 0
15

I've tried using dispatch groups, but I couldn't get them to work. Here's what I did with GCD:

// Call to search through twitter with a query
func searchQuery(query: String) -> [Tweet] {
    var tweets: [Tweet] = []
    var query_URL = query.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
    var group = dispatch_group_create()
    if let query_URL = query_URL {
        dispatch_group_enter(group)
        TwitterClient.sharedInstance.GET("https://api.twitter.com/1.1/search/tweets.json?q=\(query_URL)", parameters: nil, success: { (operation: AFHTTPRequestOperation!, response: AnyObject!) -> Void in
            tweets = parseJSON(response)
            dispatch_group_leave(group)
            }, failure: { (operation: AFHTTPRequestOperation!, error: NSError!) -> Void in
                dispatch_group_leave(group)
        })
    }

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
    return tweets
}

But, that code seems to wait forever.

For reference, Tweet is a struct for saving data related to a tweet. I use it to move around lots of data in a more compact way. parseJSON populates and returns an array of tweets based on the JSON response. Ideally, that returned array would be saved to tweets, which should then return from the method, but that doesn't happen.

Any ideas or techniques to overcome this will be much appreciated!

Edit: @Hamza Ansari Here's the actual function:

// Call to search through twitter with a query
func searchQuery(query: String, completionHandler:(returntweets: [Tweet]) -> Void) {
    var query_URL = query.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
    if let query_URL = query_URL {
        TwitterClient.sharedInstance.GET("https://api.twitter.com/1.1/search/tweets.json?q=\(query_URL)", parameters: nil, success: { (operation: AFHTTPRequestOperation!, response: AnyObject!) -> Void in
            var tweets:[Tweet] =  parseJSON(response)
            println("size in method: \(tweets.count)")
            completionHandler(returntweets: tweets)

            }, failure: { (operation: AFHTTPRequestOperation!, error: NSError!) -> Void in

        })
    }
}

But, when called in a different method used to initialize the data source:

// Initializes the data source
func initialize(query: String) {
    self.query = query
    searchQuery(query, { (returnTweets) -> Void in
        self.searches = returnTweets
        })

    println("size when called: \(searches.count)")
}

The output (in order):

size when called: 0
size in method: 15
Naldhelaan
  • 390
  • 4
  • 13
  • look at the similar question asked yesterday : http://stackoverflow.com/questions/31794542/ios-swift-function-that-returns-asynchronously-retrieved-value – vadian Aug 04 '15 at 09:36
  • Here is a hint: The `GET` method that you're using for fetching tweets does just this (async fetch of some data), you could cmd+click on it and get an idea of how it works. Also note that async functions (almost) never return something (that's the idea behind asynchronicity after all) – Alladinian Aug 04 '15 at 09:41
  • You need to invoke some method from within the closure to process the tweets that have been retrieved. – Paulw11 Aug 04 '15 at 09:52
  • its because you are using `println("size when called: \(searches.count)")` outside block – Hamza Ansari Aug 04 '15 at 12:44
  • `searchQuery(query, { (returnTweets) -> Void in self.searches = returnTweets println("size when called: \(searches.count)" })` – Hamza Ansari Aug 04 '15 at 12:44

2 Answers2

2

This is by design; this is an "asynchronous" method (ie, it goes off and does some work, and calls your supplied "success" code later when it's done). The reason it's designed that way is so your main thread won't be blocked waiting for the network, which can take an indeterminate amount of time.

This requires you to design the code around it accordingly-- you can't really have a method that does what you want (you call it, it returns with results ready). Well, you can (by queuing the async call on a background thread and then waiting for it) but then you're stuck waiting for the network which you don't want to do. Instead you should change your own method searchQuery to do similarly (be asynchronous) and its callers should handle that as required.

That pattern is both acceptable and good when asynchronous work needs to be done and it's not as onerous as it initially seems once you get used to it.

(See @HamzaAnsari's answer for how you could do this.)

Ben Zotto
  • 70,108
  • 23
  • 141
  • 204
  • Yes, essentially, everything you want to happen after the request is completed needs to go in the 'success' block or be called from it. The function should just return straight after the `GET` call. – JeremyP Aug 04 '15 at 10:06
  • Alright, thanks Ben. I understand the concept, but the code is a mystery. Are there any resources where I can read more about this design pattern? – Naldhelaan Aug 04 '15 at 12:00
0

Define function with completion Handler:

func searchQuery(query: String, completionHandler:(returntweets: [Tweet]) -> Void) {
    var query_URL = query.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
    if let query_URL = query_URL {
      TwitterClient.sharedInstance.GET("https://api.twitter.com/1.1/search/tweets.json?q=\(query_URL)", parameters: nil, success: { (operation: AFHTTPRequestOperation!, response: AnyObject!) -> Void in
        var tweets:[Tweet] =  parseJSON(response)
        println(tweets.count)
        completionHandler(returntweets: tweets)

        }, failure: { (operation: AFHTTPRequestOperation!, error: NSError!) -> Void in

      })
    }
  }

Now call it where you required tweets:

searchQuery(query: "youQuery",{ (returntweets) -> Void in
      //Do something 
     println(returntweets.count)
    }
Hamza Ansari
  • 3,009
  • 1
  • 23
  • 25
  • So, when you call it, should `returntweets` hold the array of Tweets? When I try it in code, it returns an array of size 0. – Naldhelaan Aug 04 '15 at 12:08
  • have you checked `parseJSON(response)` is it returnig Tweets? – Hamza Ansari Aug 04 '15 at 12:41
  • I've updated the question, check above. `parseJSON(response)` gets called right before a print statement, and the printed array is populated, so the problem shouldn't be there. – Naldhelaan Aug 04 '15 at 12:42