本文是对2015年11月使用Objective-C撰写的在
iOS中获取远程数据一文的更新,因此在道德上已过时。 现在,将给出已被重写为Swift 3和iOS 10的代码(最新版本为Swift 4.1和iOS 11,但我的计算机不再支持它们)。
简要理论
网址格式
http://www.google.com/?q=Hello&safe=off
- http是一种协议,它确定发出请求的标准。 更多选项:https,ftp,文件
www.google.com
域名- /-我们需要的资源所在的目录。
- 在问号(?)之后,转到参数q = Hello&safe = off。 它们由键值对组成。
- 该请求还指示一种方法,该方法告诉服务器应如何处理此请求。 默认情况下,这是一个GET方法。
该示例中的网址可以如下所示:将带有GET方法的http请求发送到根目录/中的google.com,带有两个q参数(分别为Hello和safe和off)。
http标头
浏览器将url字符串转换为请求的标头和正文。 对于http请求,正文为空,并且标头如下
GET /?q=Hello&safe=off HTTP/1.1 Host: google.com Content-Length: 133 // //
服务器请求图
首先,创建请求,然后建立连接,发送请求并接收响应。
会议代表
所有UI操作(与用户界面关联)均在主线程中执行。 在执行某种类型的资源密集型操作时,您不能只是采用并停止该线程。 因此,解决此问题的方法之一是创建委托。 因此,操作变为异步,并且主线程不间断运行。 完成必要的操作后,将调用相应的委托方法。 第二种解决方案是创建一个新的执行线程。
就像在原始书中一样,我们使用委托,以便在方法之间更清楚地划分操作。 尽管代码通过块更紧凑。
会话代表类型的描述
我们使用
NSURLSessionDownloadDelegate并实现其方法
URLSession:downloadTask:didFinishDownloadingToURL:。 也就是说,实际上,我们将带有笑话的数据下载到临时存储中,下载完成后,我们调用委托方法进行处理。
过渡到主流
不会将数据加载到临时存储中,而是在主流中进行,但是为了使用此数据来更改UI,我们将转到主流。
失控闭包(@转义)
由于由于代码的实现,我们传递给从url加载数据的方法的闭包将在方法本身中保留下来,因此对于Swift 3,有必要将其@escaping显式指定,并将self设为无所有权,以便self链接不会捕获并保留在此闭包中。 但是这些只是Swift语言本身实现的细微差别,而不是通过API接收数据的技术。
重定向(重定向)
在某些情况下,会发生重定向。 例如,如果我们有一些短网址,那么当我们将其输入到浏览器的搜索栏中时,浏览器会首先转到服务器,该短网址被解密并发送给我们,然后我们使用此完整网址转到目标服务器。 如有必要,我们可以使用
NSURLSessionTaskDelegate来控制这些重定向,但是默认情况下,
NSURLSession处理所有详细信息。
序列化方案
序列化是将数据从一种类型的存储传输到另一种类型而不会丢失内容的过程。 例如,数据以二进制形式存储以占用更少的空间,并且在通过网络发送时,数据被转换为通用JSON(JavaScript对象表示法)格式,我们已经在编程环境中将其解密并转换为对象。
JSON示例:
{ "name": "Martin Conte Mac Donell", "age": 29, "username": "fz" }
圆括号表示字典,字典中的对象由键值对表示。
API(应用程序编程接口)
在我们的例子中,API由接收随机笑话和JSON响应格式的地址表示,我们需要将其解析为便于操作的结构
http:
icndb API示例:
{ "type": "success", "value": { "id": 201, "joke": "Chuck Norris was what Willis was talkin' about" } }
现在练习
像上次一样,整个项目都使用代码实现,而不使用情节提要。 所有代码
均以 3个文件编写:
AppDelegate.swift ,
MainViewController.swift和
HTTPCommunication.swift 。 AppDelegate.swift包含应用程序的常规配置。 HTTPCommunication.swift配置连接(请求,会话)并接收数据。 在MainViewController.swift中,此数据被序列化以输出,并且还包含用户界面代码。
创建一个空项目。 为简单起见,我们仅为
iPhone编写应用程序。 我们删除
ViewController.swift ,
Main.storyboard,并在
Info.plist中删除指向情节
提要的链接,即行
Main Storyboard文件的基本名称 -String -Main 。
默认情况下,iOS上的
App Transport Security使用常规的http(不是https)阻止从Internet下载,因此我们对
Info.plist进行了更改,如下所示。 为此,打开
Info.plist作为
源代码 ,然后添加以下代码:
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <false/> <key>NSExceptionDomains</key> <dict> <key>api.icndb.com</key> <dict> <key>NSExceptionAllowsInsecureHTTPLoads</key> <true/> <key>NSIncludesSubdomains</key> <true/> </dict> </dict> </dict>
默认情况下,我们禁止任意下载:NSAllowsArbitraryLoads键为false。 但是,我们将带有笑话和所有子域的域添加为例外:键值NSExceptionDomains。
现在在
AppDelegate.swift中,我们按如下所示重写
应用程序(_:didFinishLaunchingWithOptions :) :
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { self.window = UIWindow(frame: UIScreen.main.bounds)
创建
HTTPCommunication.swift文件。 然后在其中编写以下代码。
import UIKit // NSObject, (conform) NSObjectProtocol, // URLSessionDownloadDelegate , // , . class HTTPCommunication: NSObject { // completionHandler - , // // . var completionHandler: ((Data) -> Void)! // retrieveURL(_: completionHandler:) // url func retrieveURL(_ url: URL, completionHandler: @escaping ((Data) -> Void)) { } } // , NSObject // (conforms) URLSessionDownloadDelegate, // // . extension HTTPCommunication: URLSessionDownloadDelegate { // // . func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { } }
现在我们将编写这些函数的代码。
复制代码
retrieveURL(_ url:,complementHandler :) // , // @escaping. func retrieveURL(_ url: URL, completionHandler: @escaping ((Data) -> Void)) { self.completionHandler = completionHandler let request: URLRequest = URLRequest(url: url) let session: URLSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil) let task: URLSessionDownloadTask = session.downloadTask(with: request) // , // . task.resume() }
复制代码
func urlSession(_ session:,downloadTask:,didFinishDownloadingTo :) func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { do { // // . // , try, // do {} catch {} let data: Data = try Data(contentsOf: location) // completionHandler . // , // , // , . DispatchQueue.main.async(execute: { self.completionHandler(data) }) } catch { print("Can't get data from location.") } }
我们创建
MainViewController.swift文件并复制以下代码,这将创建必要的接口:
import UIKit class MainViewController: UIViewController { lazy var jokeLabel: UILabel = { let label: UILabel = UILabel(frame: CGRect.zero) label.lineBreakMode = .byWordWrapping label.textAlignment = .center label.numberOfLines = 0 label.font = UIFont.systemFont(ofSize: 16) label.sizeToFit() self.view.addSubview(label) return label }() // . var jokeID: Int = 0 // ActivityView , // , . lazy var activityView: UIActivityIndicatorView = { let activityView: UIActivityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) activityView.hidesWhenStopped = true activityView.startAnimating() view.addSubview(activityView) return activityView }() lazy var stackView: UIStackView = { let mainStackView: UIStackView = UIStackView(arrangedSubviews: [self.jokeLabel]) // mainStackView.spacing = 50 mainStackView.axis = .vertical mainStackView.distribution = .fillEqually self.view.addSubview(mainStackView) return mainStackView }() override func viewDidLoad() { super.viewDidLoad() self.title = "Chuck Norris Jokes" // stackView activityView, // . // stackView // label. self.configConstraints() // (E.2) // // . self.retrieveRandomJokes() // (E.3) } func retrieveRandomJokes() { } } extension MainViewController { func configConstraints() { // autoresizingMask (constraints) // false, // self.stackView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor), self.stackView.leadingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.leadingAnchor), self.stackView.trailingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.trailingAnchor) ]) self.activityView.translatesAutoresizingMaskIntoConstraints = false // (constraints) activityView, // label: X Y // label X Y. NSLayoutConstraint.activate([ self.activityView.centerXAnchor.constraint(equalTo: self.jokeLabel.centerXAnchor), self.activityView.centerYAnchor.constraint(equalTo: self.jokeLabel.centerYAnchor) ]) } }
我们找到了界面,现在您可以填写功能了。
这是
retrieveRandomJokes()代码
func retrieveRandomJokes() { let http: HTTPCommunication = HTTPCommunication() // url , force unwrap // url , let url: URL = URL(string: "http://api.icndb.com/jokes/random")! http.retrieveURL(url) { // self , weak self [weak self] (data) -> Void in // json // , , . // json , // . guard let json = String(data: data, encoding: String.Encoding.utf8) else { return } // : JSON: { "type": "success", "value": // { "id": 391, "joke": "TNT was originally developed by Chuck // Norris to cure indigestion.", "categories": [] } } print("JSON: ", json) do { let jsonObjectAny: Any = try JSONSerialization.jsonObject(with: data, options: []) // , Any // , . guard let jsonObject = jsonObjectAny as? [String: Any], let value = jsonObject["value"] as? [String: Any], let id = value["id"] as? Int, let joke = value["joke"] as? String else { return } // , // . self.activityView.stopAnimating() self.jokeID = id self.jokeLabel.text = joke } catch { print("Can't serialize data.") } } }
现在运行该应用程序并获得以下结果。
我们正在等待网站的笑话。

最后,笑话被加载并显示。

在下一篇文章中,我们将看一下快速重写的应用程序的第二部分,它使您可以在不重新启动程序的情况下接收新的笑话,以及对笑话进行投票。