Советы

Сетевые запросы и REST API в iOS и Swift: протокольно-ориентированное программирование. Часть 2

  • You can make a REST API call in Swift in just three lines of code thanks to URLSession and async/await.
  • However, implementing a networking layer in a full-fledged app presents several architectural pitfalls.
  • In this article we will see how REST works, how to perform API calls in a SwiftUI app, and the best way to architect the networking layer of an iOS app.

Сетевые запросы и REST API в iOS и Swift: протокольно-ориентированное программирование. Часть 2

Table of contents

Making a single REST API call in Swift is as straightforward as fetching data from a URL using URLSession. The steps can be distilled into:

  1. Identify the URL of the desired API endpoint.
  2. Retrieve JSON data from the API using the URLSession class with async/await.
  3. Decode the data into a Swift type using Codable and JSONDecoder.

These steps can be accomplished with just a few lines of code. However, as we will see later in this article, fetching data from a REST API within a SwiftUI app involves more complexity than meets the eye.

In this chapter:

Decoding the JSON Data from a REST API

As an example, we will retrieve the title and score for the most upvoted Stack Overflow question of all time using this URL from the StackExchange API.

Here is the JSON data returned by the /questions API endpoint, configured to produce only the data we are interested in.

{
«items»: [
{
«score»: 27089,
«title»: «Why is processing a sorted array faster than processing an unsorted array?»
}
]
}

As a starting point, we need to define two Swift structures for decoding the JSON data using the Codable protocol.

struct Wrapper: Codable {
let items: [Question]
}

struct Question: Codable {
let score: Int
let title: String
}

Since the Stack Exchange API wraps all data inside a common wrapper object, we also require a Wrapper structure. This is specific to the Stack Exchange API, as not all REST APIs wrap their returned data in this way.

Fetching data from a REST API in Swift

Now, we can create a function to make an API request using async/await.

func performAPICall() async throws -> Question {
let url = URL(string: «https://api.stackexchange.com/2.3/questions?pagesize=1&order=desc&sort=votes&site=stackoverflow&filter=)pe0bT2YUo)Qn0m64ED*6Equ»)!
let (data, _) = try await URLSession.shared.data(from: url)
let wrapper = try JSONDecoder().decode(Wrapper.self, from: data)
return wrapper.items[0]
}

To test that our code functions as expected, we can execute it within a Task in a Swift playground and inspect the returned data.

Task {
try await performAPICall()
}

Сетевые запросы и REST API в iOS и Swift: протокольно-ориентированное программирование. Часть 2

More commonly, in a SwiftUI app, REST API calls are executed when a view appears on the screen using the task(priority:_) view modifier.

struct ContentView: View {
@State var question: Question?

var body: some View {
VStack(alignment: .leading) {
if let question {
Text(question.title)
.font(.title)
Text(«Score: » + question.score.formatted())
} else {
Text(«No data available»)
}
}
.padding(20.0)
.task {
do {
question = try await performAPICall()
} catch {
question = nil
}
}
}

func performAPICall() async throws -> Question {
// …
}
}

  1. Сетевые запросы и REST API в iOS и Swift: протокольно-ориентированное программирование. Часть 2
  2. This is also where you handle potential errors, typically by displaying an error message to the user.
  3. While making a network request can be achieved with just a few lines of code, fully utilizing a REST API necessitates understanding its structure and the internet technologies it relies on.
  4. In this chapter:

Getting a list of an API’s URL endpoints and resource paths

The first information you need when interacting with a REST API is a list of its endpoints. These uniform resource locators (URLs) provide the locations of all the API resources on a server.

REST APIs typically have a base URL endpoint, such as https://api.stackexchange.com or https://api.github.com, from which all other endpoints are derived by adding a URL path.

The content of a response can be further configured by adding query string parameters to the endpoint URL or, in some cases, by providing additional HTTP parameters, as we will discuss later.

Whether you interact with a private API owned by your company or one of the many public APIs available on the web, the API’s endpoints are usually documented. Often, you will have to assemble each endpoint URL from its components, as is the case with the Stack Exchange API. However, as the GitHub API does, some APIs return most endpoints in their responses.

REST uses HTTP requests to make API calls

REST, or Representational State Transfer, is a type of architecture for web services. As iOS developers, we are primarily concerned with how REST appears from the perspective of a client SwiftUI app. REST operates over the Hypertext Transfer Protocol (HTTP), initially designed for transmitting web pages over the internet.

In an HTTP request, you typically include:

  • A URL that identifies the resource you want.
  • An HTTP method indicating the action you wish to perform.
  • HTTP headers that specify parameters for the request.
  • Optionally, a body containing data to send to the server.

GET /index.html HTTP/1.1
Host: www.example.com
Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l

HTTP methods and REST API actions

Most REST APIs utilize a subset of HTTP methods to express the actions you can perform:

  • GET to retrieve a resource.
  • POST to create a new resource.
  • PUT to create a new resource or update an existing one.
  • PATCH to update an existing resource.
  • DELETE to remove a resource.

For instance, the MailChimp Marketing API employs all five methods. Using distinct HTTP methods allows the API to return an error for invalid actions, such as attempting to create a resource that already exists.

However, these actions are not universally adopted by all REST APIs. For example, editing a question on the Stack Exchange API requires the POST method.

Regarding parameters, you may have noticed two options: the query string in the URL or HTTP headers.

The choice between query string parameters and HTTP headers typically depends on the API, but, in general:

  • The query string is used for parameters related to the resource being accessed.
  • HTTP headers are used for parameters related to the request, such as authentication headers.

A REST API returns data inside an HTTP response

In HTTP, requests are sent to a server, which responds with data. An HTTP response generally includes:

  • A status code, a numerical indicator of the success or failure of the request.
  • HTTP headers containing additional information about the response.
  • Data, if requested.

HTTP/1.1 200 OK
Date: Mon, 23 May 2005 22:38:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Encoding: UTF-8
Content-Length: 138
Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)
ETag: «3f80f-1b6-3e1cb03b»
Accept-Ranges: bytes
Connection: close

While REST APIs can use various data formats, most return data in Javascript Object Notation (JSON) format. JSON is designed to be lightweight, human-readable, and machine-friendly.

However, some web services employ XML, Markdown, or HTML formats. If interacting with a Windows server, you might receive data in the SOAP format, requiring a custom parser as it’s XML-based.

How to Make a Simple Async GET REST API call in SwiftUI

In this tutorial for beginners, you will learn the basics of using SwiftUI to make API calls using the popular Internet Chuck Norris DataBase (ICNDB) as an example. It will display a joke quickly and easily using Swift and SwiftUI.

You'll see how the cross-platform framework SwiftUI lets us use the exact same code across iOS, iPadOS, macOS, watchOS, App Clips and tvOS, which otherwise would have been impossible.

Along with that, you will use async-await that was introduced in Swift 5.5, which works for newer operating systems including iPhones running iOS > v15.0. This really simplifies our work of making data network calls asynchronously on click of a button without freezing the UI thread.

I will share the code changes you'll need to make first. Then in the following section, I will share a brief analysis of the code so beginners can understand what's going on as well.

Сетевые запросы и REST API в iOS и Swift: протокольно-ориентированное программирование. Часть 2tvOS app running the code displays a button that retrieves the joke on click

How to Make API Calls in Swift and SwiftUI

First, you'll need a Mac to install Xcode. Once it's installed, open Xcode and create a new project. Then select «App» for iOS, macOS, tvOS, or watchOS.

ContentView

Just update your existing ContentView SwiftUI file to add a Button and use the State variable to refresh the text displayed as the joke returns from ICNDB API:

Читайте также:  Какие вопросы задавать работодателю на собеседовании в IT-компанию

import Foundation
import SwiftUI
struct ContentView: View {
@State private var joke: String = «»
var body: some View {
Text(joke)
Button {
Task {
let (data, _) = try await URLSession.shared.data(from: URL(string:»https://api.chucknorris.io/jokes/random»)!)
let decodedResponse = try? JSONDecoder().decode(Joke.self, from: data)
joke = decodedResponse?.value ?? «»
}
} label: {
Text(«Fetch Joke»)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Joke: Codable {
let value: String
}

Fetch a joke!

If you press build/play, the app will build in whatever platform you selected above:

Сетевые запросы и REST API в iOS и Swift: протокольно-ориентированное программирование. Часть 2Screenshots of watchOS, macOS, and iOS apps running the same exact code

Code Analysis

If you go to the random joke URL, you'll notice that the data is in JSON format. You can copy that and use a JSON Linter to view its structure to figure out what property of the Joke object is needed.

Using RESTful APIs in Swift

This article will cover how we go about using RESTful APIs in Swift: we’ll start by covering what a RESTful API actually is and move on to cover how we go from knowing we have one of these things available to actually being able to use it in our apps.

The article is will not go into every detail, and beyond that, I’ll try to expand and clarify it over time, so please leave comments and feedback.

Prerequisites: You should be familiar with the basic of the Swift programming language. For the use of 3-rd party libraries familiarity with use Cocoapods in Xcode is useful.

Сетевые запросы и REST API в iOS и Swift: протокольно-ориентированное программирование. Часть 2

Part I: The Background of REST and RESTful APIs

What is REST?

REST (Representational State Transfer) is an architectural style for designing distributed systems. It is not a standard but a set of constraints, such as being stateless, having a client/server relationship, and a uniform interface. REST is not strictly related to HTTP, but it is most commonly associated with it.

“By using a stateless protocol and standard operations, RESTful systems aim for fast performance, reliability, and the ability to grow, by re-using components that can be managed and updated without affecting the system as a whole, even while it is running” (Wikipedia).

History of REST

The full version of REST was actually only defined in the relatively recent past – by Roy Fielding in 2000 in his doctoral dissertation.

However, the principles embodied in REST started much earlier with the “HTTP object model” beginning in 1994, and were instrumental in the design of both the HTTP 1.

1 and Uniform Resource Identifiers (URI) standards.

For example, in the “Hypertext Transfer Protocol — HTTP/1.

0” document, Tim Berners-Lee,  Fielding, & Frystyk mention GET, HEAD, and POST and what these should be used for (with PUT and DELETE also being mentioned in the appendix).

So, as we can see, the majority of what we think off as part of the usual set of REST keywords, and indeed the general approach, were seen as part of earlier standards and are now thus part of the DNA of REST.

Principles of REST

  • Resources expose easily understood directory structure URIs.
  • Representations transfer JSON or XML to represent data objects and attributes.
  • Messages use HTTP methods explicitly (for example, GET, POST, PUT, and DELETE).
  • Stateless interactions store no client context on the server between requests. State dependencies limit and restrict scalability. The client holds the session state.

HTTP methods

We use HTTP methods to map CRUD (create, retrieve, update, delete) operations to HTTP requests. As we know, these operations are the things we typically use with our SQL databases including MySQL and Core Data implementations. The below table clarifies how we can think about the equivalencies:

Operation
SQL
HTTP
RESTful
DDS
Create INSERT PUT / POST POST write
Read (Retrieve) SELECT GET GET read/take
Update (Modify) UPDATE PUT / POST / PATCH PUT write
Delete (Destroy) DELETE DELETE DELETE dispose

(Source: Wikipedia)

GET

GET requests are for Retrieving Information.

GET requests must be safe and idempotent, meaning regardless of how many times it repeats with the same parameters, the results are the same. They can have side effects, but the user doesn’t expect them, so they cannot be critical to the operation of the system. Requests can also be partial or conditional.

Retrieve an address with an ID of 1:

POST

Request that the resource at the URI does something with the provided entity. Often POST is used to create a new entity, but it can also be used to update an entity.

Create a new address:

PUT

Store an entity at a URI. PUT can create a new entity or update an existing one. A PUT request is idempotent.

Idempotency is the main difference between the expectations of PUT versus a POST request.

Modify the address with an ID of 1:

N.B. PUT replaces an existing entity – If only a subset of data elements is provided, the rest will be replaced with empty or null.

PATCH

Update only the specified fields of an entity at a URI. A PATCH request is neither safe nor idempotent (RFC 5789). That’s because a PATCH operation cannot ensure the entire resource has been updated.

DELETE

Request that a resource be removed; however, the resource does not have to be removed immediately. It could be an asynchronous or long-running request.

Delete an address with an ID of 1:

HTTP Status Code Definitions

Status codes get returned to us (the client) from the remote server as the result of a request. These status codes indicate the result of the HTTP request. There are five classes of status code as follows:

  • 1XX – Informational
  • 2XX – Success
  • This class of status code indicates that the client’s request was successfully received, understood, and accepted.
  • 3XX – Redirection
  • Examples include: 301 Moved Permanently which is a pretty common one demoting the fact that the URI of the resource which was requested has been permanently changed.
  • 4XX – Client Error
  • Many codes in this category, a few common ones are: 401 Unauthorized and 403 Forbidden
  • 5XX – Server Error
  • Response status codes beginning with the digit “5” indicate cases in which the server is aware that it has erred or is incapable of performing the request.
  • For a full list of status codes, see here

HTTP Headers and Media types

  1. The Accept and Content-Type HTTP headers can be used to describe the content being sent or requested within an HTTP request.

  2. The client may set Accept to application/jsonif it is requesting a response in the JSON format.

  3. When sending data, the client can set the Content-Type to, for example, application/xml telling the client that the data being sent to it will be in the XML format.

RESTful API Aspects

Web service APIs that adhere to the REST architectural constraints are called RESTful APIs. HTTP-based RESTful APIs are defined with the following aspects:

  • Base URI, such as “https://reqres.in/api/users”
  • Standard HTTP Method (e.g., GET, POST, PUT, PATCH and DELETE)
  • Media Type that defines state transition data elements (e.g., Atom, microformats, application/vnd.collection+json etc.). The current representation tells the client how to compose requests for transitions to all the next available application states. This could be as simple as a URI or as complex as a Java applet.

(Ref#: C)

Part II: Implementing RESTful API Code in Swift

Dynamic and Asynchronous Networking

Before we go into implementation details let confirm our understanding with regards to networking always being asynchronous and our UI updates always being done on the main thread:

“A device’s network environment can change at a moment’s notice.

There are a number of simple (yet devastating) networking mistakes that can adversely affect your app’s performance and usability, such as executing synchronous networking code on your program’s main thread, failing to handle network changes gracefully, and so on. You can save a lot of time and effort by designing your program to avoid these issues to begin with instead of debugging it later” (Ref#: M).

When we are doing UI work as a result of networking information coming back (or not if there happens to be an error) we naturally must do this back on the main thread

Читайте также:  Приложение для хранения заметок на Django, Django Ninja REST Framework и Alpine.js

Swift: сетевой запрос, ориентированный на протокол — Русские Блоги

class Light {
плагин func () {}
функция open () ()
func увеличить яркость () {}
func уменьшить яркость () {}
}

class LEDLight: Light {}
class DeskLamp: Light {}

func open (object: Light) {
Object.plugin ()
Object.

open ()
}

func main() {
Открыть (Объект: DeskLamp ())
Включите (Объект: LEDLight ())
}

В вышеупомянутой объектно-ориентированной реализациивключитьМетод, похоже, ограниченLightЭтот класс и его производные классы.

Если мы хотим описатьвключитьЭта операция не ограничиваетсяLightЭтот класс и его производные классы (ведь шкафы, таблицы и другие объекты тоже можно открывать) абстрактныевключитьЭта операция, тоprotocolМожет пригодиться.

protocol Openable {
подготовительная работа func ()
func open ()
}

extension Openable {
подготовительная работа func () {}
функция open () ()
}

class LEDLight: Openable {}
class DeskLamp: Openable {}
class Desk: Openable {}

func Open (Object: T) {
Объект. Подготовка ()
Object.open ()
}

func main() {
Открыть (Объект: Стол ())
Включите (Объект: LEDLight ())
}

Обычный сетевой запрос

// 1. Подготавливаем тело запроса
let urlString = «https://www.baidu.com/user»
guard let url = URL(string: urlString) else {
return
}
let body = prepareBody()
let headers = [«token»: «thisisatesttokenvalue»]
var request = URLRequest(url: url)
request.httpBody = body
request.allHTTPHeaderFields = headers
request.httpMethod = «GET»

// 2. Используйте URLSeesion для создания сетевых задач
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let data = data {
// 3. Десериализуем данные
}
}.resume()

Мы видим, что обычно существует три шага для инициирования сетевого запроса.

  • Подготовьте тело запроса (URL, параметры, тело, заголовки …)
  • Используйте фреймворк для создания сетевых задач (URLSession, Alamofire, AFN …)
  • Десериализовать данные (Codable, Protobuf, SwiftyJSON, YYModel …)

Мы можем абстрагироваться от этих трех шагов и использовать триprotocolСделайте спецификации. После стандартизации эти три протокола могут быть реализованы каждым типом, их можно комбинировать и использовать по желанию.

Шаги абстрактного сетевого запроса

Parsable

Сначала мы определяемParsableПротокол для абстрагирования процесса десериализации

protocol Parsable {
static func parse(data: Data) -> Result
}

ParsableПротокол определяет статический метод, этот метод может быть из Data -> Self. НапримерUserследоватьParsableПротокол, необходимо реализовать преобразование из данных в пользователяparse(:)метод

struct User {
var name: String
}
extension User: Parsable {
static func parse(data: Data) -> Result {
//… Реализация данных для пользователя
}
}

Codable

Мы можем использоватьбыстрое расширение протоколаСледоватьCodableДобавьте реализацию типа по умолчанию

extension Parsable where Self: Decodable {
static func parse(data: Data) -> Result {
do {
let model = try decoder.decode(self, from: data)
return .success(model)
} catch let error {
return .failure(error)
}
}
}

такойUserЕсли следоватьCodable, Нет необходимости реализовыватьparse(:)Метод Таким образом, процесс десериализации становится таким простым предложением

extension User: Codable, Parsable {}

URLSession.shared.dataTask(with: request) { (data, response, error) in
if let data = data {
// 3. Десериализуем данные
let user = User.parse(data: data)
}

На этом этапе вы можете задуматься над вопросом: что, если данные представляют собой модельный массив? Это вParsableДобавить в протокол еще один метод для возврата модельного массива? Тогда сделать это снова?

public protocol Parsable {
static func parse(data: Data) -> Result
// возвращаем массив
static func parse(data: Data) -> Result
}

Это неплохо, но есть более быстрый метод, который быстро вызываетУсловия следуют

// Когда элементы в массиве следуют Parsable и Decodable, Array также следует протоколу Parsable
extension Array: Parsable where Array.Element: (Parsable & Decodable) {}

URLSession.shared.dataTask(with: request) { (data, response, error) in
if let data = data {
// 3. Десериализуем данные
let users = [User].parse(data: data)
}

Отсюда мы видим, что протокол swift очень мощный. Если вы правильно его используете, вы можете сэкономить много кода. В стандартной библиотеке swift есть множество примеров.

protobuf

Конечно, если вы используетеSwiftProtobuf, Вы также можете указать его реализацию по умолчанию

extension Parsable where Self: SwiftProtobuf.Message {
static func parse(data: Data) -> Result {
do {
let model = try self.init(serializedData: data)
return .success(model)
} catch let error {
return .failure(error)
}
}
}

Процесс десериализации такой же, как и в предыдущем примере, вызываяparse(:)Метод

Request

Теперь определимRequestПротокол для абстрагирования процесса подготовки тела запроса

protocol Request {
var url: String { get }
var method: HTTPMethod { get }
var parameters: [String: Any]? { get }
var headers: HTTPHeaders? { get }
var httpBody: Data? { get }

/// Тип возврата запроса (необходимо следовать протоколу Parsable)
associatedtype Response: Parsable
}

Определяем тип ассоциации: следоватьParsableизResponse — разрешить типу, реализующему этот протокол, указывать тип, возвращаемый этим запросом, ограничиваяResponseДолжен следоватьParsableПотому что мы будем использоватьparse(:)Метод десериализации.

Реализуем общее тело запроса

struct NormalRequest: Request {
var url: String
var method: HTTPMethod
var parameters: [String: Any]?
var headers: HTTPHeaders?
var httpBody: Data?

typealias Response = T

init(_ responseType: Response.Type,
urlString: String,
method: HTTPMethod = .get,
parameters: [String: Any]? = nil,
headers: HTTPHeaders? = nil,
httpBody: Data? = nil) {
self.url = urlString
self.method = method
self.parameters = parameters
self.headers = headers
self.httpBody = httpBody
}
}

Используется так

let request = NormalRequest(User.self, urlString: «https://www.baidu.com/user»)

Если на сервере есть набор интерфейсовhttps://www.baidu.com/userhttps://www.baidu.com/managerhttps://www.baidu.com/driver мы можем определитьBaiduRequest, Получить URL-адрес или общедоступные заголовки и текстBaiduRequestуправление

// BaiduRequest.swift
private let host = «https://www.baidu.com»

enum BaiduPath: String {
case user = «/user»
case manager = «/manager»
case driver = «/driver»
}

struct BaiduRequest: Request {
var url: String
var method: HTTPMethod
var parameters: [String: Any]?
var headers: HTTPHeaders?
var httpBody: Data?

typealias Response = T

init(_ responseType: Response.Type,
path: BaiduPath,
method: HTTPMethod = .get,
parameters: [String: Any]? = nil,
headers: HTTPHeaders? = nil,
httpBody: Data? = nil) {
self.url = host + path.rawValue
self.method = method
self.parameters = parameters
self.httpBody = httpBody
self.headers = headers
}
}

Легко создать

let userRequest = BaiduRequest(User.self, path: .user)
let managerRequest = BaiduRequest(Manager.self, path: .manager, method: .post)

Client

Наконец, мы определяемClientПротокол, абстрагирование процесса инициирования сетевых запросов

enum Result {
case success(T)
case failure(Error)
}
typealias Handler = (Result) -> ()

protocol Client {
// Принять T, который следует за Parsable, и, наконец, параметром закрытия обратного вызова является ответ в T, который является ответом, определенным протоколом запроса
func send(request: T, completionHandler: @escaping Handler)
}

URLSession

Реализуем использованиеURLSessionизClient

struct URLSessionClient: Client {
static let shared = URLSessionClient()
private init() {}

func send(request: T, completionHandler: @escaping (Result) -> ()) {
var urlString = request.url
if let param = request.parameters {
var i = 0
param.forEach {
urlString += i == 0 ? «?($0.key)=($0.value)» : «&($0.key)=($0.value)»
i += 1
}
}
guard let url = URL(string: urlString) else {
return
}
var req = URLRequest(url: url)
req.httpMethod = request.method.rawValue
req.httpBody = request.httpBody
req.allHTTPHeaderFields = request.headers

URLSession.shared.dataTask(with: req) { (data, respose, error) in
if let data = data {
// Используем метод синтаксического анализа для десериализации
let result = T.Response.parse(data: data)
switch result {
case .success(let model):
completionHandler(.success(model))
case .failure(let error):
completionHandler(.failure(error))
}
} else {
completionHandler(.failure(error!))
}
}
}
}

После выполнения трех соглашений Сетевой запрос в начале примера можно записать следующим образом

let request = NormalRequest(User.self, urlString: «https://www.baidu.com/user»)
URLSessionClient.shared.send(request) { (result) in
switch result {
case .success(let user):
// На данный момент экземпляр User уже получен
print(«user: (user)»)
case .failure(let error):
printLog(«get user failure: (error)»)
}
}

Alamofire

Конечно, вы также можете использоватьAlamofireдостичьClient

struct NetworkClient: Client {
static let shared = NetworkClient()

func send(request: T, completionHandler: @escaping Handler) {
let method = Alamofire.HTTPMethod(rawValue: request.method.rawValue) ?? .get
var dataRequest: Alamofire.DataRequest

if let body = request.httpBody {
var urlString = request.url
if let param = request.parameters {
var i = 0
param.forEach {
urlString += i == 0 ? «?($0.key)=($0.value)» : «&($0.key)=($0.value)»
i += 1
}
}
guard let url = URL(string: urlString) else {
print («Ошибка формата URL»)
return
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = method.rawValue
urlRequest.httpBody = body
urlRequest.allHTTPHeaderFields = request.headers
dataRequest = Alamofire.request(urlRequest)
} else {
dataRequest = Alamofire.request(request.url,
method: method,
parameters: request.parameters,
headers: request.headers)
}

dataRequest.responseData { (response) in
switch response.result {
case .success(let data):
// Используем метод parse (:) для десериализации
let parseResult = T.Response.parse(data: data)
switch parseResult {
case .success(let model):
completionHandler(.success(model))
case .failure(let error):
completionHandler(.failure(error))
}
case .failure(let error):
completionHandler(.failure(error))
}
}
}

private init() {}
}

Пробуем инициировать набор сетевых запросов

let userRequest = BaiduRequest(User.self, path: .user)
let managerRequest = BaiduRequest(Manager.self, path: .manager, method: .post)

NetworkClient.shared.send(managerRequest) { result in
switch result {
case .success(let manager):
// На данный момент экземпляр Manager уже получен
print(«manager: (manager)»)
case .failure(let error):
printLog(«get manager failure: (error)»)
}
}

подводить итоги

Мы используем триprotocolПроцесс сетевых запросов является абстрагированным, что делает сетевые запросы очень гибкими. Вы можете комбинировать различные реализации по желанию.

Различные тела запросов оснащены разными методами сериализации или разными сетевыми структурами. Вы можете использовать URLSession + Codable, Alamofire + Protobuf и т. Д.

, Что значительно облегчает нашу повседневную разработку.

Цитата

Эта статья Мяошена стала началом моего обучения, ориентированного на протокол, что дало мне большое вдохновение:Встреча протокольно-ориентированного программирования и Какао

Swift Vapor Tutorial — Creating a REST API

Swift is a popular programming language among iOS developers. It's known for its common use for developing iOS mobile apps for the Apple store but it can be also used to create server-side web applications which allows developers to use one language to build iOS apps and their REST API back-ends.

Читайте также:  Рекурсивные функции в Python

There are many server side frameworks for building web apps with Swift—the most popular framework is Vapor which has a strong community.

In this tutorial, we'll be using Swift and Vapor for creating a REST API that can be consumed from your client apps.

Prerequisites

To follow this tutorial, you will to have:

  • A Ubuntu 16.04 system with Swift installed,
  • cURL or Postman installed,
  • A working knowledge of Swift.

On macOS you need Xcode

If you are ready! Let's get started by installing Vapor to create your REST web application.

Installing Swift Vapor

Let's get started with this tutorial by looking at how you can install Vapor in your Ubuntu 16.04 machine.

First, you need to make sure your system have the Vapor packages by adding the APT repository for Vapor using the following command:
.
bash
$ eval «$(curl -sL https://apt.vapor.sh)»

For macOS you need to run the eval «$(curl -sL https://apt.vapor.sh)» command.

You can then install Vapor in your Ubuntu system using the official package manager via the following command:

$ sudo apt-get install swift vapor -y

In macOS, you need to use Homebrew instead: sudo brew install vapor.

  • You can check the installed version of Swift using:
  • For Vapor, you need to be able to run the following command:
  • The command should display a list of commands that can be used with Vapor.

Creating your First Vapor Project

After installing Vapor on your Ubuntu system, you'll be able to access the vapor binary from your terminal. This utilti will allow you to create and work with Vapor projects.

  1. In your terminal run the following command to create a new project:
  2. This command will generate a new Vapor project called Myproject using a Vapor template.
  3. Next, navigate inside your project's folder:
  4. You can now build and run your project using the vapor build and vapor run commands:

$ vapor build
$ vapor run

Your application will be available from the localhost:8080/ address in your web browser.

Creating REST API Routes

After creating the project, you can open it in your preferred code IDE (in macOS you can use the vapor xcode command from your project's root folder).

Now, you can create a new route in the Sources/App/routes.swift file and add the following code:

import Vapor public func routes(_ router: Router) throws { router.get(«myroute») { req in return «A new route!» }
}

We simply use the router supplied as a parameter to the routes() function to register our routes.

In this example we are using the get() method of the router instance to add a route that accepts a GET request at the /myroute path. The path is passed as a parameter to get() method.

In the body of the get() method, we add the code for adding any logic that needs to be called when the route is visited and returning the response. In the example we simply return the «A new route!» text.

You can also supply paths as comma separated strings. For example:

router.get(«my», «route») {}

This route will be accessed from the /my/route path.

In the same way, you can create routes for the other HTTP requests, using the .put(), .post(), .patch() and .delete() methods.

You need to build and run the project to use the previous route.

Getting Parameters from your Route(s)

In many cases, you'll need to pass dynamic data to your route. For example an ID for an item to fetch from the database.

Let's create another route that has a dynamic part and responds to GET requests at /items/:id:

router.get(«items», Int.parameter) { req -> String in let id = try req.parameters.next(Int.self) return «requested id #(id)»
}

In the second parameter of the get() method, you simply provide the type of data you expect from the second part of the route.

In the body of the get() method you can access the request information using a req object of String type. Among that information are the passed parameters which are available from the req.parameters object.

You use the next() method to extract the Int parameter from the request.

Parsing and Serializing POST Data

To be able to parse and serialize data in your route(s), you need to use a Codable struct or class. You can pass data in either JSON, URLEncodedForm or Multipart formats.

For example, let's suppose our client is sending the following POST request to our /item/create route with some JSON data:

POST /item/create HTTP/1.1
Content-Type: application/json { «name»: «this is the name», «description»: «this is the description»
}

First, you need create a route that accepts a POST request at the /item/create path:

router.post(Item.self, at: «item/create») { req, data -> String in return «You posted (data.name) (data.description)»
}

The /items route accepts some data of the Item type. The Item type needs to be defined and corresponds to the data sent by the client:

struct Item: Content { let name: String let description: String
}

The Item type extends Content which tells Vapor how to encode and decode it.

You have seen how you can decode data from the request using Content. Now let's suppose we want to decode some data and return the following response to the client:

Протокольно-ориентированное программирование в Swift

Протокольно-ориентированное программирование (POP) — новый фреймворк, который решает проблемы с объектно-ориентированным программированием (ООП) и который был представлена ​​на WWDC 2015.

Все языки программирования имеют понятие наследования. Одна из важнейших концепций языков объектно-ориентированного программирования — это концепция наследования. Swift не поддерживает множественное наследование. Это язык, поддерживающий одиночное наследование.

Мы не можем наследовать два разных класса, как показано в примере.

При этом объектно-ориентированные концепции не работают со структурами (structs) и перечислениями (enums).

Например, структура не может наследовать от другой структуры, перечисление не может наследовать от другого перечисления.

Следовательно, наследование, которое является одной из основных концепций в ООП, нельзя применять к типам значений, таким как struct, enum, но перечисления и структуры могут использоваться с протоколами.

Теперь давайте разберемся на примере, зачем нам нужны протоколы.

Пингвин — это птица, но он не летает, как другие птицы. Все птицы ходят и летают, но пингвины плавают и ходят.

Прежде всего, мы создаем наш класс птиц и добавляем способности передвигаться.

Если мы наследуем класс птиц при создании Penguin, мы унаследуем функцию полета, не характерную для пингвина. Когда мы удалим функцию полета из класса птиц, мы удалим навык, который является общей чертой всех других птиц.

Подход POP (протокольно-ориентированное программирование) появился как решение таких проблем. Давайте определим фичи полета, ходьбы и плавания с помощью следующих протоколов.

  • Давайте унаследуем необходимые нам возможности от протоколов при создании нашего класса Penguin.
  • Таким образом, мы унаследовали фичи, которые принадлежат только Penguin.
  • Протоколы можно использовать аналогично со структурами и перечислениями.

Классы, структуры и перечисления должны соответствовать требованиям, определенным в протоколах. Как и в примере выше, класс Penguin должен содержать методы swim и walk. У методов, определенных в протоколах, нет тела. Протоколы определяют то, что методы делать в классе, структуре или перечислении.

Определение переменной для протокола

Когда мы добавляем переменную в протокол Swift, нам нужно написать геттер и сеттер рядом с ней в фигурных скобках. Ключевые слова get и set рядом с переменной означают, что эту переменную можно как читать, так и изменять. Если мы не хотим, чтобы ее изменяли, мы должны просто добавить в скобках get.

Расширение протокола Swift

Мы можем добавлять расширения к протоколам. Нам не нужно использовать методы или переменные, которые мы добавили с расширением, в классах или структурах, которые мы наследуем.

Например, когда мы определяем функцию run в нашем классе Walkers, нам не нужно использовать эту функцию в классе penguin.

Заключение

Протокольно-ориентированное программирование обращается к тем моментам, в которых ООП недостаточно для программистов, оно дает программисту больше свободы с расширением протокола.

Источник

Еще про протокольно-ориентированное программирование

Если вы нашли опечатку — выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *