Home > Software engineering >  How to receive an output for a method from a closure in Swift?
How to receive an output for a method from a closure in Swift?

Time:12-04

How to receive an output of an array of [TweetSentimentClassifierInput] objects to send it further to my prediction model?

I have the array but inside a closure which turns it unavailable to return as a method output. If I initialize an empty array outside the closure then the output is always an empty array since fetch closure takes time to be completed.

Code

struct TweetFetcher {

   let tweetCount = 100
   let swifter = Swifter(consumerKey: key, consumerSecret: secret)

func fetchTweets(with searchText: String) -> [TweetSentimentClassifierInput] {
    
    swifter.searchTweet(using: searchText, lang: "en", count: tweetCount, tweetMode: .extended) {(results, searchMetadata) in
        var tweets = [TweetSentimentClassifierInput]()
        let data = results.description.data(using: .utf8)

        do {
            let decodedData = try JSONDecoder().decode([TweetData].self, from: data!)
        } catch {
            print("Error with decoding, \(error)")
        }

        for tweet in decodedData {
            let tweetForClassification = TweetSentimentClassifierInput(text: tweet.full_text)
            tweets.append(tweetForClassification)
        }
    } failure: { (error) in
        print("Error with the Twitter API request, \(error)")
    }
}
}

How can I return a non-empty array from a closure as a method output?

CodePudding user response:

You should use a completionHandler concept to achieve async operations like this:

struct TweetFetcher {
    let tweetCount = 100
    let swifter = Swifter(consumerKey: key, consumerSecret: secret)
    
    func fetchTweets(with searchText: String, completion: @escaping ([TweetSentimentClassifierInput]?, Error?) -> Void) {
        
        swifter.searchTweet(using: searchText, lang: "en", count: tweetCount, tweetMode: .extended) {(results, searchMetadata) in
            var tweets = [TweetSentimentClassifierInput]()
            let data = results.description.data(using: .utf8)
            
            do {
                let decodedData = try JSONDecoder().decode([TweetData].self, from: data!)
            } catch {
                print("Error with decoding, \(error)")
                completion(nil, error)
            }
            
            for tweet in decodedData {
                let tweetForClassification = TweetSentimentClassifierInput(text: tweet.full_text)
                tweets.append(tweetForClassification)
            }
            completion(tweets, nil)
        } failure: { (error) in
            print("Error with the Twitter API request, \(error)")
            completion(nil, error)
        }
    }
}

Usage

let fetcher = TweetFetcher()
fetcher.fetchTweets(with: "Keyword...") { tweets, error in
    if let error = error {
        print(error.localizedDescription)
    } else {
        // Use tweets array content here ...
    }
}

CodePudding user response:

Convert this method in async, passing closure with [TweetSentimentClassifierInput] as closure argument, and an error as secondary closure argument,

func fetchTweets(with searchText: String, finished: ((_ sentiments: [TweetSentimentClassifierInput]?,_ error: Error?) -> Void)) {

    swifter.searchTweet(using: searchText, lang: "en", count: tweetCount, tweetMode: .extended) {(results, searchMetadata) in
        var tweets = [TweetSentimentClassifierInput]()
        let data = results.description.data(using: .utf8)

        do {
           let decodedData = try JSONDecoder().decode([TweetData].self, from: data!)
           } catch {
           print("Error with decoding, \(error)")
        }

        for tweet in decodedData {
            let tweetForClassification = TweetSentimentClassifierInput(text: tweet.full_text)
            tweets.append(tweetForClassification)
        }
        finished(tweets, nil)
    } failure: { (error) in
        print("Error with the Twitter API request, \(error)")
        finished(nil, error)
    }
}

CodePudding user response:

You need a completion handler.

The modern Result type which can contain both the good data and an error provides a convenience initializer to catch the DecodingError implicitly

struct TweetFetcher {
    
    let tweetCount = 100
    let swifter = Swifter(consumerKey: key, consumerSecret: secret)
    
    func fetchTweets(with searchText: String, completion: @escaping (Result<[TweetSentimentClassifierInput], Error>) -> Void) {
        
        swifter.searchTweet(using: searchText, lang: "en", count: tweetCount, tweetMode: .extended) {(results, searchMetadata) in
            completion( Result {
                try JSONDecoder().decode([TweetData].self, from: Data(results.description.utf8))
                    .map{TweetSentimentClassifierInput(text: $0.full_text)}
            })
        } failure: { completion(.failure($0)) }
    }
}

And call it

fetchTweets(with: "Foo") { result in
    switch result {
        case .success(let input): print(input)
        case .failure(let error): print(error)
    }
}
  • Related