Swift - Grand Central Dispatch Class - swift

I work on a central point(class), where my data is requested and retrieved.
This class can be called from any view:
override func viewDidLoad() {
super.viewDidLoad()
let group = dispatch_group_create()
var data = RestApiManager(group)
dispatch_group_notify(group, dispatch_get_main_queue()) {
return data
}
}
class RestApiManager {
let user = "user"
let password = "password"
var result: JSON = []
init(group) {
dispatch_group_enter(group)
Alamofire.request(.GET, "http://example.com/api/")
.authenticate(user: user, password: password)
.responseJSON { _, _, result in
switch result {
case .Success(let data):
self.result[] = JSON(data)
case .Failure(_, let error):
print("Request failed with error: \(error)")
}
dispatch_group_leave(group)
}
}
}
But it already fails when initializing the class.
Anybody could help me to design this a proper way??
Thanks and Greetings

import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
import Foundation
class C {
var txt: String = "start"
init(group: dispatch_group_t) {
dispatch_group_enter(group)
print(txt)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
usleep(200000)
self.txt = "end"
print(self.txt)
dispatch_group_leave(group)
}
}
}
func f() {
let group = dispatch_group_create()
let c = C(group: group)
dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
print("notify: \(c) created")
}
}
f()
print("continue running ...")
This works for me without any trouble. You wrote
"But it already fails when initializing the class."
What does it means exactly? Explain your 'failure' in details ...
By the way, I don't understand how do you return your data (return data) from group notification handler ... (generally from any asynchronous code)

Related

Error: No exact matches in call to instance method 'append'

Im trying to implement a stock API but get an error with ".append":
No exact matches in call to instance method 'append'.
I'm not familiar with it and need help to solve this problem
final class StockQuoteManager: QuoteManagerProtocol, ObservableObject {
#Published var quotes: [Quote] = []
func download(stocks: [String], completion: #escaping (Result<[Quote], NetworkError>) -> Void) {
var internalQuotes = [Quote]()
let downloadQueue = DispatchQueue(label: "com.app.dwonloadQueue")
let downloadGroup = DispatchGroup()
stocks.forEach { (stock) in
downloadGroup.enter()
let url = URL(string: API.quoteUrl(for: stock))!
NetworkManager<GlobalQuoteResponse>().fetch(from: url) { (result) in
switch result {
case .failure(let err):
print(err)
downloadQueue.async {
downloadGroup.leave()
}
case .success(let resp):
downloadQueue.async {
internalQuotes.append(resp.quote) // <-- ERROR
downloadGroup.leave()
}
}
}
}
downloadGroup.notify(queue: DispatchQueue.global()) {
completion(.success(internalQuotes))
DispatchQueue.main.async {
self.quotes.append(contentsOf: internalQuotes)
}
}
}
}

Show error response in alert RxSwift using Driver

How to get an error response with driver so I can show it in alert. When I see the trait driver is can't error out, so should I use subject or behaviourRelay to get error response when I subscribe. Actually I like how to use driver but I don't know how to passed error response using driver.
this is my network service
func getMovies(page: Int) -> Observable<[MovieItem]> {
return Observable.create { observer -> Disposable in
self.service.request(endpoint: .discover(page: page)) { data, response, error in
if let _ = error {
observer.onError(MDBError.unableToComplete)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
observer.onError(MDBError.invalidResponse)
return
}
guard let data = data else {
observer.onError(MDBError.invalidData)
return
}
if let decode = self.decode(jsonData: MovieResults.self, from: data) {
observer.onNext(decode.results)
}
observer.onCompleted()
}
return Disposables.create()
}
}
This is my viewModel
protocol ViewModelType {
associatedtype Input
associatedtype Output
func transform(input: Input) -> Output
}
class PopularViewModel: ViewModelType {
struct Input {
let viewDidLoad: Driver<Void>
}
struct Output {
let loading: Driver<Bool>
let movies: Driver<[MovieItem]>
}
private let service: NetworkDataFetcher
init(service: NetworkDataFetcher = NetworkDataFetcher(service: NetworkService())) {
self.service = service
}
func transform(input: Input) -> Output {
let loading = ActivityIndicator()
let movies = input.viewDidLoad
.flatMap { _ in
self.service.getMovies(page: 1)
.trackActivity(loading)
.asDriver(onErrorJustReturn: [])
}
let errorResponse = movies
return Output(loading: loading.asDriver(),movies: movies)
}
}
this is how I bind the viewModel in viewController
let input = PopularViewModel.Input(viewDidLoad: rx.viewDidLoad.asDriver())
let output = viewModel.transform(input: input)
output.movies.drive { [weak self] movies in
guard let self = self else { return }
self.populars = movies
self.updateData(on: movies)
}.disposed(by: disposeBag)
output.loading
.drive(UIApplication.shared.rx.isNetworkActivityIndicatorVisible)
.disposed(by: disposeBag)
You do this the same way you handled the ActivityIndicator...
The ErrorRouter type below can be found here.
This is such a common pattern that I have created an API class that takes care of this automatically.
class PopularViewModel: ViewModelType {
struct Input {
let viewDidLoad: Driver<Void>
}
struct Output {
let loading: Driver<Bool>
let movies: Driver<[MovieItem]>
let displayAlertMessage: Driver<String>
}
private let service: NetworkDataFetcher
init(service: NetworkDataFetcher = NetworkDataFetcher(service: NetworkService())) {
self.service = service
}
func transform(input: Input) -> Output {
let loading = ActivityIndicator()
let errorRouter = ErrorRouter()
let movies = input.viewDidLoad
.flatMap { [service] in
service.getMovies(page: 1)
.trackActivity(loading)
.rerouteError(errorRouter)
.asDriver(onErrorRecover: { _ in fatalError() })
}
let displayAlertMessage = errorRouter.error
.map { $0.localizedDescription }
.asDriver(onErrorRecover: { _ in fatalError() })
return Output(
loading: loading.isActive.asDriver(onErrorRecover: { _ in fatalError() }),
movies: movies,
displayAlertMessage: displayAlertMessage
)
}
}

Execute func after first func

self.werteEintragen() should start after weatherManager.linkZusammenfuegen() is done. Right now I use DispatchQueue and let it wait two seconds. I cannot get it done with completion func because I dont know where to put the completion function.
This is my first Swift file:
struct DatenHolen {
let fussballUrl = "deleted="
func linkZusammenfuegen () {
let urlString = fussballUrl + String(Bundesliga1.number)
perfromRequest(urlString: urlString)
}
func perfromRequest(urlString: String)
{
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (gettingInfo, response, error) in
if error != nil{
print(error!)
return
}
if let safeFile = gettingInfo {
self.parseJSON(datenEintragen: safeFile)
}
}
task.resume()
}
}
func parseJSON(datenEintragen: Data) {
let decoder = JSONDecoder()
do {
let decodedFile = try decoder.decode(JsonDaten.self, from: datenEintragen)
TeamOne = decodedFile.data[0].home_name
} catch {
print(error)
}
}
}
And this is my second Swift File as Viewcontroller.
class HauptBildschirm: UIViewController {
func werteEintragen() {
Tone.text = TeamOne
}
override func viewDidLoad() {
super.viewDidLoad()
weatherManager.linkZusammenfuegen()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [unowned self] in
self.werteEintragen()
}
}
}
How can I implement this and where?
func firstTask(completion: (_ success: Bool) -> Void) {
// Do something
// Call completion, when finished, success or faliure
completion(true)
}
firstTask { (success) in
if success {
// do second task if success
secondTask()
}
}
You can have a completion handler which will notify when a function finishes, also you could pass any value through it. In your case, you need to know when a function finishes successfully.
Here is how you can do it:
func linkZusammenfuegen (completion: #escaping (_ successful: Bool) -> ()) {
let urlString = fussballUrl + String(Bundesliga1.number)
perfromRequest(urlString: urlString, completion: completion)
}
func perfromRequest(urlString: String, completion: #escaping (_ successful: Bool) -> ()) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (gettingInfo, response, error) in
guard error == nil else {
print("Error: ", error!)
completion(false)
return
}
guard let safeFile = gettingInfo else {
print("Error: Getting Info is nil")
completion(false)
return
}
self.parseJSON(datenEintragen: safeFile)
completion(true)
}
task.resume()
} else {
//can't create URL
completion(false)
}
}
Now, in your second view controller, call this func like this:
override func viewDidLoad() {
super.viewDidLoad()
weatherManager.linkZusammenfuegen { [weak self] successful in
guard let self = self else { return }
DispatchQueue.main.async {
if successful {
self.werteEintragen()
} else {
//do something else
}
}
}
}
I highly recommend Google's Promises Framework:
https://github.com/google/promises/blob/master/g3doc/index.md
It is well explained and documented. The basic concept works like this:
import Foundation
import Promises
struct DataFromServer {
var name: String
//.. and more data fields
}
func fetchDataFromServer() -> Promise <DataFromServer> {
return Promise { fulfill, reject in
//Perform work
//This block will be executed asynchronously
//call fulfill() if your value is ready
//call reject() if an error occurred
fulfill(data)
}
}
func visualizeData(data: DataFromServer) {
// do something with data
}
func start() {
fetchDataFromServer
.then { dataFromServer in
visualizeData(data: dataFromServer)
}
}
The closure after "then" will always be executed after the previous Promise has been resolved, making it easy to fulfill asynchronous tasks in order.
This is especially helpful to avoid nested closures (pyramid of death), as you can chain promises instead.

Alamofire number of requests one after another

I have a number of requests witch I would like to call one after another without having nested spaghetti code.
I tried it already with a serial dispatch queue
let queue = dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL)
Alamofire.request(Router.Countries).responseString { (response:Response<String, NSError>) in
print(1)
}
Alamofire.request(Router.Countries).responseString { (response:Response<String, NSError>) in
print(2)
}
Alamofire.request(Router.Countries).responseString { (response:Response<String, NSError>) in
print(3)
}
But unfortunately that does not work. The output of this can be 1,3,2 or 3,1,2 or any other combination.
What would be the best approach to get the output 1,2,3 so one after the other.
Ok I ended up writing my own implementation.
I created a class RequestChain wich takes Alamofire.Request as parameter
class RequestChain {
typealias CompletionHandler = (success:Bool, errorResult:ErrorResult?) -> Void
struct ErrorResult {
let request:Request?
let error:ErrorType?
}
private var requests:[Request] = []
init(requests:[Request]) {
self.requests = requests
}
func start(completionHandler:CompletionHandler) {
if let request = requests.first {
request.response(completionHandler: { (_, _, _, error) in
if error != nil {
completionHandler(success: false, errorResult: ErrorResult(request: request, error: error))
return
}
self.requests.removeFirst()
self.start(completionHandler)
})
request.resume()
}else {
completionHandler(success: true, errorResult: nil)
return
}
}
}
And I use it like this
let r1 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("1")
}
let r2 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("2")
}
let r3 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("3")
}
let chain = RequestChain(requests: [r1,r2,r3])
chain.start { (success, errorResult) in
if success {
print("all have been success")
}else {
print("failed with error \(errorResult?.error) for request \(errorResult?.request)")
}
}
Importent is that you are telling the Manager to not execute the request immediately
let manager = Manager.sharedInstance
manager.startRequestsImmediately = false
Hope it will help someone else
I'm using Artman's Signals to notify my app once a result is returned, after which a queue elsewhere can call it's next request:
Alamofire.request( httpMethod, url, parameters: params ).responseJSON
{
( response: Response< AnyObject, NSError > ) in
self._signals.dispatchSignalFor( Key: url, data: response.result )
}
More details here.
One solution is to call your second request in the first one's callback :
Alamofire.request(Router.Countries).responseString { (response:Response<String, NSError>) in
print(1)
Alamofire.request(Router.Countries).responseString { (response:Response<String, NSError>) in
print(2)
Alamofire.request(Router.Countries).responseString { (response:Response<String, NSError>) in
print(3)
}
}
}

Return values from completion handler

I want to return the values from an api call.
The call to my api class (I want to get the values in res):
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let t = Api_test();
let res = t.getSomething();
print(res)
}
}
The api class:
import Foundation
class Api_test {
func getAllStations(completionHandler: (response : XMLIndexer) -> ()) {
getRequest { result in
completionHandler(response: SWXMLHash.parse(result))
};
}
func getRequest(completionHandler: (result: NSData) -> ()) {
let baseUrl = "http://api.test.com"
let request = NSMutableURLRequest(URL: NSURL(string: baseUrl)!)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "GET"
let task = session.dataTaskWithRequest(request) {
(data, response, error) in
if data == nil {
print("dataTaskWithRequest error: \(error)")
return
} else {
completionHandler(result: data!)
}
}
task.resume()
}
}
Everything works as thought, but I'm stuck at the point to return the values back to the getSomething function. The data is in xml format. How can I get the result set as returned values in the res (viewDidLoad)?
NSURLSession is a fully asynchronous networking API so ideally your view controller should operate correctly and not wait for the data to be returned from the network.
You have three options here:
You can pass a completion block to getSomething and have it pass the result to the block:
func getSomething(completionHandler: (result: XMLIndexer) -> ()) {
getRequest { result in
completionHandler(result: SWXMLHash.parse(result))
}
}
override func viewDidLoad() {
...
t.getSomething { res in
print(res)
}
}
If you desperately need the XML data in hand before view is displayed onto screen, you can make the main thread wait till network operation finishes executing. You can use dispatch_semaphore_t:
func getSomething() -> XMLIndexer? {
var xml: XMLIndexer? = nil
let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)
getRequest { result in
xml = SWXMLHash.parse(result)
dispatch_semaphore_signal(semaphore)
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
return xml
}
Last option is, you can use another 3rd party that does the parsing synchronously. There is a great one named Ono:
var error: NSError?
let xml = ONOXMLDocument(data: result, error: &error)