MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Swift网络编程基础

2021-11-254.5k 阅读

网络编程基础概念

在深入Swift网络编程之前,我们先来了解一些网络编程的基础概念。

网络协议

网络协议是网络中计算机之间进行通信的规则集合。常见的网络协议有TCP(传输控制协议)、UDP(用户数据报协议)和HTTP(超文本传输协议)。

TCP:是一种面向连接的、可靠的传输协议。它通过三次握手建立连接,确保数据的有序传输和完整性。例如,在文件传输、电子邮件发送等场景中,TCP被广泛使用。

UDP:是一种无连接的协议,它不保证数据的可靠传输和顺序。UDP适用于对实时性要求较高,而对数据准确性要求相对较低的场景,如视频流、音频流传输等。

HTTP:是基于TCP之上的应用层协议,用于在Web浏览器和服务器之间传输超文本。它是目前互联网上应用最为广泛的协议之一,我们日常浏览网页、使用各种Web应用都依赖于HTTP协议。

网络请求与响应

在网络编程中,客户端向服务器发送请求,服务器接收请求并处理后返回响应。一个典型的HTTP请求包含请求行、请求头和请求体。请求行包含请求方法(如GET、POST、PUT、DELETE等)、请求URL等信息;请求头包含关于请求的元数据,如User - Agent(标识客户端类型)、Content - Type(请求体的数据类型)等;请求体则包含具体要发送的数据,通常在POST请求中使用。

HTTP响应同样包含状态行、响应头和响应体。状态行包含HTTP状态码(如200表示成功,404表示未找到资源等);响应头提供关于响应的元数据,如Content - Length(响应体的长度)等;响应体则是服务器返回的实际数据,可能是HTML页面、JSON数据等。

Swift中的网络编程框架

在Swift中,有多个网络编程框架可供选择,其中最常用的是URLSession和Alamofire。

URLSession

URLSession是苹果官方提供的网络请求框架,它基于Foundation框架,功能强大且稳定。URLSession提供了三种不同类型的会话:默认会话、后台会话和 ephemeral 会话。

默认会话:这是最常用的会话类型,它会将数据缓存在磁盘上,并且会自动处理cookies。以下是一个使用默认会话进行简单GET请求的示例:

let url = URL(string: "https://example.com/api/data")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
    guard let data = data, error == nil else {
        print(error?.localizedDescription ?? "No data")
        return
    }
    if let httpResponse = response as? HTTPURLResponse {
        print("Status code: \(httpResponse.statusCode)")
    }
    let responseString = String(data: data, encoding:.utf8)
    print("Response: \(responseString ?? "No response string")")
}
task.resume()

在上述代码中,我们首先创建一个URL对象,指定请求的地址。然后使用URLSession.shared获取默认的URLSession实例,并通过dataTask(with:completionHandler:)方法创建一个数据任务。这个任务会在后台执行网络请求,当请求完成后,会调用完成处理程序,在处理程序中我们检查是否有错误,获取HTTP响应状态码,并将响应数据转换为字符串进行打印。

后台会话:适用于需要在后台执行网络任务的场景,即使应用程序被挂起或关闭,任务仍可继续执行。例如,在下载大文件时,如果使用后台会话,用户切换应用或锁屏后,下载仍能继续。

let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.backgroundSession")
let backgroundSession = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let url = URL(string: "https://example.com/bigfile.zip")!
let task = backgroundSession.downloadTask(with: url)
task.resume()

上述代码中,我们通过URLSessionConfiguration.background(withIdentifier:)创建一个后台会话配置,然后使用这个配置创建URLSession实例。注意,这里我们还指定了一个代理(假设self实现了URLSessionDownloadDelegate协议),因为后台会话需要代理来处理一些事件,如下载完成等。

ephemeral 会话:这种会话不会在磁盘上存储任何数据,包括缓存和cookies。它适用于对隐私要求较高的场景,例如匿名浏览。

let configuration = URLSessionConfiguration.ephemeral
let ephemeralSession = URLSession(configuration: configuration)
let url = URL(string: "https://example.com/api/private")!
let task = ephemeralSession.dataTask(with: url) { (data, response, error) in
    // 处理响应
}
task.resume()

Alamofire

Alamofire是一个流行的第三方网络框架,它基于URLSession进行了更高层次的封装,使得网络请求更加简洁和易于使用。Alamofire支持多种请求方法,并且提供了方便的参数设置、响应处理等功能。

以下是使用Alamofire进行GET请求的示例:

import Alamofire

AF.request("https://example.com/api/data").responseJSON { response in
    switch response.result {
    case.success(let value):
        print("Success: \(value)")
    case.failure(let error):
        print("Error: \(error)")
    }
}

在这个示例中,我们通过AF.request(_:)方法发起一个GET请求,AF是Alamofire的全局实例。responseJSON方法表示我们期望得到JSON格式的响应,并在闭包中处理响应结果。如果请求成功,value就是解析后的JSON数据;如果失败,则error包含错误信息。

对于POST请求,Alamofire也很容易实现:

let parameters: [String: Any] = ["username": "testuser", "password": "testpass"]
AF.request("https://example.com/api/login", method:.post, parameters: parameters, encoding: JSONEncoding.default).responseJSON { response in
    switch response.result {
    case.success(let value):
        print("Success: \(value)")
    case.failure(let error):
        print("Error: \(error)")
    }
}

这里我们定义了一个参数字典parameters,并通过AF.request(_:method:parameters:encoding:)方法发起POST请求,指定了请求方法为.post,参数为parameters,编码方式为JSONEncoding.default,以JSON格式发送数据。

处理不同类型的网络请求

GET请求

GET请求通常用于从服务器获取数据。在URLSession中,GET请求是最基本的请求类型,如前面示例所示,只需创建一个dataTask并指定URL即可。

在Alamofire中,GET请求同样简单,只需调用AF.request(_:)并传入URL。如果需要传递参数,GET请求的参数通常附加在URL的查询字符串中。

let parameters: [String: Any] = ["category": "electronics", "page": 1]
let url = "https://example.com/api/products"
let encodedUrl = url + "?" + parameters.map { "\($0)=\($1)" }.joined(separator: "&")
AF.request(encodedUrl).responseJSON { response in
    // 处理响应
}

上述代码中,我们将参数编码为查询字符串并附加到URL上,然后发起GET请求。

POST请求

POST请求用于向服务器提交数据,如用户注册、登录信息等。在URLSession中,发起POST请求需要设置请求方法为POST,并将数据添加到请求体中。

let url = URL(string: "https://example.com/api/login")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let parameters = "username=testuser&password=testpass"
request.httpBody = parameters.data(using:.utf8)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
    // 处理响应
}
task.resume()

在这个示例中,我们创建一个URLRequest对象,设置其请求方法为POST,并将参数编码为字符串后设置为httpBody

在Alamofire中,POST请求更加简洁,如前面示例,只需指定请求方法为.post并传入参数即可,Alamofire会自动处理参数编码。

PUT、DELETE等其他请求

PUT请求通常用于更新服务器上的资源,DELETE请求用于删除资源。在URLSession中,实现方式与POST请求类似,只需将请求方法设置为PUTDELETE

// PUT请求示例
let putUrl = URL(string: "https://example.com/api/products/1")!
var putRequest = URLRequest(url: putUrl)
putRequest.httpMethod = "PUT"
let updateParameters = "name=New Product&price=100"
putRequest.httpBody = updateParameters.data(using:.utf8)
let putTask = URLSession.shared.dataTask(with: putRequest) { (data, response, error) in
    // 处理响应
}
putTask.resume()

// DELETE请求示例
let deleteUrl = URL(string: "https://example.com/api/products/1")!
var deleteRequest = URLRequest(url: deleteUrl)
deleteRequest.httpMethod = "DELETE"
let deleteTask = URLSession.shared.dataTask(with: deleteRequest) { (data, response, error) in
    // 处理响应
}
deleteTask.resume()

在Alamofire中,同样可以通过设置请求方法轻松实现PUT和DELETE请求。

// PUT请求
let putParameters: [String: Any] = ["name": "New Product", "price": 100]
AF.request("https://example.com/api/products/1", method:.put, parameters: putParameters, encoding: JSONEncoding.default).responseJSON { response in
    // 处理响应
}

// DELETE请求
AF.request("https://example.com/api/products/1", method:.delete).responseJSON { response in
    // 处理响应
}

处理网络响应

网络请求完成后,我们需要处理服务器返回的响应。响应可能包含各种类型的数据,如JSON、XML、HTML等。

处理JSON响应

JSON是目前最常用的数据交换格式之一。在Swift中,处理JSON响应通常使用JSONSerialization类或第三方库如SwiftyJSON

在URLSession中,处理JSON响应如下:

let url = URL(string: "https://example.com/api/data")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
    guard let data = data, error == nil else {
        print(error?.localizedDescription ?? "No data")
        return
    }
    do {
        let json = try JSONSerialization.jsonObject(with: data, options:.mutableContainers)
        print("JSON: \(json)")
    } catch let jsonError {
        print("JSON error: \(jsonError)")
    }
}
task.resume()

在上述代码中,我们使用JSONSerialization.jsonObject(with:options:)方法将响应数据解析为JSON对象。如果解析成功,json就是解析后的对象;如果失败,jsonError包含错误信息。

使用SwiftyJSON库处理JSON响应更加方便:

import SwiftyJSON

let url = URL(string: "https://example.com/api/data")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
    guard let data = data, error == nil else {
        print(error?.localizedDescription ?? "No data")
        return
    }
    let json = JSON(data)
    print("JSON: \(json)")
}
task.resume()

SwiftyJSON库提供了简洁的语法来访问JSON数据,例如,如果JSON数据是一个包含用户信息的字典,我们可以这样访问:

let userJson = JSON(data)
let username = userJson["username"].stringValue
let age = userJson["age"].intValue

处理XML响应

处理XML响应在Swift中可以使用NSXMLParser类。假设服务器返回一个包含书籍信息的XML文档:

<?xml version="1.0" encoding="UTF - 8"?>
<books>
    <book>
        <title>Swift Programming</title>
        <author>John Doe</author>
        <price>29.99</price>
    </book>
    <book>
        <title>iOS Development</title>
        <author>Jane Smith</author>
        <price>39.99</price>
    </book>
</books>

我们可以使用以下代码解析这个XML:

class BookParser: NSObject, XMLParserDelegate {
    var currentElement = ""
    var currentBook: [String: String] = [:]
    var books: [[String: String]] = []

    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
        currentElement = elementName
        if elementName == "book" {
            currentBook = [:]
        }
    }

    func parser(_ parser: XMLParser, foundCharacters string: String) {
        let data = string.trimmingCharacters(in:.whitespacesAndNewlines)
        if!data.isEmpty {
            if currentElement == "title" {
                currentBook["title"] = data
            } else if currentElement == "author" {
                currentBook["author"] = data
            } else if currentElement == "price" {
                currentBook["price"] = data
            }
        }
    }

    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        if elementName == "book" {
            books.append(currentBook)
        }
    }
}

let url = URL(string: "https://example.com/api/books.xml")!
let parser = XMLParser(contentsOf: url)!
let bookParser = BookParser()
parser.delegate = bookParser
parser.parse()
print("Books: \(bookParser.books)")

在上述代码中,我们创建了一个BookParser类,实现了XMLParserDelegate协议。parser(_:didStartElement:namespaceURI:qualifiedName:attributes:)方法在遇到开始标签时被调用,parser(_:foundCharacters:)方法用于处理标签内的文本数据,parser(_:didEndElement:namespaceURI:qualifiedName:)方法在遇到结束标签时被调用。通过这些方法,我们可以解析XML文档并提取所需的数据。

处理HTML响应

处理HTML响应相对复杂一些,因为HTML结构较为灵活。在Swift中,可以使用第三方库如SwiftSoup来解析HTML。假设服务器返回一个包含文章列表的HTML页面:

<!DOCTYPE html>
<html>
<head>
    <title>Article List</title>
</head>
<body>
    <div class="article">
        <h2 class="title">Article 1</h2>
        <p class="content">This is the content of article 1.</p>
    </div>
    <div class="article">
        <h2 class="title">Article 2</h2>
        <p class="content">This is the content of article 2.</p>
    </div>
</body>
</html>

使用SwiftSoup解析这个HTML的代码如下:

import SwiftSoup

let url = URL(string: "https://example.com/articles")!
let html = try String(contentsOf: url)
do {
    let doc: Document = try SwiftSoup.parse(html)
    let articles = try doc.select(".article")
    for article in articles {
        let title = try article.select(".title").text()
        let content = try article.select(".content").text()
        print("Title: \(title), Content: \(content)")
    }
} catch let error {
    print("Error: \(error)")
}

在上述代码中,我们首先将HTML内容读取为字符串,然后使用SwiftSoup.parse(_:)方法将其解析为Document对象。通过doc.select(_:)方法,我们可以根据CSS选择器选择所需的元素,从而提取出文章的标题和内容。

网络请求的优化与处理

缓存策略

在网络编程中,缓存策略可以显著提高应用的性能和用户体验。URLSession提供了几种缓存策略,我们可以通过URLRequestcachePolicy属性进行设置。

URLRequest.CachePolicy.returnCacheDataElseLoad:如果缓存中有数据,则直接返回缓存数据,否则从网络加载。

let url = URL(string: "https://example.com/api/data")!
var request = URLRequest(url: url)
request.cachePolicy =.returnCacheDataElseLoad
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
    // 处理响应
}
task.resume()

URLRequest.CachePolicy.reloadIgnoringLocalCacheData:忽略本地缓存,始终从网络加载数据。

let url = URL(string: "https://example.com/api/data")!
var request = URLRequest(url: url)
request.cachePolicy =.reloadIgnoringLocalCacheData
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
    // 处理响应
}
task.resume()

URLRequest.CachePolicy.returnCacheDataDontLoad:只返回缓存数据,如果缓存中没有数据,则不进行网络加载,请求失败。

let url = URL(string: "https://example.com/api/data")!
var request = URLRequest(url: url)
request.cachePolicy =.returnCacheDataDontLoad
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
    // 处理响应
}
task.resume()

错误处理

在网络请求过程中,可能会出现各种错误,如网络连接失败、服务器响应错误等。在URLSession中,错误信息会在完成处理程序的error参数中返回。

let url = URL(string: "https://example.com/api/data")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
    if let error = error {
        if let urlError = error as? URLError {
            switch urlError.code {
            case.notConnectedToInternet:
                print("Not connected to internet")
            case.cannotFindHost:
                print("Cannot find host")
            default:
                print("Other error: \(error.localizedDescription)")
            }
        } else {
            print("General error: \(error.localizedDescription)")
        }
    } else {
        // 处理成功响应
    }
}
task.resume()

在上述代码中,我们首先检查是否有错误,如果有错误,我们判断是否是URLError类型,并根据错误码进行不同的处理。

在Alamofire中,错误处理也很直观,在响应闭包的result中可以获取错误信息。

AF.request("https://example.com/api/data").responseJSON { response in
    if let error = response.error {
        print("Error: \(error)")
    } else {
        // 处理成功响应
    }
}

并发与异步请求

在实际应用中,我们可能需要同时发起多个网络请求,或者在后台线程执行网络请求以避免阻塞主线程。

在URLSession中,dataTask等任务默认在后台线程执行,不会阻塞主线程。如果需要并发执行多个请求,可以创建多个任务并调用resume方法。

let url1 = URL(string: "https://example.com/api/data1")!
let task1 = URLSession.shared.dataTask(with: url1) { (data, response, error) in
    // 处理响应1
}
task1.resume()

let url2 = URL(string: "https://example.com/api/data2")!
let task2 = URLSession.shared.dataTask(with: url2) { (data, response, error) in
    // 处理响应2
}
task2.resume()

在Alamofire中,同样可以轻松实现并发请求。

let group = DispatchGroup()

group.enter()
AF.request("https://example.com/api/data1").responseJSON { response in
    // 处理响应1
    group.leave()
}

group.enter()
AF.request("https://example.com/api/data2").responseJSON { response in
    // 处理响应2
    group.leave()
}

group.notify(queue:.main) {
    print("All requests completed")
}

在上述代码中,我们使用DispatchGroup来管理并发请求,当所有请求完成后,group.notify(queue:execute:)闭包中的代码会被执行。

通过以上对Swift网络编程基础的介绍,包括网络协议、常用框架、请求与响应处理、优化等方面,希望能帮助你在Swift开发中更好地进行网络编程,构建出高性能、稳定的网络应用。