I have a function which sets the JSON results into an NSDictionary. It then uses this value to call a few other functions. I am using Alamofire and since I wrote this app in Swift 1, some this has changed which is giving me errors.
Here is the function:
func fetchApiData() {
// I believe this is the problem code below.
let _requestURL1 = Alamofire.request(.GET,dataSourceURL!)
_requestURL1.responseJSON { (_requestUrl, _requestResponse, _objJSON1, error) -> Void in
if(_objJSON1 != nil)
{
let jsonResult1 = _objJSON1 as NSDictionary;
self.checkIP(jsonResult1)
self.checkGeo(jsonResult1)
}
else{
return
}
}
let _requestURL2 = Alamofire.request(.GET,self.dataSourceURL2!)
_requestURL2.responseJSON { (_requestUrl, _requestResponse, _objJSON2, error) -> Void in
if(_objJSON2 != nil)
{
let jsonResult2 = _objJSON2 as NSDictionary;
self.checkDNS(jsonResult2)
NSTimer.scheduledTimerWithTimeInterval(self.refreshFrequencyInt, target: self, selector: Selector("fetchApiData"), userInfo: nil, repeats: false)
}
else{
return
}
}
}
Apparently now, this line:
_requestURL1.responseJSON { (_requestUrl, _requestResponse, _objJSON1, error) -> Void in
gives me this error:
'(_, _, _, _) -> Void' is not convertible to 'Response<AnyObject, NSError> -> Void'
I have tried the solutions for this issue but I can't seem to get the code working in the same way it was before.
Please help! :)
Try this:
Alamofire.request(.GET, url)
.responseJSON(completionHandler: {
(JSON) -> Void in
let JSONDictonary = JSON.result.value as! NSDictionary
let photoInfos = (JSONDictonary.valueForKey("photos") as! [NSDictionary]).filter({
($0["nsfw"] as! Bool) == false
}).map{
PhotoInfo(id: $0["id"] as! Int, url: $0["image_url"] as! String)
}
Related
Im trying to refactor dat fetching func to enable it for several Decodable struct types.
func fetchData<T: Decodable>(_ fetchRequest: FetchRequestType, completion: #escaping ((Result<T, Error>) -> Void)) {
guard !isFetching else { return }
isFetching = true
guard let url = getURL(fetchRequest) else { assertionFailure("Could not compose URL"); return }
let urlRequest = URLRequest(url: url)
self.session.dataTask(with: urlRequest) { [unowned self] (data, response, error) in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200 else {
self.isFetching = false
completion(.failure(NSError()))
return
}
guard let data = data else { assertionFailure("No data"); return }
if let jsonData = try? JSONDecoder().decode(T.self, from: data) {
self.isFetching = false
completion(.success(jsonData))
} else {
assertionFailure("Could not decode JSON data"); return
}
}.resume()
}
But when Im calling the func from controller with one of Decodable types I get a compile error
Generic parameter 'T' could not be inferred
networkClient.fetchData(.accountsSearch(searchLogin: text, pageNumber: 1)) { [unowned self] result in
switch result {
case .success(let dataJSON):
let accountsListJSON = dataJSON as! AccountsListJSON
let fetchedAccounts = accountsListJSON.items
.map({ AccountGeneral(login: $0.login, id: $0.id, avatarURLString: $0.avatarURL, type: $0.type) })
self.accounts = fetchedAccounts
case .failure(_):
assertionFailure("Fetching error!")
}
}
Please help me to find out what happened and solve a problem.
You can generally help the compiler to infer the T type by providing the result type, when you call fetchData(_:completion:) function like this:
networkClient.fetchData(
.accountsSearch(searchLogin: text, pageNumber: 1)
) { [unowned self] (result: Result<AccountsListJSON, Error>) in
...
}
If the method doesn't have a return type where the static type can be specified you have to add a parameter
func fetchData<T: Decodable>(_ fetchRequest: FetchRequestType, type: T.Type, completion: #escaping (Result<T, Error>) -> Void) { ...
and call it
networkClient.fetchData(.accountsSearch(searchLogin: text, pageNumber: 1), type: AccountsListJSON.self) { [unowned self] result in
and delete the downcast as! AccountsListJSON
I am using RxMoya for my networking calls and extending PremitiveSequence and Response so as to handle the error coming back. I declared a struct of Networking error which I could use to get all the error details and as such Pass the error message via the BaseResponse Model. Here is my NetwokingError struct
public struct NetworkingError: Error {
let httpResponse: HTTPURLResponse?
let networkData: Data?
let baseError: Error
}
For my coding, I have extended the primitive sequence as follows
public extension PrimitiveSequence where TraitType == SingleTrait,
ElementType == Response {
func mapObject<T: Codable>(_ type: T.Type, path: String? = nil) -> Single<T> {
return flatMap { response -> Single<T> in
return Single.just(try response.mapObject(type, path: path))
}
}
func mapArray<T: Codable>(_ type: T.Type, path: String? = nil) -> Single<[T]> {
return flatMap { response -> Single<[T]> in
return Single.just(try response.mapArray(type, path: path))
}
}
func filterSuccess() -> Single<E> {
return flatMap { (response) -> Single<E> in
if 200 ... 299 ~= response.statusCode {
return Single.just(response)
}
print("THIS ERROR JSON jsonObject2 xx mm \(response.data)")
do {
let jsonObject2 = try JSONSerialization.jsonObject(with: response.getJsonData(), options: .allowFragments)
print("THIS ERROR JSON jsonObject2 xx \(jsonObject2)")
let jsonObject = try JSONSerialization.jsonObject(with: response.getJsonData(), options: .allowFragments) as? NetworkingError
print("THIS ERROR JSON xx \(jsonObject)")
return Single.error(jsonObject ?? NetworkingError.self as! Error)
}
}
}
}
if I run this code here, The app crashes return Single.error(jsonObject ?? NetworkingError.self as! Error)
in my code, I am passing data like
func postVerifyApp(challenge: Int, identifier: String) -> Observable<AuthResponse> {
return provider.rx.request(.postVerifyApp(challenge: challenge, identifier: identifier))
.filterSuccess()
.mapObject(AuthResponse.self)
.asObservable()
.flatMap({ authResponse -> Observable<AuthResponse> in
return self.sendTokenToServer(authResponse)
})
}
then I am working with this in my presenter class like this
func postVerifyApp(challenge: Int, identifier: String) {
view?.setProgress(enabled: true)
source.postVerifyApp(challenge: challenge, identifier: identifier)
.retry(.delayed(maxCount: 2, time: 2.5), shouldRetry: networkRetryPredicate)
.asSingle()
.subscribe(onSuccess: { [weak self] response in
guard let presenter = self, let view = presenter.view else {return}
view.setProgress(enabled: false)
log(response, .json)
guard let data = response.data else {
return }
view.showVerifySuccess()
}, onError: { [weak self] error in
guard let presenter = self, let view = presenter.view else {return}
print("MESSAGE X \(error.localizedDescription)")
if let error = error as? NetworkingError {
print("MESSAGE X httpResponse \(error.httpResponse)")
}
view.setProgress(enabled: false)
}).disposed(by: disposeBag)
}
I want to be able to pass this Error and extract the error message and passing it to the console.
This is what my base Model looks like
struct ResponseBase<T: Codable>: Codable {
var error: Bool?
var message: String?
var data: T
var isSucessful: Bool {
return error == false
}
}
The expression used to construct the Single.error can not cast as Error. Firstly, you are trying to cast a jsonObject (a Dictionary) as Error. On the right hand, on the ifNull expression, you are trying to cast a metatype (Networking.Type) as an Error.
To solve your casting problem you can use this modified NetworkingError.
public struct NetworkingError: Error {
let httpResponse: HTTPURLResponse?
let networkData: Data?
let baseError: MoyaError
init(_ response:Response) {
self.baseError = MoyaError.statusCode(response)
self.httpResponse = response.response
self.networkData = response.data
}
func getLocalizedDescription() -> String {
return self.baseError.localizedDescription
}
}
Having this, modify the closure in the filterSuccess to create the NetworkingError object, passing it the Response, just like this:
func filterSuccess() -> Single<E> {
return flatMap {
(response) -> Single<E> in
if 200 ... 299 ~= response.statusCode {
return Single.just(response)
} else {
let netError = NetworkingError(response)
return Single.error(netError)
}
}
}
I encourage you to take a look at the MoyaError definition
I am having trouble to use the result of a completion handler.
I am getting this error "Cannot convert value of type '()' to expected argument type"
struct SearchCollectionViewModel {
let name: String
let previewURL: String?
var image:UIImage?
let dataController = DataController()
}
extension SearchCollectionViewModel {
init(with result: Result) {
self.name = result.trackName
self.previewURL = result.previewURL
if let url = result.previewURL {
let imgData = preview(with: url, completion: { data -> Data? in
guard let data = data as? Data else { return nil }
return data
})
self.image = UIImage(data: imgData)
}
}
private func preview(with url: String, completion: #escaping (Data) -> Data?) {
dataController.download(with: url) { data, error in
if error == nil {
guard let imageData = data else { return }
DispatchQueue.main.async {
_ = completion(imageData)
}
}
}
}
}
A couple of observations:
You cannot “return” a value that is retrieved asynchronously via escaping closure.
The closure definition (Data) -> Data? says that the closure not only will be passed the Data retrieved for the image, but that the closure will, itself, return something back to preview. But it’s obviously not doing that (hence the need for _, as in _ = completion(...)). I’d suggest you change that to (Data?) -> Void (or use the Result<T, U> pattern).
I’d suggest renaming your Result type as there’s a well-known generic called Result<Success, Failure> for returning .success(Success) or .failure(Failure). This is a pattern that we’ve used for a while, but is formally introduced in Swift 5, too. See SE-0235.
Your codebase can have its own Result type, but it’s an invitation for confusion later down the road if and when you start adopting this Result<T, U> convention.
You really shouldn’t be initiating asynchronous process from init, but instead invoke a method to do that.
Personally, I’d move the conversion to UIImage into the DataController, e.g.
extension DataController {
func downloadImage(with url: URL, completion: #escaping (UIImage?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, _, error in
let image = data.flatMap { UIImage(data: $0) }
completion(image, error)
}
task.resume()
}
}
So, I might suggest you end up with something like:
class SearchCollectionViewModel {
let name: String
let previewURL: String?
let dataController = DataController()
var image: UIImage?
init(with result: Result) {
self.name = result.trackName
self.previewURL = result.previewURL
}
}
extension SearchCollectionViewModel {
func preview(with url: String, completion: #escaping (UIImage?) -> Void) {
guard let urlString = previewURL, let url = URL(string: urlString) else {
completion(nil)
return
}
dataController.downloadImage(with: url) { [weak self] image, error in
DispatchQueue.main.async {
self?.image = image
completion(image)
}
}
}
}
I got a getJSON Function with url parameter:
func getJsons(jsonUrl: String) {
guard let url = URL(string: jsonUrl) else { return }
URLSession.shared.dataTask(with: url:) { (data, response, err) in
if err != nil {
print("ERROR: \(err!.localizedDescription)")
let alertController = UIAlertController(title: "Error", message:
err!.localizedDescription, preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.default,handler: nil))
var topController:UIViewController = UIApplication.shared.keyWindow!.rootViewController!
while ((topController.presentedViewController) != nil) {
topController = topController.presentedViewController!;
}
topController.present(alertController, animated: true, completion: nil)
}
guard let data = data else { return }
do {
let test = try JSONDecoder().decode([ArticleStruct].self, from: data)
DispatchQueue.main.async {
self.myArticles = test
print(self.myArticles?.count ?? 0 )
self.myTableView.reloadData()
}
} catch let jsonErr {
print("Error:", jsonErr)
}
}.resume()
}
now i want to move the function to another class (network class).
what must I do to add a completionHandler to the function and how do I call it from other classes.
i want to return the json to the caller class.
My plan:
in MainActivity -> viewDidLoad: call network completionHandler(getJsons(192.168.178.100/getPicture.php))
on completion -> myJsonDataMainActivity = (json data from completionHandler)
-> MainActivity.TableView.reload
in otherClass -> call network completionHandler(getJsons(192.168.178.100/getData.php))
on completion -> myJsonDataOtherClass = (json data from completionHandler)
-> otherClass.TableView.reload
Thanks for your help!
You can use delegate.
myJsonDataOtherClass:
protocol NetworkDelegate {
func didFinish(result: Data)
}
class myJsonDataOtherClass {
var delegate: NetworkDelegate? = nil
...
func getJsons(jsonUrl: String) {
...
URLSession.shared.dataTask(with: url:) { (data, response, err) in
...
delegate?.didFinish(data)
}.resume()
}
}
and set delegate at MainActivity
class MainActivity: UIViewController, NetworkDelegate{
...
let jsonClass = myJsonDataOtherClass()
jsonClass.delegate = self
jsonClass.getJsons(jsonUrl:url)
func didFinish(result:Data) {
// process data
}
}
You should add a completion handler in your function and pass the JSON object.
func getJsons(jsonUrl: String, completion:#escaping (_ success: Bool,_ json: [String: Any]?) -> Void) {
...
completion(true, json)
}
I'm experiencing an issue with a function that should return an encrypted file from dropbox, but is instead returning the empty dictionary I initialized to receive the data.
I'm almost certain it's a race condition issue since an async call has to be made to the Dropbox API, but so far I have been unable to resolve the issue using GCD. Any help would be most appreciated:
func loadDropboxAccounts() {
let retreiveDataGroup = dispatch_group_create()
var dictionary = [String:String]()
dispatch_group_enter(retreiveDataGroup)
if DropboxClientsManager.authorizedClient() == nil {
DropboxClientsManager.authorizeFromController(UIApplication.sharedApplication(), controller: self, openURL: {(url: NSURL) -> Void in
UIApplication.sharedApplication().openURL(url)
}, browserAuth: true)
}
if let client = DropboxClientsManager.authorizedClient() {
client.filesRoutes.downloadData("/example/example.txt").response({(result: AnyObject?, routeError: AnyObject?, error: DBError?, fileContents: NSData) -> Void in
if (fileContents.length != 0) {
let cipherTextData = fileContents
let plainTextByteArray = CryptoHelper.accountDecrypt(cipherTextData, fileName: "vault")
let plainTextString = plainTextByteArray.reduce("") { $0 + String(UnicodeScalar($1)) }
let plainTextData = dataFromByteArray(plainTextByteArray)
do {
try dictionary = NSJSONSerialization.JSONObjectWithData(plainTextData, options: []) as! [String:String]
for (key, value) in dictionary {
let t = dictionary[key]
print(t)
}
} catch let error as NSError{
print("loadAccountInfo:", error)
}
} else {
print("\(routeError)\n\(error)\n")
}
}).progress({(bytesDownloaded: Int64, totalBytesDownloaded: Int64, totalBytesExpectedToDownload: Int64) -> Void in
print("\(bytesDownloaded)\n\(totalBytesDownloaded)\n\(totalBytesExpectedToDownload)\n")
})
}
dispatch_group_notify(retreiveDataGroup, dispatch_get_main_queue()) {
return dictionary
}
}
Just for reference, this the pod that I am using in the project:
https://github.com/dropbox/dropbox-sdk-obj-c