我对Swift和编程完全不熟悉。我非常热衷于学习所有正确的方法。所以任何额外的提示或评论总是受到赞赏。
我正在向api发出HTTP请求,并且工作正常。问题是每个请求限制为100个结果。 Theres是一个可选的偏移和我可以设置的限制。如果我给出101的限制,我会收到服务器错误:“错误请求:为限制指定的值无效。允许的最大值为100.”总数是101,所以我需要做至少两个请求。只有在收到我要填充我的tableview的所有请求的总数据后。这就是我所拥有的:
class Book {
var id: Int
var title: String
let description: String
var coverImage: String
var isbn: String
var publisherID: Int
var publisherName: String
var authorID: Int
var authorFirstName: String
var authorLastName: String
class func getDataFromJson(completionHandler: ([Book]) -> ()) {
var books = [Book]()
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let request = NSURLRequest(URL: NSURL(string: "http://example.website.nl/books/highlighted")!)
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if let data = data {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
if let booksFromResult = json["books"] as? [[String: AnyObject]] {
for book in booksFromResult {
let bookID = book["id"] as! Int
let bookTitle = book["title"] as! String
let bookDescription = book["description"] as! String
let bookCoverImage = book["cover_url"] as! String
let bookISBN = book["isbn"] as! String
if let bookPublisher = book["publisher"] as? [String: AnyObject] {
let bookPublisherID = bookPublisher["id"] as! Int
let bookPublisherName = bookPublisher["name"] as! String
if let bookAuthor = book["author"] as? [String: AnyObject] {
let bookAuthorID = bookAuthor["id"] as! Int
let bookAuthorFirstname = bookAuthor["first_name"] as! String
let bookAuthorLastName = bookAuthor["last_name"] as! String
books.append(Book(id: bookID, title: bookTitle, description: bookDescription, coverImage: bookCoverImage, isbn: bookISBN, publisherID: bookPublisherID, publisherName: bookPublisherName, authorID: bookAuthorID, authorFirstName: bookAuthorFirstname, authorLastName: bookAuthorLastName))
}
}
}
print(books.count)
}
dispatch_async(dispatch_get_main_queue(),{
completionHandler(books)
})
} catch {
print("error serializing JSON: \(error)")
}
}
}
task.resume()
}
init(id: Int, title: String, description: String, coverImage: String, isbn: String, publisherID: Int, publisherName: String, authorID: Int, authorFirstName: String, authorLastName: String) {
self.id = id
self.title = title
self.description = description
self.coverImage = coverImage
self.isbn = isbn
self.publisherID = publisherID
self.publisherName = publisherName
self.authorID = authorID
self.authorFirstName = authorFirstName
self.authorLastName = authorLastName
}
}
我一直试图解决这个问题超过24小时。我真的在这里和网上搜索过一个例子。我在这里找到的那个小伙伴无法帮助我。
我的想法是如何做到这一点:
我应该使用url数组并迭代 通过它,而不是将数据附加到某个地方?
我希望有人可以帮助我。我真的很感激。
提前致谢。
答案 0 :(得分:1)
// Heavily based on the video I recommended. Watch it for a great explanation
struct Resource<A>{
let url: NSURL
let parse: (NSData) -> [A]?
}
extension Book {
// You could figure a way to dynamically populate this based on limiting
static let urls = [NSURL(string: "http://example.website.nl/books/highlighted")!,
NSURL(string: "http://example.website.nl/books/highlighted2")!]
// Creates an array of Requests passing in each url for the url, but the same parse function
static let requests = urls.map { Resource<Book>(url: $0, parse: Book.parse) }
// Used by Webservice (from the Resource struct) to parse the data into a Book
static let parse: (NSData?) -> [Book]? = { data in
guard let data = data else { return nil }
guard let json = try? NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) else {
print("Error deserializing json.")
return nil
}
var books: [Book]? = nil
guard let jsonBooks = json["books"] as? [[String: AnyObject]] else { return nil }
for jsonBook in jsonBooks {
guard let book = Book(fromJson: jsonBook) else { continue } // skips nil books from failable initializer, depends on how you want to handle that
books = books ?? [Book]() // if nil create a new array, if not use the old one
books!.append(book)
}
return books
}
}
class Webservice {
// A stands for a generic type. You could add a type called Publisher later and use the same function
// This adopted from the video I showed you so it's a little more in depth
func loadAll<A>(resources: [Resource<A>], completion: [A] -> ()) {
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
var currentRequest = 0 // used to keep track of asynchronous callback order
var allA = [A]()
for resource in resources {
session.dataTaskWithURL(resource.url) { (data, _, _) in
defer {
currentRequest += 1 // marks that we're done with one request
// This check ensures that we only call the completion handler
// after we're done with the last request
if currentRequest == resources.count {
completion(allA)
}
}
guard let data = data else { return }
// this parse function comes from the resource struct passed in.
// It converts the data we get back from one request into an array of books.
guard let manyA = resource.parse(data) else { return }
// This is the total running tally of books from all our requests.
allA.appendContentsOf(manyA)
}
}
}
}
class TableViewController: UITableViewController {
var books = [Book]() {
didSet { tableView.reloadData() }
}
override func viewDidLoad() {
super.viewDidLoad()
// Call site
Webservice().loadAll(Book.requests) { [weak self] (books) in
dispatch_async(dispatch_get_main_queue()) {
self?.books.appendContentsOf(books)
}
}
}
//... all your normal methods for cells and stuff
}
我基于JSON对象为您的Book
类创建了一个可用的初始化程序,这样您的类方法就不必进行大量的解析。您可能希望Book
类型为struct
以获得memberwise initializer和pass by value semantics)
利用guard let else
控制语句避免pyramid of doom可选展开。
defer
statements来调用完成处理程序(避免重复代码)。
我高度推荐this video showing a webservice api design。它有点高级,但它向您展示了从Web服务初始化模型对象的好方法。
class Book {
var id: Int
var title: String
let description: String
var coverImage: String
var isbn: String
var publisherID: Int
var publisherName: String
var authorID: Int
var authorFirstName: String
var authorLastName: String
init(id: Int, title: String, description: String, coverImage: String, isbn: String, publisherID: Int, publisherName: String, authorID: Int, authorFirstName: String, authorLastName: String) {
self.id = id
self.title = title
self.description = description
self.coverImage = coverImage
self.isbn = isbn
self.publisherID = publisherID
self.publisherName = publisherName
self.authorID = authorID
self.authorFirstName = authorFirstName
self.authorLastName = authorLastName
}
typealias JSONDictionary = [String: AnyObject] // syntactic sugar, makes it clearer
convenience init?(fromJson json: JSONDictionary) {
let bookID = json["id"] as! Int
let bookTitle = json["title"] as! String
let bookDescription = json["description"] as! String
let bookCoverImage = json["cover_url"] as! String
let bookISBN = json["isbn"] as! String
// I would use guard let else statements here to avoid the pyramid of doom but it's stylistic
if let bookPublisher = json["publisher"] as? [String: AnyObject] {
let bookPublisherID = bookPublisher["id"] as! Int
let bookPublisherName = bookPublisher["name"] as! String
if let bookAuthor = json["author"] as? [String: AnyObject] {
let bookAuthorID = bookAuthor["id"] as! Int
let bookAuthorFirstname = bookAuthor["first_name"] as! String
let bookAuthorLastName = bookAuthor["last_name"] as! String
self.init(id: bookID, title: bookTitle, description: bookDescription, coverImage: bookCoverImage, isbn: bookISBN, publisherID: bookPublisherID, publisherName: bookPublisherName, authorID: bookAuthorID, authorFirstName: bookAuthorFirstname, authorLastName: bookAuthorLastName)
return
}
}
return nil
}
}
extension Book {
class func getDataFromJson(completionHandler: ([Book]) -> ()) {
var books = [Book]()
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let request = NSURLRequest(URL: NSURL(string: "http://example.website.nl/books/highlighted")!)
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
defer { // no matter how you exit the scope this will be called
dispatch_async(dispatch_get_main_queue()) {
completionHandler(books)
}
}
guard let data = data else { return } // still will call the deferred completion handler
guard let json = try? NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) else {
print("Error deserializing json.")
return // still will call the deferred completion handler
}
if let jsonBooks = json["books"] as? [[String: AnyObject]] {
for jsonBook in jsonBooks {
guard let book = Book(fromJson: jsonBook) else { continue } // skips nil books from failable initializer, depends on how you want to handle that
books.append(book)
}
print(books.count)
}
}
task.resume()
// call the deferred completion handler after leaving scope
}
}