Work with escaping closure value - swift

Have this to fill my full fullJsonData array
and i want to work with value after closure.
func getNewsAsDic(completionHandler: #escaping ([fullJsonData]) -> [fullJsonData]) {
let session = URLSession.shared
let dataTask = session.dataTask(with: requestNewsForToday()) { data, _, error in
if error == nil {
if let dic = try? JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
var dataArray = [fullJsonData]()
let jdata = dic?["articles"] as! [[String:Any]]
for item in jdata {
let data = fullJsonData(author: item["author"] as? String,
description: item["description"] as? String,
publishedAt: item["publishedAt"] as? String,
title: item["title"] as? String,
url: item["url"] as! String,
imageURL: item["urlToImage"] as? String)
print(item["author"] as! String)
print(item["description"] as! String)
print(item["title"] as! String)
print(item["urlToImage"] as! String)
print(item["url"] as! String)
print(item["urlToImage"] as! String)
print(item["publishedAt"] as! String)
dataArray.append(data)
}
completionHandler(dataArray)
print(jdata.count)
}
} else {
return
}
}
dataTask.resume()
}
So problem is:
1)The xcode error, what Result of call is unused, but produces '[fullJsonData]'
2)Then I am trying to use it in project like this:
var dataArray = [fullJsonData]()
dataArray = NetworkManager.shared.getNewsAsDic(completionHandler: { dict in
return dict
})
Had an error too: Cannot assign value of type '()' to type '[fullJsonData]'
So is this real to take and use values from closure?

You should change the signature of your completion handler from:
func getNewsAsDic(completionHandler: #escaping ([fullJsonData]) -> [fullJsonData]) {
to:
func getNewsAsDic(completionHandler: #escaping ([fullJsonData]) -> Void) {
And then change your call to:
NetworkManager.shared.getNewsAsDic(completionHandler: { myArray in
// Do whatever you want with myArray here
})
Also, you should rename getNewsAsDic because you're really returning an array.

it seems you have to use
var dataArray: [fullJsonData] = [] instead of your code.

Related

Saving JSON into Realm

For my project I want to save data I'm getting from an API in realm and then display it in a table view.
The JSON will look like this:
{"books":[{"author":"Chinua Achebe", "title":"Things Fall Apart","imageLink":"http://books.google.com/books/content?id=plk_nwEACAAJ&printsec=frontcover&img=1&zoom=5&source=gbs_api"}]}
I've tried a few different things, but I can't figure out how to decode and store the JSON properly. I have used this function before for parsing the JSON, but when I add the realm Code I'm getting errors.
My function for fetching the JSON is:
func fetchArticle(){
let urlRequest = URLRequest(url: URL(string: "https:/mocki.io/v1/89aa9fe9-fdba-463f-99b3-5d8b6bc1d32e")!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if error != nil {
print(error)
return
}
self.books = [Books]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
if let booksFromJson = json["books"] as? [[String : AnyObject]]{
for bookFromJson in booksFromJson {
let book = Books()
if let title = bookFromJson["title"] as? String, let author = bookFromJson["author"] as? String, let imageLink = bookFromJson["imageLink"] as? String {
book.author = author
book.title = title
book.imageLink = imageLink
}
self.books?.append(book)
let realm = try! Realm()
for books in bookFromJson {
try! realm.write {
realm.add(books, update: .all)
}
}
DispatchQueue.main.async {
self.tableview.reloadData()
}
} catch let error {
print(error)
}
}
task.resume()
}
}
}
This is my Struct:
class Books: Object, Decodable {
#objc dynamic var author: String?
#objc dynamic var imageLink: String?
#objc dynamic var title: String?
convenience init(author: String, imageLink: String, title: String) {
self.init()
self.author = author
self.imageLink = imageLink
self.title = title
}
override static func primaryKey() -> String? {
return "author"
}
private enum CodingKeys: String, CodingKey {
case author
case imageLink
case title
}
}
This are the errors im getting in the func:
Invalid conversion from throwing function of type '(Data?, URLResponse?, Error?) throws -> Void' to non-throwing function type '(Data?, URLResponse?, Error?) -> Void'
'let' declarations cannot be computed properties
There are probably a 100 different ways to map your json to a realm object but let's keep it simple. First I assume your incoming json may be several books so it would look like this
let jsonStringWithKey = """
{
"books":
[{
"author":"Chinua Achebe",
"title":"Things Fall Apart",
"imageLink":"someLink"
},
{
"author":"another author",
"title":"book title",
"imageLink":"another link"
}]
}
"""
So encode it as data
guard let jsonDataWithKey = jsonStringWithKey.data(using: .utf8) else { return }
Then, using JSONSerialization, map it to an array. Keeping in mind the top level object is "books" and AnyObject will be all of the child data
do {
if let json = try JSONSerialization.jsonObject(with: jsonDataWithKey) as? [String: AnyObject] {
if let bookArray = json["books"] as? [[String:AnyObject]] {
for eachBook in bookArray {
let book = Book(withBookDict: eachBook)
try! realm.write {
realm.add(book)
}
}
}
}
} catch {
print("Error deserializing JSON: \(error)")
}
and the Realm object is
class Book: Object, Codable {
#objc dynamic var author = ""
#objc dynamic var title = ""
#objc dynamic var imageLink = ""
convenience init(withBookDict: [String: Any]) {
self.init()
self.author = withBookDict["author"] as? String ?? "No Author"
self.title = withBookDict["title"] as? String ?? "No Title"
self.imageLink = withBookDict["imageLink"] as? String ?? "No link"
}
}
Again, there are a LOT of different ways of handling this so this is kind of the basics that can be expanded on.
As a suggestion, Realm Results are live-updating objects that also have corresponding events. So a neat thing you can do is to make a results object your tableView datasource and add an observer to it.
Results objects work very much like an array.
As books are added, updated or deleted from realm, the results object will reflect those changes and an event will be fired for each one - that makes keeping your tableView updated very simple.
So in your viewController
class ViewController: NSViewController {
var bookResults: Results<PersonClass>? = nil
#IBOutlet weak var bookTableView: NSTableView!
var bookToken: NotificationToken?
self.bookResults = realm.objects(Book.self)
and then
override func viewDidLoad() {
super.viewDidLoad()
self.bookToken = self.bookResults!.observe { changes in
//update the tableView when bookResults change
You have an error in terms of where you've placed your catch statement -- it should be in line with the do { } block. This might be easier to see if you format/indent your code (Ctrl-i in Xcode).
func fetchArticle(){
let urlRequest = URLRequest(url: URL(string: "https:/mocki.io/v1/89aa9fe9-fdba-463f-99b3-5d8b6bc1d32e")!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if let error = error {
print(error)
return
}
self.books = [Books]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
if let booksFromJson = json["books"] as? [[String : AnyObject]]{
for bookFromJson in booksFromJson {
let book = Books()
if let title = bookFromJson["title"] as? String, let author = bookFromJson["author"] as? String, let imageLink = bookFromJson["imageLink"] as? String {
book.author = author
book.title = title
book.imageLink = imageLink
}
self.books?.append(book)
let realm = try! Realm()
for books in bookFromJson {
try! realm.write {
realm.add(books, update: .all)
}
}
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
}
} catch { //<-- no need for `let error`
print(error)
}
}
task.resume() //<-- Moved outside the declaration
}

In method A, get data from callback in method B?

I have a function that is build to get the latest items from a API. There are several other ones, with different functionality, but they all work the same. It looks like this:
func getLatest(pageNumber: Int) -> Array<Any>{
let urlRequest = URL(string: baseUrl + latestUrl + String(pageNumber))
let requestedData = doRequest(url: urlRequest!, completion: { data -> Void in
// We have the data from doRequest stored in data, but now what?!
})
return allData
}
I also have a async method that handles the requests. That one looks like this:
func doRequest(url: URL, completion: #escaping ([[ApiItem]]) -> ()){
var allItems = [[ApiItem]]()
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do{
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! [String: AnyObject]
let results = json["items"] as? [AnyObject]
for r in results!{
let item = ApiItem(json: r as! [String: Any])
allItems.append([item])
}
completion(allItems)
} catch let jsonError{
print("JSON error: \(jsonError)")
}
}.resume()
The doRequest function works absolutely fine. It gets the data, parses the JSON and send it back to getLatest --> requestedData. The problem right now is, is that getLatest() is a function that needs to return the data that is stored in the data variable of requestedData.
How can I make it so, that the getLatest() function returns the data that is stored in the data in requestedData()?
So I've fixed it by doing this:
In the first method, the one that actually needs the data from the API, I added this:
let trendingData = restApiManager.getLatest(pageNumber: 0, completion: { data -> Void in
let item = data[indexPath.row]
let url = NSURL(string: item.still)
let data = NSData(contentsOf: url as! URL)
if data != nil {
cell.image.image = UIImage(data:data! as Data)
}
})
The getLatest() method looks like this:
func getLatest(pageNumber: Int, completion: #escaping ([ApiItem]) -> ()) {
let urlRequest = URL(string: baseUrl + trendingUrl + String(pageNumber))
let requestedData = doRequest(url: urlRequest!, completion: { data -> Void in
// We have the data from doRequest stored in data
var requestedData = data
completion(requestedData)
})
}
And finally, the doRequest() method looks like this:
func doRequest(url: URL, completion: #escaping ([ApiItem]) -> ()){
var allItems = [ApiItem]()
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do{
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! [String: AnyObject]
let results = json["items"] as? [AnyObject]
for r in results!{
let item = ApiItem(json: r as! [String: Any])
allItems.append(item)
}
completion(allItems)
} catch let jsonError{
print("JSON error: \(jsonError)")
}
}.resume()
}
What I would do is use a Singleton in which I can store the Data
class DataManager:NSObject
{
static let instance = DataManager()
override private init(){}
var items:[ApiItem] = []
}
Then in your first method I would do this:
func getLatest(pageNumber: Int){
let urlRequest = URL(string: baseUrl + latestUrl + String(pageNumber))
let requestedData = doRequest(url: urlRequest!, completion: { data -> items in
// We have the data from doRequest stored in data, but now what?!
DataManager.instance.items = items
})
}
This is how I usually go about this kind of situations. There may be better options though...

Saving array of [String: Any] to user default crash occasionally?

I try to save an array of [String: Any] to user default, and for some situations it works, but others do not. I use the following to save to the default:
static func savingQueueToDisk(){
let queueDict = App.delegate?.queue?.map({ (track) -> [String: Any] in
return track.dict
})
if let queueDict = queueDict{
UserDefaults.standard.set(queueDict, forKey: App.UserDefaultKey.queue)
UserDefaults.standard.synchronize()
}
}
Queue is an array of Track, which is defined as follows:
class Track {
var dict: [String: Any]!
init(dict: [String: Any]) {
self.dict = dict
}
var album: Album?{
guard let albumDict = self.dict[AlbumKey] as? [String: Any] else{
return nil
}
return Album(dict: albumDict)
}
var artists: [Artist]?{
guard let artistsDict = self.dict[ArtistsKey] as? [[String: Any]] else{
return nil
}
let artists = artistsDict.map { (artistdict) -> Artist in
return Artist(dict: artistdict)
}
return artists
}
var id: String!{
return self.dict[IdKey] as! String
}
var name: String?{
return self.dict[NameKey] as? String
}
var uri: String?{
return self.dict[URIKey] as? String
}
}
I got different output when retrieving from the same API
Crashing output:
http://www.jsoneditoronline.org/?id=cb45af75a79aff64995e01e5efc0e7b6
Valid output:
http://www.jsoneditoronline.org/?id=0939823a4ac261bd4cb088663c092b20
It turns out it's not safe to just store an array of [String: Any] to the user defaults directly, and it might break based on the data it contains, and hence complaining about can't set none-property-list to user defaults. I solve this by first convert the array of [String: Any] to Data using JSONSerializationand now it can be saved correctly.
Solution:
//saving current queue in the app delegate to disk
static func savingQueueToDisk(){
if let queue = App.delegate?.queue{
let queueDict = queue.map({ (track) -> [String: Any] in
return track.dict
})
if let data = try? JSONSerialization.data(withJSONObject: queueDict, options: []){
UserDefaults.standard.set(data, forKey: App.UserDefaultKey.queue)
UserDefaults.standard.synchronize()
}else{
print("data invalid")
}
}
}
//retriving queue form disk
static func retrivingQueueFromDisk() -> [Track]?{
if let queueData = UserDefaults.standard.value(forKey: App.UserDefaultKey.queue) as? Data{
guard let jsonObject = try? JSONSerialization.jsonObject(with: queueData, options: []) else{
return nil
}
guard let queueDicts = jsonObject as? [[String: Any]] else{
return nil
}
let tracks = queueDicts.map({ (trackDict) -> Track in
return Track(dict: trackDict)
})
return tracks
}
return nil
}

Need to adjust NSJSONSerialization to iOS10

After upgrading to iOS10 users started complaining about crashes of my app.
I am testing it with iOS10 on the simulator and indeed the app crashes with a message saying "Could not cast value of type '__NSArrayI' to 'NSMutableArray'". Here's my code, please help:
import Foundation
protocol getAllListsModel: class {
func listsDownloadingComplete(downloadedLists: [ContactsList])
}
class ListsDownloader: NSObject, NSURLSessionDataDelegate{
//properties
weak var delegate: getAllListsModel!
var data : NSMutableData = NSMutableData()
func downloadLists() {
let urlPath: String = "http://..."
let url: NSURL = NSURL(string: urlPath)!
var session: NSURLSession!
let configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration() //defaultSessionConfiguration()
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithURL(url)
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.data.appendData(data);
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error != nil {
print("Failed to download data")
}else {
self.parseJSON()
print("Lists downloaded")
}
}
func parseJSON() {
var jsonResult: NSMutableArray = NSMutableArray()
do{
try jsonResult = NSJSONSerialization.JSONObjectWithData(self.data, options:NSJSONReadingOptions.AllowFragments) as! NSMutableArray
} catch let error as NSError {
print(error)
}
var jsonElement: NSDictionary = NSDictionary()
var downloadedLists: [ContactsList] = []
for i in 0...jsonResult.count-1 {
jsonElement = jsonResult[i] as! NSDictionary
let tempContactsList = ContactsList()
//the following insures none of the JsonElement values are nil through optional binding
let id = jsonElement["id"] as? String
let name = jsonElement["name"] as? String
let pin = jsonElement["pin"] as? String
let lastUpdated = jsonElement["created"] as? String
let listAdminDeviceID = jsonElement["admin"] as? String
tempContactsList.id = id
tempContactsList.name = name
tempContactsList.pin = pin
tempContactsList.lastUpdated = lastUpdated
tempContactsList.listAdmin = listAdminDeviceID
downloadedLists.append(tempContactsList)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.listsDownloadingComplete(downloadedLists)
})
}
}
Even in iOS 9, there was no guarantee NSJSONSerialization.JSONObjectWithData(_:options:) would return mutable object or not. You should have specified NSJSONReadingOptions.MutableContainers.
And in your code, you are not modifying jsonResult, which means you have no need to declare it as NSMutableArray. Just replace NSMutableArray to NSArray, and then you have no need to specify NSJSONReadingOptions.MutableContainers.
But as vadian is suggesting, you better use Swift types rather than NSArray or NSDictionary. This code should work both in iOS 9 and 10.
func parseJSON() {
var jsonResult: [[String: AnyObject]] = [] //<- use Swift type
do{
try jsonResult = NSJSONSerialization.JSONObjectWithData(self.data, options: []) as! [[String: AnyObject]] //<- convert to Swift type, no need to specify options
} catch let error as NSError {
print(error)
}
var downloadedLists: [ContactsList] = []
for jsonElement in jsonResult { //<- your for-in usage can be simplified
let tempContactsList = ContactsList()
//the following insures none of the JsonElement values are nil through optional binding
let id = jsonElement["id"] as? String
let name = jsonElement["name"] as? String
let pin = jsonElement["pin"] as? String
let lastUpdated = jsonElement["created"] as? String
let listAdminDeviceID = jsonElement["admin"] as? String
tempContactsList.id = id
tempContactsList.name = name
tempContactsList.pin = pin
tempContactsList.lastUpdated = lastUpdated
tempContactsList.listAdmin = listAdminDeviceID
downloadedLists.append(tempContactsList)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.listsDownloadingComplete(downloadedLists)
})
}
Try this and check it on iOS 10 devices.
(The as! conversion would cause some weird crashes when your server is malfunctioning, but that would be another issue, so I keep it there.)

String still optional in Swift

I am trying to assign a string to a label like so:
self.MsgBlock4.text = "\(wsQAshowTagArray![0]["MsgBlock4"]!)"
But it display the label like so Optional(James) how do I remove the Optional() ?
The Dictionary inside the array comes from here:
self.wsQAshowTag(Int(barcode)!, completion: { wsQAshowTagArray in
})
Here is the method:
func wsQAshowTag(tag: Int, completion: ([AnyObject]? -> Void)) {
let requestString = NSString(format: "URL?wsQATag=%d", tag) as String
let url: NSURL! = NSURL(string: requestString)
let task = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: {
data, response, error in
do {
let result = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as? [AnyObject]
completion(result)
}
catch {
completion(nil)
}
})
task.resume()
}
Since these are optionals and the type can be AnyObject, optional chaining / downcasting and providing a default value using the nil coalescing operator seem the safest approach. If you're expecting the item to be a String:
self.MsgBlock4.text = (wsQAshowTagArray?[0]["MsgBlock4"] as? String) ?? ""
Or I guess for any type that conforms to CustomStringConvertible you can use its description
self.MsgBlock4.text = (wsQAshowTagArray?[0]["MsgBlock4"] as? CustomStringConvertible)?.description ?? ""