1 Star 1 Fork 0

Fred的技术笔记 / swift_common_lib

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

swift_common_lib

输入图片说明

Alamofire使用

Alamofire 5 Tutorial for iOS: Getting Started

Getting Started

If you’ve been developing iOS apps for some time, you’ve probably needed to access data over the network. And for that you may have used Foundation’s URLSession. This is fine and all, but sometimes it becomes cumbersome to use. And that’s where this Alamofire tutorial comes in!

Alamofire is a Swift-based, HTTP networking library. It provides an elegant interface on top of Apple’s Foundation networking stack that simplifies common networking tasks. Its features include chainable request/response methods, JSON and Codable decoding, authentication and more.

In this Alamofire tutorial, you’ll perform basic networking tasks including:

Requesting data from a third-party RESTful API. Sending request parameters. Converting the response into JSON. Converting the response into a Swift data model via the Codable protocol. Note: Before starting this tutorial, you should have a conceptual understanding of HTTP networking. Some exposure to Apple’s networking classes is helpful, but not necessary. Alamofire obscures implementation details, but it’s good to have some background knowledge if you need to troubleshoot your network requests. Getting Started To kick things off, use the Download Materials button at the top or bottom of this article to download the begin project.

The app for this tutorial is StarWarsOpedia, which provides quick access to data about Star Wars films as well as the starships used in those films.

Start by opening StarWarsOpedia.xcworkspace inside the begin project.

Build and run. You’ll see this:

empty tableview with search bar at the top

It’s a blank slate now, but you’ll populate it with data soon!

Note: You’d normally integrate Alamofire using CocoaPods or another dependency manager. In this case, it’s pre-installed in your downloaded projects. For help integrating Alamofire into your projects using CocoaPods, see CocoaPods Tutorial for Swift: Getting Started.

Using the SW API

SW API is a free and open API that provides Star Wars data. It’s only updated periodically, but it’s a fun way to get to know Alamofire. Access the API at swapi.dev.

There are multiple endpoints to access specific data, but you’ll concentrate on https://swapi.dev/api/films and https://swapi.dev/api/starships.

For more information, explore the Swapi documentation.

Understanding HTTP, REST and JSON

If you’re new to accessing third-party services over the internet, this quick explanation will help.

HTTP is an application protocol used to transfer data from a server to a client, such as a web browser or an iOS app. HTTP defines several request methods that the client uses to indicate the desired action. For example:

  • GET: Retrieves data, such as a web page, but doesn’t alter any data on the server.
  • HEAD: Identical to GET, but only sends back the headers and not the actual data.
  • POST: Sends data to the server. Use this, for example, when filling a form and clicking submit.
  • PUT: Sends data to the specific location provided. Use this, for example, when updating a user’s profile.
  • DELETE: Deletes data from the specific location provided.

JSON stands for JavaScript Object Notation. It provides a straightforward, human-readable and portable mechanism for transporting data between systems. JSON has a limited number of data types to choose from: string, boolean, array, object/dictionary, number and null.

Back in the dark days of Swift, pre-Swift 4, you needed to use the JSONSerialization class to convert JSON to data objects and vice-versa.

It worked well and you can still use it today, but there’s a better way now: Codable. By conforming your data models to Codable, you get nearly automatic conversion from JSON to your data models and back.

REST, or REpresentational State Transfer, is a set of rules for designing consistent web APIs. REST has several architecture rules that enforce standards like not persisting states across requests, making requests cacheable and providing uniform interfaces. This makes it easy for app developers to integrate the API into their apps without having to track the state of data across requests.

HTTP, JSON and REST comprise a good portion of the web services available to you as a developer. Trying to understand how every piece works can be overwhelming. That’s where Alamofire comes in.

Why Use Alamofire?

You may be wondering why you should use Alamofire. Apple already provides URLSession and other classes for accessing content via HTTP, so why add another dependency to your code base?

The short answer is that while Alamofire is based on URLSession, it obscures many of the difficulties of making networking calls, freeing you to concentrate on your business logic. You can access data on the internet with little effort, and your code will be cleaner and easier to read.

There are several major functions available with Alamofire:

  • AF.upload: Upload files with multi-part, stream, file or data methods.
  • AF.download: Download files or resume a download already in progress.
  • AF.request: Other HTTP requests not associated with file transfers.

These Alamofire methods are global, so you don’t have to instantiate a class to use them. Underlying Alamofire elements include classes and structs like SessionManager, DataRequest and DataResponse. However, you don’t need to fully understand the entire structure of Alamofire to start using it.

Enough theory. It’s time to start writing code!

Requesting Data

Before you can start making your awesome app, you need to do some setup.

Start by opening MainTableViewController.swift. Under import UIKit, add the following:

import Alamofire

This allows you to use Alamofire in this view controller. At the bottom of the file, add:

extension MainTableViewController {
  func fetchFilms() {
    // 1
    let request = AF.request("https://swapi.dev/api/films")
    // 2
    request.responseJSON { (data) in
      print(data)
    }
  }
}

Here’s what’s happening with this code:

Alamofire uses namespacing, so you need to prefix all calls that you use with AF. request(_:method:parameters:encoding:headers:interceptor:) accepts the endpoint for your data. It can accept more parameters, but for now, you’ll just send the URL as a string and use the default parameter values. Take the response given from the request as JSON. For now, you simply print the JSON data for debugging purposes. Finally, at the end of viewDidLoad(), add:

fetchFilms()

This triggers the Alamofire request you just implemented.

Build and run. At the top of the console, you’ll see something like this:

success({
  count = 7;
  next = "<null>";
  previous = "<null>";
  results =  ({...})
})

In a few very simple lines, you’ve fetched JSON data from a server. Good job!

Using a Codable Data Model

But, how do you work with the JSON data returned? Working with JSON directly can be messy due to its nested structure, so to help with that, you’ll create models to store your data.

In the Project navigator, find the Networking group and create a new Swift file in that group named Film.swift.

Then, add the following code to it:

struct Film: Decodable {
  let id: Int
  let title: String
  let openingCrawl: String
  let director: String
  let producer: String
  let releaseDate: String
  let starships: [String]
  
  enum CodingKeys: String, CodingKey {
    case id = "episode_id"
    case title
    case openingCrawl = "opening_crawl"
    case director
    case producer
    case releaseDate = "release_date"
    case starships
  }
}

With this code, you’ve created the data properties and coding keys you need to pull data from the API’s film endpoint. Note how the struct is Decodable, which makes it possible to turn JSON into the data model.

The project defines a protocol — Displayable — to simplify showing detailed information later in the tutorial. You must make Film conform to it. Add the following at the end of Film.swift:

extension Film: Displayable {
  var titleLabelText: String {
    title
  }
  
  var subtitleLabelText: String {
    "Episode \(String(id))"
  }
  
  var item1: (label: String, value: String) {
    ("DIRECTOR", director)
  }
  
  var item2: (label: String, value: String) {
    ("PRODUCER", producer)
  }
  
  var item3: (label: String, value: String) {
    ("RELEASE DATE", releaseDate)
  }
  
  var listTitle: String {
    "STARSHIPS"
  }
  
  var listItems: [String] {
    starships
  }
}

This extension allows the detailed information display’s view controller to get the correct labels and values for a film from the model itself.

In the Networking group, create a new Swift file named Films.swift.

Add the following code to the file:

struct Films: Decodable {
  let count: Int
  let all: [Film]
  
  enum CodingKeys: String, CodingKey {
    case count
    case all = "results"
  }
}

This struct denotes a collection of films. As you previously saw in the console, the endpoint swapi.dev/api/films returns four main values: count, next, previous and results. For your app, you only need count and results, which is why your struct doesn’t have all properties.

The coding keys transform results from the server into all. This is because Films.results doesn’t read as nicely as Films.all. Again, by conforming the data model to Decodable, Alamofire will be able to convert the JSON data into your data model.

Note: For more information on Codable, see our tutorial on Encoding and Decoding in Swift. Back in MainTableViewController.swift, in fetchFilms(), replace:


request.responseJSON { (data) in
  print(data)
}

With the following:


request.responseDecodable(of: Films.self) { (response) in
  guard let films = response.value else { return }
  print(films.all[0].title)
}

Now, rather than converting the response into JSON, you’ll convert it into your internal data model, Films. For debugging purposes, you print the title of the first film retrieved.

Build and run. In the Xcode console, you’ll see the name of the first film in the array. Your next task is to display the full list of movies.

Debug console which reads 'A New Hope'

Method Chaining

Alamofire uses method chaining, which works by connecting the response of one method as the input of another. This not only keeps the code compact, but it also makes your code clearer.

Give it a try now by replacing all of the code in fetchFilms() with:

AF.request("https://swapi.dev/api/films")
  .validate()
  .responseDecodable(of: Films.self) { (response) in
    guard let films = response.value else { return }
    print(films.all[0].title)
  }

This single line not only does exactly what took multiple lines to do before, but you also added validation.

From top to bottom, you request the endpoint, validate the response by ensuring the response returned an HTTP status code in the range 200–299 and decode the response into your data model. Nice! :]

Setting up Your Table View

Now, at the top of MainTableViewController, add the following:

var items: [Displayable] = []

You’ll use this property to store the array of information you get back from the server. For now, it’s an array of films but there’s more coolness coming soon! In fetchFilms(), replace:

print(films.all[0].title)

With:

self.items = films.all
self.tableView.reloadData()

This assigns all retrieved films to items and reloads the table view.

To get the table view to show the content, you must make some further changes. Replace the code in tableView(_:numberOfRowsInSection:) with:

return items.count

This ensures that you show as many cells as there are films.

Next, in tableView(_:cellForRowAt:) right below the declaration of cell, add the following lines:

let item = items[indexPath.row]
cell.textLabel?.text = item.titleLabelText
cell.detailTextLabel?.text = item.subtitleLabelText

Here, you set up the cell with the film name and episode ID, using the properties provided via Displayable.

Build and run. You’ll see a list of films:

tableview showing list of film titles

Now you’re getting somewhere! You’re pulling data from a server, decoding it into an internal data model, assigning that model to a property in the view controller and using that property to populate a table view.

But, as wonderful as that is, there’s a small problem: When you tap one of the cells, you go to a detail view controller which isn’t updating properly. You’ll fix that next.

Updating the Detail View Controller

First, you’ll register the selected item. Under var items: [Displayable] = [], add:

var selectedItem: Displayable?

You’ll store the currently-selected film to this property.

Now, replace the code in tableView(_:willSelectRowAt:) with:


selectedItem = items[indexPath.row]
return indexPath

Here, you’re taking the film from the selected row and saving it to selectedItem.

Now, in prepare(for:sender:), replace:

destinationVC.data = nil

With:

destinationVC.data = selectedItem

This sets the user’s selection as the data to display.

Build and run. Tap any of the films. You should see a detail view that is mostly complete.

detail view controller showing most film details

Fetching Multiple Asynchronous Endpoints

Up to this point, you’ve only requested films endpoint data, which returns an array of film data in a single request.

If you look at Film, you’ll see starships, which is of type [String]. This property does not contain all of the starship data, but rather an array of endpoints to the starship data. This is a common pattern programmers use to provide access to data without providing more data than necessary.

For example, imagine that you never tap “The Phantom Menace” because, you know, Jar Jar. It’s a waste of resources and bandwidth for the server to send all of the starship data for “The Phantom Menace” because you may not use it. Instead, the server sends you a list of endpoints for each starship so that if you want the starship data, you can fetch it.

Creating a Data Model for Starships

Before fetching any starships, you first need a new data model to handle the starship data. Your next step is to create one.

In the Networking group, add a new Swift file. Name it Starship.swift and add the following code:

struct Starship: Decodable {
  var name: String
  var model: String
  var manufacturer: String
  var cost: String
  var length: String
  var maximumSpeed: String
  var crewTotal: String
  var passengerTotal: String
  var cargoCapacity: String
  var consumables: String
  var hyperdriveRating: String
  var starshipClass: String
  var films: [String]
  
  enum CodingKeys: String, CodingKey {
    case name
    case model
    case manufacturer
    case cost = "cost_in_credits"
    case length
    case maximumSpeed = "max_atmosphering_speed"
    case crewTotal = "crew"
    case passengerTotal = "passengers"
    case cargoCapacity = "cargo_capacity"
    case consumables
    case hyperdriveRating = "hyperdrive_rating"
    case starshipClass = "starship_class"
    case films
  }
}

As with the other data models, you simply list all the response data you want to use, along with any relevant coding keys.

You also want to be able to display information about individual ships, so Starship must conform to Displayable. Add the following at the end of the file:

extension Starship: Displayable {
  var titleLabelText: String {
    name
  }
  
  var subtitleLabelText: String {
    model
  }
  
  var item1: (label: String, value: String) {
    ("MANUFACTURER", manufacturer)
  }
  
  var item2: (label: String, value: String) {
    ("CLASS", starshipClass)
  }
  
  var item3: (label: String, value: String) {
    ("HYPERDRIVE RATING", hyperdriveRating)
  }
  
  var listTitle: String {
    "FILMS"
  }
  
  var listItems: [String] {
    films
  }
}

Just like you did with Film before, this extension allows DetailViewController to get the correct labels and values from the model itself.

Fetching the Starship Data

To fetch the starship data, you’ll need a new networking call. Open DetailViewController.swift and add the following import statement to the top:

import Alamofire

Then at the bottom of the file, add:


extension DetailViewController {
  // 1
  private func fetch<T: Decodable & Displayable>(_ list: [String], of: T.Type) {
    var items: [T] = []
    // 2
    let fetchGroup = DispatchGroup()
    
    // 3
    list.forEach { (url) in
      // 4
      fetchGroup.enter()
      // 5
      AF.request(url).validate().responseDecodable(of: T.self) { (response) in
        if let value = response.value {
          items.append(value)
        }
        // 6
        fetchGroup.leave()
      }
    }
    
    fetchGroup.notify(queue: .main) {
      self.listData = items
      self.listTableView.reloadData()
    }
  }
}

Here is what’s happening in this code:

You may have noticed that Starship contains a list of films, which you’ll want to display. Since both Film and Starship are Displayable, you can write a generic helper to perform the network request. It needs only to know the type of item its fetching so it can properly decode the result. You need to make multiple calls, one per list item, and these calls will be asynchronous and may return out of order. To handle them, you use a dispatch group so you’re notified when all the calls have completed. Loop through each item in the list. Inform the dispatch group that you are entering. Make an Alamofire request to the starship endpoint, validate the response, and decode the response into an item of the appropriate type. In the request’s completion handler, inform the dispatch group that you’re leaving. Once the dispatch group has received a leave() for each enter(), you ensure you’re running on the main queue, save the list to listData and reload the list table view. Note: You fetch list items asynchronously to allow the total request to finish faster. Imagine you had 100 starships in a movie and had to fetch only one at a time, synchronously. If each request took 100ms, you would have to wait for 10 seconds to fetch all starships! Fetching more starships at the same time greatly reduces this. Now that you have your helper built, you need to actually fetch the list of starships from a film. Add the following inside your extension:

func fetchList() {
  // 1
  guard let data = data else { return }
  
  // 2
  switch data {
  case is Film:
    fetch(data.listItems, of: Starship.self)
  default:
    print("Unknown type: ", String(describing: type(of: data)))
  }
}

Here’s what this does:

Since data is optional, ensure it’s not nil before doing anything else. Use the type of data to decide how to invoke your helper method. If the data is a Film, the associated list is of starships. Now that you’re able to fetch the starships, you need to be able to display it in your app. That’s what you’ll do in your next step.

Updating Your Table View

In tableView(_:cellForRowAt:), add the following before return cell:

cell.textLabel?.text = listData[indexPath.row].titleLabelText

This code sets the cell’s textLabel with the appropriate title from your list data.

Finally, add the following at the end of viewDidLoad():

fetchList()

Build and run, then tap any film. You’ll see a detail view that’s fully populated with film data and starship data. Neat, right?

Completed film details including starship list

The app is starting to look pretty solid. However, look at the main view controller and notice that there’s a search bar that isn’t working. You want to be able to search for starships by name or model, and you’ll tackle that next.

Sending Parameters With a Request

For the search to work, you need a list of the starships that match the search criteria. To accomplish this, you need to send the search criteria to the endpoint for getting starships.

Earlier, you used the films’ endpoint, https://swapi.dev/api/films, to get the list of films. You can also get a list of all starships with the https://swapi.dev/api/starships endpoint.

Take a look at the endpoint, and you’ll see a response similar to the film’s response:


success({
  count = 37;
  next = "<null>";
  previous = "<null>";
  results =  ({...})
})

The only difference is that this time, the results data is a list of all starships.

Alamofire’s request can accept more than just the URL string that you’ve sent so far. It can also accept an array of key/value pairs as parameters.

The swapi.dev API allows you to send parameters to the starships endpoint to perform a search. To do this, you use a key of search with the search criteria as the value.

But before you dive into that, you need to set up a new model called Starships so that you can decode the response just like you do with the other responses.

Decoding Starships

Create a new Swift file in the Networking group. Name it Starships.swift and enter the following code:

struct Starships: Decodable {
  var count: Int
  var all: [Starship]
  
  enum CodingKeys: String, CodingKey {
    case count
    case all = "results"
  }
}

Like with Films you only care about count and results.

Next, open MainTableViewController.swift and, after fetchFilms(), add the following method for searching for starships:

func searchStarships(for name: String) {
  // 1
  let url = "https://swapi.dev/api/starships"
  // 2
  let parameters: [String: String] = ["search": name]
  // 3
  AF.request(url, parameters: parameters)
    .validate()
    .responseDecodable(of: Starships.self) { response in
      // 4
      guard let starships = response.value else { return }
      self.items = starships.all
      self.tableView.reloadData()
  }
}

This method does the following:

Sets the URL that you’ll use to access the starship data. Sets the key-value parameters that you’ll send to the endpoint. Here, you’re making a request like before, but this time you’ve added parameters. You’re also performing a validate and decoding the response into Starships. Finally, once the request completes, you assign the list of starships as the table view’s data and reload the table view. Executing this request results in a URL https://swapi.dev/api/starships?search={name} where {name} is the search query passed in.

Searching for Ships

Start by adding the following code to searchBarSearchButtonClicked(_:):


guard let shipName = searchBar.text else { return }
searchStarships(for: shipName)

This code gets the text typed into the search bar and calls the new searchStarships(for:) method you just implemented.

When the user cancels a search, you want to redisplay the list of films. You could fetch it again from the API, but that’s a poor design practice. Instead, you’re going to cache the list of films to make displaying it again quick and efficient. Add the following property at the top of the class to cache the list of films:

var films: [Film] = []

Next, add the following code after the guard statement in fetchFilms():

self.films = films.all

This saves away the list for films for easy access later.

Now, add the following code to searchBarCancelButtonClicked(_:):


searchBar.text = nil
searchBar.resignFirstResponder()
items = films
tableView.reloadData()

Here, you remove any search text entered, hide the keyboard using resignFirstResponder() and reload the table view, which causes it to show films again.

Build and run. Search for wing. You’ll see all the ships with the word “wing” in their name or model.

search results for 'wing'

That’s great! But, it’s not quite complete. If you tap one of the ships, the list of films that ship appears in is empty. This is easy to fix thanks to all the work you did before. There’s even a huge hint in the debug console!

Display a Ship’s List of Films

Open DetailViewController.swift and find fetchList(). Right now, it only knows how to fetch the list associated with a film. You need to fetch the list for a starship. Add the following just before the default: label in the switch statement:

case is Starship:
  fetch(data.listItems, of: Film.self)

This tells your generic helper to fetch a list of films for a given starship.

Build and run. Search for a starship. Select it. You’ll see the starship details and the list of films it appeared in.

completed app showing list of films for the X-wing

You now have a fully functioning app! Congratulations.

Where to Go From Here?

You can download the completed project using the Download Materials button at the top or bottom of this article.

While building your app, you’ve learned a lot about Alamofire’s basics. You learned that Alamofire can make networking calls with very little setup and how to make basic calls using the request function by sending just the URL string.

Also, you learned to make more complex calls to do things like searching by sending parameters.

You learned how to use request chaining and request validation, how to convert the response into JSON and how to convert the response data into a custom data model.

This article covered the very basics. You can take a deeper dive by looking at the documentation on the Alamofire site at https://github.com/Alamofire/Alamofire.

I highly suggest learning more about Apple’s URLSession, which Alamofire uses under the hood:

Ray Wenderlich – URLSession Tutorial: Getting Started Apple URL Session Programming Guide I hope you enjoyed this tutorial. Please share any comments or questions about this article in the forum discussion below!

Moya的使用

Note: This tutorial uses Xcode 10 and Swift 4.2. The libraries it depends upon are not yet updated for Swift 4.2 but can be used without issue. You’ll need to ignore the single warning telling you that Swift 4.2 conversion is available. There are many moving pieces involved in crafting a beautiful and performant iOS app. One of the most important pieces, if not the most important for a modern app, is networking. As an iOS developer, you may structure your networking layer in many different ways — be it using URLSession or some third-party library.

In this tutorial, you’ll learn about a third-party networking library named Moya, which aims to create a type-safe structure to your network services and requests.

You might ask yourself, “What is this Moya? I already know and love Alamofire!” And if you don’t know and love it, now would be a great time to check out our awesome tutorial on this subject.

Well, this is the great part: Moya actually uses Alamofire while providing a different approach to structuring your network layer. You’ll learn much more about the relation between Moya and Alamofire later in this tutorial.

In this tutorial, you’ll build a neat little app called ComicCards in which you’ll use the Marvel API to show the user a list of comics released in a given week, along with their cover images and other interesting information. When a user selects a comic, your app will generate an image of a shareable card with the comic’s information and image, letting the user upload it to the Imgur service and share it:

The finished ComicCards app! The finished ComicCards app!

Woah — two different API services in one app? Don’t worry! It isn’t as hard as it sounds. Let’s get started!

Note: This tutorial assumes basic knowledge of how HTTP APIs work, though you should be able to easily follow this tutorial even with minimal knowledge. But if you want to know more about HTTP APIs, either refer to the previously mentioned Alamofire tutorial, or refer to this interesting site for more information on REST API basics.

Getting Started

Use the Download Materials button at the top or bottom of this tutorial to download the ComicCards starter project, which already has Moya bundled. Open ComicCards.xcworkspace and not the project file — this is important.

With the project open, check out Main.storyboard to get a general sense of the app structure:

The ComicCards app consists of two different screens:

ComicsViewController: The view controller responsible for presenting the list of comics to the user. CardViewController: The view controller responsible for creating the card for the selected comic and letting the user share the generated card. Build and run the project. You should see the following screen:

Unsurprisingly, you’re presented with an error screen since you haven’t yet implemented the logic related to fetching the comics from the server and displaying them in your app. You’ll get to adding all of the required code very soon, but first you need to learn a bit about Moya.

Moya: What Is It?
What Is Moya?

Moya is a networking library focused on encapsulating network requests in a type-safe way, typically by using enumerations (e.g., enum) to provide compile-time guarantees and confidence when working with your network layer, along with added discoverability.

It was built by Ash Furrow and Orta Therox for Artsy’s Eidolon app and quickly gained popularity. Today, it’s entirely maintained by a passionate community of open-source contributors.

How Is Moya Related to Alamofire?

As mentioned in the introduction to this tutorial, Moya and Alamofire are tightly related simply by the fact that Moya doesn’t really do any networking by itself. It uses Alamofire’s battle-tested networking capabilities and simply provides additional abilities, types and concepts to further abstract Alamofire.

Practically speaking, you are using Alamofire! Instead of using it directly, you use Moya, which uses Alamofire under the hood.

Looking at the starter project’s Podfile.lock reveals just that — Alamofire is a dependency of Moya:

Moya’s Building Blocks Moya introduces a few unique concepts and building blocks that you should be aware of before starting to write your code. It uses the following building blocks to let you describe your entire networking chain:

Moya's Building Blocks

Moya’s Building Blocks

Provider: Moya’s MoyaProvider will be the main object that you’ll create and use when interacting with any network service. It’s a generic object that takes a Moya Target upon initialization. Target: A Moya target usually describes an entire API service; in this case, a Marvel target and an Imgur target. Each of these targets describe the service, its possible endpoints, and the information required by each endpoint to perform a request. You define a target by conforming to the TargetType protocol. Endpoint: Moya uses the semi-internal Endpoint object to describe the basic pieces of information required to perform a network request, e.g., HTTP method, request body, headers and more. Moya’s MoyaProvider transforms every target to an Endpoint, which is eventually transformed into a raw URLRequest. Endpoints are highly customizable but are out of scope for this tutorial as you won’t need any custom mappings. Now that you have all of the basic theory out of the way, it’s time for you to write some code!

Marvel API – The API of Heroes

The Marvel API is the world’s largest comic API, created and maintained by Marvel itself.

Start by creating a free account. Once you’re all set, go back to to the My Developer Account page where you’ll find your new public and private keys:

Marvel API Keys

Keep both keys handy; you’ll need them in a few minutes.

Creating Your First Moya Target Go back to the ComicCards Xcode project. In your project navigator, right-click the ComicCards/Network folder and select New File… Create a new Swift file and name it Marvel.swift:

After import Foundation, add the following code:

import Moya

public enum Marvel {
  // 1
  static private let publicKey = "YOUR PUBLIC KEY"
  static private let privateKey = "YOUR PRIVATE KEY"

  // 2
  case comics
}

You just created a very simple enumeration describing the API service that you’re going to use:

These are your Marvel public and private keys. You store them alongside the definition of your service to make sure the keys are easily accessible as part of your service configuration. Make sure to replace the placeholders with the actual keys generated in the previous step. A single enumeration case named comics, which represents the only endpoint you’re going to hit in Marvel’s API — GET /v1/public/comics. Now that you have your basic enumeration configured, it’s time to actually make it a target by conforming to TargetType.

Add the following code to the end of the file (after the closing curly bracket):

extension Marvel: TargetType {
  // 1
  public var baseURL: URL {
    return URL(string: "https://gateway.marvel.com/v1/public")!
  }

  // 2
  public var path: String {
    switch self {
    case .comics: return "/comics"
    }
  }

  // 3
  public var method: Moya.Method {
    switch self {
    case .comics: return .get
    }
  }

  // 4
  public var sampleData: Data {
    return Data()
  }

  // 5
  public var task: Task {
    return .requestPlain // TODO
  }

  // 6
  public var headers: [String: String]? {
    return ["Content-Type": "application/json"]
  }

  // 7
  public var validationType: ValidationType {
    return .successCodes
  }
}

This might seem like a ton of code, but it’s all simply to conform to TargetType. Let’s break this down:

Every target (e.g., a service) requires a base URL. Moya will use this to eventually build the correct Endpoint object. For every case of your target, you need to define the exact path you’ll want to hit, relative to the base URL. Since the comic’s API is at https://gateway.marvel.com/v1/public/comics, the value here is simply /comics. You need to provide the correct HTTP method for every case of your target. Here, .get is what you want. sampleData is used to provide a mocked/stubbed version of your API for testing. In your case, you might want to return a fake response with just one or two comics. When creating unit tests, Moya can return this “fake” response to you instead of reaching out to the network. As you won’t be doing unit tests for this tutorial, you return an empty Data object. task is probably the most important property of the bunch. You’re expected to return a Task enumeration case for every endpoint you want to use. There are many options for tasks you could use, e.g., plain request, data request, parameters request, upload request and many more. This is currently marked as “to do” since you’ll deal with this in the next section. headers is where you return the appropriate HTTP headers for every endpoint of your target. Since all the Marvel API endpoints return a JSON response, you can safely use a Content-Type: application/json header for all endpoints. validationType is used to provide your definition of a successful API request. There are many options available and, in your case, you’ll simply use .successCodes which means a request will be deemed successful if its HTTP code is between 200 and 299. Note: Notice that you’re using a switch statement in all of your properties even though you only have a single case (.comics). This is a general best practice, since your target might easily evolve and add more endpoints. Any new endpoint will require its own values for the different target properties. Wow, that was a lot of knowledge to take in! You should feel very proud given the fact that this is most of what you need to know to work with Moya in its most basic form!

There’s only one thing missing in your new Marvel target — the “to do” left in the code, meaning the returned Task.

Authorizing Requests in Marvel’s API

The Marvel API uses a custom authorization scheme where you create a “hash” from a unique identifier (such as a timestamp), the private key and the public key, all concatenated together and hashed using MD5. You can read the full specification in the API reference under Authentication for Server-Side Applications.

In Marvel.swift, replace task with the following:

public var task: Task {
  let ts = "\(Date().timeIntervalSince1970)"
  // 1
  let hash = (ts + Marvel.privateKey + Marvel.publicKey).md5
  
  // 2
  let authParams = ["apikey": Marvel.publicKey, "ts": ts, "hash": hash]
  
  switch self {
  case .comics:
    // 3
    return .requestParameters(
      parameters: [
        "format": "comic",
        "formatType": "comic",
        "orderBy": "-onsaleDate",
        "dateDescriptor": "lastWeek",
        "limit": 50] + authParams,
      encoding: URLEncoding.default)
  }
}

Your task is ready! Here’s what that does:

You create the required hash, as mentioned earlier, by concatenating your random timestamp, the private key and the public key, then hashing the entire string as MD5. You’re using an md5 helper property found in Helpers/String+MD5.swift. The authParams dictionary contains the required authorization parameters: apikey, ts and hash, which contain the public key, timestamp and hash, respectively. Instead of the .requestPlain task you had earlier, you switch to using a .requestParameters task type, which handles HTTP requests with parameters. You provide the task with several parameters indicating that you want up to 50 comics from a given week sorted by latest onsaleDate. You add the authParams you created earlier to the parameters dictionary so that they’re sent along with the rest of the request parameters. At this point, your new Marvel target is ready to go! Next, you’re going to update ComicsViewController to use it.

Using Your Target

Go to ComicsViewController.swift and add the following at the beginning of your view controller class:

let provider = MoyaProvider<Marvel>()

As mentioned earlier, the main class you’ll use to interact with your Moya targets is MoyaProvider, so you start by creating an instance of MoyaProvider that uses your new Marvel target.

Next, inside your viewDidLoad(), replace:

state = .error

With:

// 1
state = .loading

// 2

provider.request(.comics) { [weak self] result in
  guard let self = self else { return }

  // 3
  switch result {
  case .success(let response):
    do {
      // 4
      print(try response.mapJSON())
    } catch {
      self.state = .error
    }
  case .failure:
    // 5
    self.state = .error
  }
}

The new code does the following:

First, you set the view’s state to .loading. Use the provider to perform a request on the .comics endpoint. Notice that this is entirely type-safe, since .comics is an enum case. So, there’s no worry of mis-typing the wrong option; along with the added value of getting auto-completed cases for every endpoint of your target. The closure provides a result which can be either .success(Moya.Response) or .failure(Error). If the request succeeds, you use Moya’s mapJSON method to map the successful response to a JSON object and then print it to the console. If the conversion throws an exception, you change the view’s state to .error. If the returned result is a .failure, you set the view’s state to .error as well. Build and run the app. The Xcode debug console should show something similar to the following:

{
    attributionHTML = "<a href=\"http://marvel.com\">Data provided by Marvel. \U00a9 2018 MARVEL</a>";
    attributionText = "Data provided by Marvel. \U00a9 2018 MARVEL";
    code = 200;
    copyright = "\U00a9 2018 MARVEL";
    data =     {
        count = 19;
        limit = 50;
        offset = 0;
        results =         (
            {comic object},
            {comic object},
            {comic object},
            ...
        )
}

Awesome work, you’ve got a valid JSON response from the backend using Moya and your new Marvel target!

Note: It may take several seconds for result to appear in the debug console.

The last step to complete this view controller is actually mapping the JSON response into proper Data Models — in your case, a pre-configured Comic struct.

This is the perfect time to use a different Moya response mapper that maps a response on to a Decodable instead of raw JSON.

You might’ve noticed the JSON response’s structure looks something like:

data ->
  results -> 
      [ Array of Comics ]

Meaning two levels of nesting (data, results) before getting to the objects themselves. The starter project already includes the proper Decodable object that takes care of decoding this.

Replace the following:

print(try response.mapJSON())

With:

self.state = .ready(try response.map(MarvelResponse<Comic>.self).data.results)

Instead of mapping the object to a raw JSON response, you use a mapper that takes the MarvelResponse generic Decodable with a Comic struct. This will take care of parsing the two levels of nesting as well, which lets you access the array of comics by accessing data.results.

You set the view’s state to .ready with its associated value being the array of Comic objects returned from the Decodable mapping.

Build and run the project. You should see your first screen fully functional!

On to the detail view then!

When you tap on a comic, the starter project already has the code for showing a CardViewController and passing it the selected Comic to it. But, you might notice that tapping a comics only shows an empty card without any comic details. Let’s take care of that!

Switch to CardViewController.swift and find the layoutCard(comic:) method. Inside the method, add:

// 1
lblTitle.text = comic.title
lblDesc.text = comic.description ?? "Not available"

// 2
if comic.characters.items.isEmpty {
  lblChars.text = "No characters"
} else {
  lblChars.text = comic.characters.items
                       .map { $0.name }
                       .joined(separator: ", ")
}

// 3
lblDate.text = dateFormatter.string(from: comic.onsaleDate)

// 4
image.kf.setImage(with: comic.thumbnail.url)

This code updates the screen with information from the provided Comic struct by:

Setting the comic’s title and the comic’s description. Setting the list of characters for the comic, or, “No characters” if there are no characters. Setting the “on sale” date of the comic, using a pre-configured DateFormatter. Loading the comic’s image using Kingfisher — a great third-party library for loading web images. Build and run your app, and tap one of the comics in the list — you should see a beautiful information card:

You have two more features to add: uploading your card to Imgur and letting the user delete the card.

Imgur – Sharing With Friends!

For this, you’ll create another Moya target named Imgur that will let you interact with two different endpoints for image handling: one for uploading and one for deleting.

Similar to the Marvel API, you’ll need to sign up for a free account with Imgur.

After that, you’ll need to create an Imgur Application. You may use any fake URL for the callback, as you won’t be using OAuth here. You can also simply choose OAuth 2 authorization without a callback URL.

Registering a new Imgur application

Registering a new Imgur application

Once you submit the form, Imgur will present you with your new Imgur Client ID and Client secret. Save these for the next step.

Creating the Imgur Target

Right-click the ComicCards/Network folder and select New File… Then create a new Swift file and name it Imgur.swift.

Add the following code to define the Imgur endpoints that you’ll implement and use:

import UIKit
import Moya

public enum Imgur {
  // 1
  static private let clientId = "YOUR CLIENT ID"

  // 2
  case upload(UIImage)
  case delete(String)
}

Similar to the Marvel API, you:

Store your Imgur Client ID in clientId. Make sure to replace this with the Client ID generated in the previous step (you don’t need the secret). Define the two endpoints that you’ll be using: upload, used to upload an image, and delete, which takes a hash for a previously uploaded image and deletes it from Imgur. These are represented in the Imgur API as POST /image and DELETE /image/{imageDeleteHash}. Next, you’ll conform to TargetType. Add the following code right below your new enum:

extension Imgur: TargetType {
  // 1
  public var baseURL: URL {
    return URL(string: "https://api.imgur.com/3")!
  }

  // 2
  public var path: String {
    switch self {
    case .upload: return "/image"
    case .delete(let deletehash): return "/image/\(deletehash)"
    }
  }

  // 3
  public var method: Moya.Method {
    switch self {
    case .upload: return .post
    case .delete: return .delete
    }
  }

  // 4
  public var sampleData: Data {
    return Data()
  }

  // 5
  public var task: Task {
    switch self {
    case .upload(let image):
      let imageData = image.jpegData(compressionQuality: 1.0)!

      return .uploadMultipart([MultipartFormData(provider: .data(imageData),
                                                 name: "image",
                                                 fileName: "card.jpg",
                                                 mimeType: "image/jpg")])
    case .delete:
      return .requestPlain
    }
  }

  // 6
  public var headers: [String: String]? {
    return [
      "Authorization": "Client-ID \(Imgur.clientId)",
      "Content-Type": "application/json"
    ]
  }

  // 7
  public var validationType: ValidationType {
    return .successCodes
  }
}

This should look familiar to you by now. Let’s go through the seven protocol properties of the new Imgur target.

The base URL for the Imgur API is set to https://api.imgur.com/3. You return the appropriate endpoint path based on the case. /image for .upload, and /image/{deletehash} for .delete. The method differs based on the case as well: .post for .upload and .delete for .delete. Just like before, you return an empty Data struct for sampleData. The task is where things get interesting. You return a different Task for every endpoint. The .delete case doesn’t require any parameters or content since it’s a simple DELETE request, but the .upload case needs some more work. To upload a file, you’ll use the .uploadMultipart task type, which takes an array of MultipartFormData structs. You then create an instance of MultipartFormData with the appropriate image data, field name, file name and image mime type.

Like the Marvel API, the headers property returns a Content-Type: application/json header, and an additional header. The Imgur API uses Header authorization, so you’ll need to provide your Client ID in the header of every request, in the form of Authorization: Client-ID (YOUR CLIENT ID). The .validationType is the same as before — valid for any status codes between 200 and 299. Your Imgur target is done! This concludes the Moya-related code for the ComicCards app. Kudos to you!

The final step is completing CardViewController to have it use your newly created Moya target.

Wrapping Up CardViewController

Go back to CardViewController.swift and add the following lines at the beginning of your CardViewController class, below the comic property:

private let provider = MoyaProvider<Imgur>()
private var uploadResult: UploadResult?

Like before, you create a MoyaProvider instance, this time with the Imgur target. You also define uploadResult — an optional UploadResult property you’ll use to store the result of an upload, which you’ll need when deleting an image.

You have two methods to implement: uploadCard() and deleteCard().

At the end of uploadCard(), append the following code:

// 1
let card = snapCard()

// 2
provider.request(.upload(card),
  // 3
  callbackQueue: DispatchQueue.main,
  progress: { [weak self] progress in
    // 4
    self?.progressBar.setProgress(Float(progress.progress), animated: true)
  },
  completion: { [weak self] response in
    guard let self = self else { return }
    
    // 5
    UIView.animate(withDuration: 0.15) {
      self.viewUpload.alpha = 0.0
      self.btnShare.alpha = 0.0
    }
    
    // 6
    switch response {
    case .success(let result):
      do {
        let upload = try result.map(ImgurResponse<UploadResult>.self)
        
        self.uploadResult = upload.data
        self.btnDelete.alpha = 1.0
        
        self.presentShare(image: card, url: upload.data.link)
      } catch {
        self.presentError()
      }
    case .failure:
      self.presentError()
    }
})

This big chunk of code definitely needs some explanation, but worry not — most of it should be relatively familiar.

You use a helper method called snapCard() to generate a UIImage from the presented card on screen. Like with the Marvel API, you use your provider to invoke the upload endpoint with an associated value of the card image. callbackQueue allows providing a queue on which you’ll receive upload progress updates in the next callback. You provide the main DispatchQueue to ensure progress updates happen on the main thread. You define a progress closure, which will be invoked as your image is uploaded to Imgur. This sets the progress bar’s progress and will be invoked on the main DispatchQueue provided in callbackQueue. When the request completes, you fade out the upload view and the share button. As before, you handle the success and failure options of the result. If successful, you try to map the response to an ImgurResponse and then store the mapped response in the instance property you defined before. You’ll use this property later when finishing up the deleteCard() method. After storing the upload result, you trigger the presentShare method which will present a proper share alert with the URL to the uploaded image, and the image itself. A failure will trigger the presentError() method.

And for your final piece of code for the day: Add the following code inside deleteCard():

// 1
guard let uploadResult = uploadResult else { return }
btnDelete.isEnabled = false

// 2
provider.request(.delete(uploadResult.deletehash)) { [weak self] response in
  guard let self = self else { return }

  let message: String

  // 3
  switch response {
  case .success:
    message = "Deleted successfully!"
    self.btnDelete.alpha = 0.0
  case .failure:
    message = "Failed deleting card! Try again later."
    self.btnDelete.isEnabled = true
  }

  let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert)
  alert.addAction(UIAlertAction(title: "Done", style: .cancel))

  self.present(alert, animated: true, completion: nil)
}

This method is rather simple and works as follows:

You make sure the uploadResult is available and disable the delete button so the user doesn’t tap it again. You use the Imgur provider to invoke the delete endpoint with the associated value of the upload result’s deletehash. This hash uniquely identifies the uploaded image. In case of a successful or failed deletion, you show an appropriate message. That is it! Build and run your app one final time. Select a comic and share your image to Imgur. After you’re done with it, you can tap the Delete from Imgur button to remove it.

Note: Something you might notice is that you can only delete the uploaded image as long as you’re in the card view controller. As soon as you leave it, the view controller’s uploadResult will be cleared and the deletehash will be lost. Persisting the hash for any generated images over different sessions is a nice challenge you might want to tackle :].

Taking Moya to the Next Level

Moya is an extremely versatile networking library with too many additional features to fully cover in this tutorial, but they are definitely worth mentioning:

Reactive Extensions: Moya provides and maintains two excellent reactive additions to Moya for RxSwift and ReactiveSwift, aptly named RxMoya and ReactiveMoya. Plugins: Moya lets you create pieces named Plugins, which you can use to modify requests and responses, or to perform side effects. The can be useful, for example, for logging requests and responses or automatically showing a network activity indicator when running network requests. Testing: As mentioned earlier, every TargetType has a sampleData property wherein you can provide a stubbed response for your endpoints. When creating a MoyaProvider, you can provide a stubClosure, which defines if you want Moya to return a stubbed response or a real one (the default). You can learn much more about this in Moya’s testing documentation. Harvey: Speaking of stubbing responses — some of the team behind Moya are developing a separate framework named Harvey for easy mocking of network responses. It is still in early development but I’d highly recommend following this project. Moya is a feature-packed networing library Moya is a feature-packed networing library

Where to Go From Here?

You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial. Don’t forget to set your Imgur Client ID and Marvel public and private keys in the project!

In this tutorial, you’ve learned the basics of using Moya and then some! You have everything you need to take your networking layer to the next level.

The best place to continue your exploration of Moya would be its official documentation page, which is very informative and dives into much more detail on every aspect of Moya, and even has a maintained Chinese translation.

In the meantime, if you have any questions or comments about this tutorial or networking in general, please join the forum discussion below.

Swift

Define Classes in Swift

Written by Team Kodeco Classes in Swift are a blueprint for creating objects (also known as instances) of a certain type. They allow you to encapsulate data (stored properties) and behavior (methods) within a single entity.

Here is an example of how to define a class in Swift:

class MyClass {
  // properties
  var name: String
  
  // initializer
  init(name: String) {
    self.name = name
  }
  
  // method
  func printName() {
    print(name)
  }
}

In this example, You have a class called MyClass that has one property called name of type String. You also have an initializer, which is used to set the initial value of the name property when an instance of the class is created. Finally, you have a method called printName() that simply prints the value of the name property.

To create an instance of the class, we use the following syntax:

let myInstance = MyClass(name: "John")

You can then access the properties and call the methods of the instance:

myInstance.name // John
myInstance.printName()  // prints "John"

The rest of this section of the cookbook will go into more detail about how to use classes (and structures) in Swift.

Define Structures in Swift

Structures in Swift are similar to classes, but they have a few key differences that make them useful in different situations.

A structure is a value type, which means that when you create an instance of a structure, it creates a new copy of the data. This can be useful when you want to make sure that changes to one instance of a structure don’t affect other instances.

Here’s an example of how to define a structure in Swift:


struct Point {
  var x: Double
  var y: Double
}

This creates a new structure called “Point” that has two properties, “x” and “y”.

You can create a new instance of a structure like this:

var myPoint = Point(x: 1.0, y: 2.0)

You can also access and modify the properties of a structure like this:

myPoint.x = 3.0
print(myPoint.x) // prints "3.0"

Here are some key points to remember about structures in Swift:

They are value types, which means that when you create an instance of a structure, it creates a new copy of the data. They can have properties and methods just like classes. They are useful when you want to make sure that changes to one instance of a structure don’t affect other instances. The rest of this section of the cookbook will go into more detail about how to use structures (and classes) in Swift.

Use Properties in Swift

Properties in Swift are used to store values associated with an instance of a class or structure. They can be either stored properties, which hold a value, or computed properties, which calculate a value.

Defining a Stored Property Here’s an example of how to define a stored property in a class:

class MyClass {
  var myStoredProperty: Int = 0
}

This creates a new class called MyClass with a stored property called myStoredProperty that is initialized with the value 0.

You can access and modify the value of a stored property like this:

let myObject = MyClass()
myObject.myStoredProperty = 10
print(myObject.myStoredProperty) // prints "10"

Defining a Computed Property

In Swift, computed properties are properties that don’t have a stored value, but instead provide a getter and an optional setter to compute their value. The general syntax for defining a computed property is as follows:

var myComputedProperty: SomeType {
  get {
    // code to compute the value of the property
  }
  set(newValue) {
    // code to set the value of the property
  }
}

The get keyword is used to define the code that is executed when the property is read and the set keyword is used to define the code that is executed when the property is written to.

Here’s an example of how to define a computed property in a class:

class MyClass {
  var myStoredProperty: Int = 0
  var myComputedProperty: Int {
    get {
      return myStoredProperty * 2
    }
    set {
      myStoredProperty = newValue / 2
    }
  }
}

This creates a new class called MyClass with a computed property called myComputedProperty that returns the value of myStoredProperty multiplied by 2.

You can access the value of a computed property just like you would with a stored property:

let myObject = MyClass()
myObject.myStoredProperty = 10
print(myObject.myComputedProperty) // prints "20"

It’s also possible to set the value of a computed property, which will call the setter and use the value in it:

myObject.myComputedProperty = 30
print(myObject.myStoredProperty) // prints "15"

Use Initializers in Swift

Initializers in Swift are special methods that are used to create new instances of a class or structure. They are responsible for setting up the initial state of an object when it’s first created.

Here’s an example of how to define an initializer in a class:


class MyClass {
  var myProperty: Int
  
  init(propertyValue: Int) {
    self.myProperty = propertyValue
  }
}

This creates a new class called “MyClass” with an initializer that takes one parameter called “propertyValue” and sets the value of the “myProperty” property to the value of the parameter.

You can create a new instance of the class using the initializer like this:

let myObject = MyClass(propertyValue: 10)

Here’s an example of how to define an initializer in a structure:

struct Point {
  var x: Double
  var y: Double

  init(x: Double, y: Double) {
    self.x = x
    self.y = y
  }
}

This creates a new structure called “Point” with an initializer that takes two parameters called “x” and “y” and sets the values of the corresponding properties to the values of the parameters.

You can create a new instance of the structure using the initializer like this:

let myPoint = Point(x: 1.0, y: 2.0)

Default initializer As long as all of your properites have default values, Swift provides a default initializer if no initializer is defined.

For example, in the following class definition:

class MyClass {
  var myProperty: Int = 0
}

You can create a new instance of the class without providing any parameters because the default value of 0 is already set for the myProperty property.

let myObject = MyClass()

Designated and Convenience Initializers In addition to the default initializer, you can also define multiple initializers for a class or structure, and these are called designated initializers and convenience initializers.

A designated initializer is the main initializer for a class or structure, and it’s responsible for fully initializing all the properties of the class or structure.

A convenience initializer is a secondary initializer that can call a designated initializer with default or specific values. Convenience initializers are useful for providing a simpler or more convenient way to create an instance of a class or structure.

Here’s an example of a designated initializer and a convenience initializer for a class:

class MyClass {
  var myProperty: Int

  init(propertyValue: Int) {
    self.myProperty = propertyValue
  }

  convenience init() {
    self.init(propertyValue: 0)
  }
}

Here the init(propertyValue: Int) is the designated initializer and convenience init() is the convenience initializer. The convenience initializer calls the designated initializer with a default value of 0. ####Use Methods in Swift

Methods in Swift are functions that are associated with an instance of a class or structure. They can be used to perform actions or calculations on the properties of an object and can also take parameters and return values.

Here’s an example of how to define a method in a class:


class MyClass {
  var myProperty: Int
  
  init(propertyValue: Int) {
    self.myProperty = propertyValue
  }
  
  func doubleMyProperty() {
    myProperty *= 2
  }
}

This creates a new class called MyClass with a method called doubleMyProperty that doubles the value of the myProperty property.

You can call the method on an instance of the class like this:

let myObject = MyClass(propertyValue: 10)
myObject.doubleMyProperty()
print(myObject.myProperty) // prints "20"

Here’s an example of how to define a method that takes parameters and returns a value in a structure:


import Foundation
struct Point {
  var x: Double
  var y: Double
    
  init(x: Double, y: Double) {
    self.x = x
    self.y = y
  }
    
  func distance(to point: Point) -> Double {
    let deltaX = x - point.x
    let deltaY = y - point.y
    return sqrt(deltaX * deltaX + deltaY * deltaY)
  }
}

This creates a new structure called Point with a method called distance that takes one parameter of Point type and returns the distance between two points.

You can call the method on an instance of the structure like this:

let point1 = Point(x: 0.0, y: 0.0)
let point2 = Point(x: 3.0, y: 4.0)
let distance = point1.distance(to: point2)
print(distance) // prints "5.0

Use Subscripts in Swift

Subscripts in Swift are a shorthand way to access elements of a collection, list, or sequence. They allow you to access elements of a class or structure using square brackets [] instead of calling a method.

Here’s an example of how to define a subscript in a class:

class MyList {
  var items: [String]
  
  init(items: [String]) {
    self.items = items
  }
  
  subscript(index: Int) -> String {
    get {
      return items[index]
    }
    set {
      items[index] = newValue
    }
  }
}

This creates a new class called “MyList” with a subscript that allows you to access the elements of the “items” array by index.

You can use the subscript to access or set the elements of the array like this:

let myList = MyList(items: ["apple", "banana", "orange"])
print(myList[1]) // prints "banana"
myList[1] = "mango"
print(myList.items) // prints ["apple", "mango", "orange"]

Use Inheritance in Swift

Inheritance in Swift allows a class to inherit the properties and methods of another class. It is used to create a hierarchical relationship between classes, where a subclass can inherit the characteristics of a superclass. The subclass can then add its own unique properties and methods, or override the ones inherited from the superclass.

Here’s an example of how to define a superclass and a subclass that inherits from it:

class Vehicle {
  var wheels: Int
  var weight: Double
  var maxSpeed: Double
  
  init(wheels: Int, weight: Double, maxSpeed: Double) {
    self.wheels = wheels
    self.weight = weight
    self.maxSpeed = maxSpeed
  }
  
  func description() -> String {
    return "A vehicle with \(wheels) wheels, weighs \(weight) kgs and has a maximum speed of \(maxSpeed) km/h."
  }
}

class Car: Vehicle {
  var numberOfDoors: Int
  
  init(wheels: Int, weight: Double, maxSpeed: Double, doors: Int) {
    self.numberOfDoors = doors
    super.init(wheels: wheels, weight: weight, maxSpeed: maxSpeed)
  }
}

This creates a new superclass called Vehicle with properties for the number of wheels, weight and maximum speed. It also has a method called description that returns a string with information about the vehicle.

Then, a new subclass called Car is defined, which inherits from the Vehicle class. It adds its own unique property for the number of doors and also overrides the init method to set the number of doors property.

You can create an instance of the Car class like this:

let myCar = Car(wheels: 4, weight: 1000, maxSpeed: 200, doors: 4)

And you can access the inherited properties and methods from the superclass like this:

print(myCar.wheels) // prints 4
print(myCar.description()) 
// prints "A vehicle with 4 wheels, weighs 1000.0 kgs and has a maximum speed of 200.0 km/h.

An analogy for inheritance might be a family tree. Just like a child inherits certain traits and characteristics from its parents, a subclass in Swift inherits certain properties and methods from its superclass.

Use Polymorphism in Swift

Polymorphism in Swift allows objects of different classes to be treated as objects of a common superclass or protocol. It enables objects of different types to be used interchangeably, as long as they conform to a common interface or protocol.

Here’s an example of how to use polymorphism with a superclass and its subclasses:

class Shape {
  func draw() {
    print("Drawing a shape")
  }
}

class Circle: Shape {
  override func draw() {
    print("Drawing a circle")
  }
}

class Square: Shape {
  override func draw() {
    print("Drawing a square")
  }
}

This creates a new superclass called Shape with a method called draw that prints “Drawing a shape”.

Then, two new subclasses called Circle and Square are defined, which inherit from the Shape class. They override the draw method to print “Drawing a circle” and “Drawing a square” respectively.

You can create an instance of the subclasses and use them interchangeably with the superclass:


func drawShape(shape: Shape) {
  shape.draw()
}
drawShape(shape: Circle()) // prints "Drawing a circle"
drawShape(shape: Square()) // prints "Drawing a square"

Use Extensions in Swift

Extensions in Swift allow you to add new functionality to existing classes, structures, enumerations, or protocols. It allows you to add new methods, computed properties and initializers to a class without having to subclass it or make any modifications to the original source code.

Here’s an example of how to use an extension to add a new method to an existing class:

import Foundation

extension Double {
  func roundToDecimal(_ fractionDigits: Int) -> Double {
    let multiplier = pow(10, Double(fractionDigits))
    return Darwin.round(self * multiplier) / multiplier
  }
}

This creates an extension for the built-in Double class that adds a new method roundToDecimal which rounds the double value to a specified number of decimal places.

You can use this method on any Double value like this:

let myDouble = 3.14159265
print(myDouble.roundToDecimal(2))  // prints 3.14

Here’s an example of how to use an extension to add a new computed property to an existing struct:

extension CGSize {
  var area: CGFloat {
    return width * height
  }
}

This creates an extension for the built-in CGSize struct that adds a new computed property area which calculates the area of the size.

You can use this property on any CGSize value like this:

let mySize = CGSize(width: 2.0, height: 3.0)
print(mySize.area)  // prints 6.0

In this example, the function drawShape takes a Shape as an argument. It means that the function expects an object that conforms to the interface defined by the Shape class. But, you can pass in instances of a Circle or Square to the function.

As Circle and Square classes inherit from the Shape class and override the draw method, they can be used interchangeably with the Shape class.

This is the fundamental concept of polymorphism. It allows you to write more flexible and reusable code.

Structs vs Classes in Swift

In Swift, classes and structures are similar in many ways but have some key differences.

Classes

  • Classes are reference types, meaning they are passed by reference when assigned to a variable or constant.
  • Classes can have a deinitializer, which is code that gets executed when an instance of the class is deallocated from memory.
  • Classes can use inheritance, allowing them to inherit properties and methods from a superclass.
  • Classes can be marked as final, which means they cannot be subclassed.

Structures

  • Structures are value types, meaning they are passed by value when assigned to a variable or constant.
  • Structures cannot have a deinitializer.
  • Structures cannot use inheritance, but can conform to protocols.
  • Structures do not need to be marked as final because they cannot be subclassed.

In general, it’s best to use a structure when the data is simple and does not need to be inherited and use a class when the data is more complex and will be inherited.

Here’s an example of when to use a class vs a struct:

class Person {
  var name: String
  var age: Int
  
  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
}

struct Grade {
  var letter: String
  var points: Double
  
  init(letter: String, points: Double) {
    self.letter = letter
    self.points = points
  }
}

In this example, a Person class is used to store data about a person, since it’ll likely have more complex behavior and methods and may be inherited in the future. A Grade struct is used to store data about a grade, since it’s a simple value and doesn’t need to be inherited.

Use Key Paths in Swift

Key paths in Swift are a way to refer to the properties of objects in a type-safe manner.

They allow you to dynamically extract and set the values of a property by providing a path to the property, rather than hard-coding the property name as a string. They are particularly useful when used in combination with functions like map and compactMap to extract values from an array of objects. KeyPaths can also be used with classes, structs, and enums that conform to the Codable protocol to encode and decode custom data types.

The key path syntax in Swift consists of two parts:

The \ operator: This operator is used to create a key path expression. It is followed by the property or properties that you want to reference in the key path. For example: \Person.address.street creates a key path that references the street property of the Address struct, which is a property of the Person struct. The [keyPath: xxx] subscript syntax: This syntax is used to access the property’s value using the key path. The keyPath argument specifies the key path to the property, and xxx is a reference to the key path expression created using the \ operator. For example: person[keyPath: streetKeyPath] accesses the value of the street property of the Person struct using the key path stored in the streetKeyPath constant. Here’s a simple example of using key paths in Swift:


struct Person {
  var name: String
  var address: Address
}

struct Address {
  var street: String
  var city: String
}
let person = Person(name: "John Doe", address: Address(street: "1 Main St", city: "San Francisco"))

let streetKeyPath = \Person.address.street
let street = person[keyPath: streetKeyPath] // "1 Main St"

The \ in \Person.address.street creates a key path to a specific property of an object. In this case, it creates a key path to the street property of the Address struct, which is a property of the Person struct.

The value of the street property can then be accessed using the key path with the person[keyPath: streetKeyPath] syntax.

Understand Static Functions & Properties in Swift

Static functions and properties are class-level members that belong to the type itself, rather than an instance of the type. This means that they can be called without having to create an instance of the class or struct. In Swift, the keyword static is used to define a static function or property.

Static functions are useful for sharing functionality across all instances of a type. For example, you could create a static function in a math class that calculates the square of a number. This function can be called on the type itself, rather than having to create an instance of the math class.

Here’s an example code that demonstrates a static function in Swift:

struct Math {
  static func square(number: Int) -> Int {
    return number * number
  }
}

let result = Math.square(number: 4)
print(result) // 16

Static properties are used to store class-level data that is shared across all instances of a type. For example, you could create a static property in a class to store the number of instances created. This property can be accessed by any instance of the class or by the class itself.

Here’s an example code that demonstrates a static property in Swift:

class Counter {
  static var count = 0

  init() {
    Counter.count += 1
  }
}

let c1 = Counter()
let c2 = Counter()
print(Counter.count) // 2

Static functions and properties are powerful tools in Swift for sharing functionality and data across all instances of a type. They can be used to create reusable code and store class-level data that is shared across all instances of a type.

Differences Between Static & Instance Functions in Swift

There are multiple fundemental differences between static and instance functions or properties in Swift:

Scope: Static functions and properties belong to the type itself, rather than to instances of the type. They can be called or accessed without creating an instance of the type. Instance functions and properties belong to instances of the type, and they must be called or accessed on an instance of the type.

Syntax: Static functions and properties are declared using the static keyword in front of the function or property definition. Instance functions and properties are declared without the static keyword.

Functionality: Static functions and properties can be used to encapsulate logic that is not dependent on individual instances of the type, and that is shared by all instances of the type. Instance functions and properties can be used to encapsulate logic that is dependent on individual instances of the type, and that is specific to each instance.

Accessibility: Static functions and properties can be accessed from anywhere in the program, regardless of the context or the visibility of the type. Instance functions and properties can only be accessed from within the instance of the type, or from within a method of the type that operates on instances of the type.

Memory: Static functions and properties are stored in a single location in memory, and are shared by all instances of the type. Instance functions and properties are stored in separate locations in memory, one for each instance of the type.

Here’s an example to demonstrate the differences between static and instance functions and properties in Swift:

class Car {
  static var manufacturer = "Unknown"
  var color = "Unknown"

  static func describeManufacturer() -> String {
    return "The manufacturer is \(manufacturer)."
  }

  func describeColor() -> String {
    return "The color is \(color)."
  }
}

let redCar = Car()
redCar.color = "Red"

let greenCar = Car()
greenCar.color = "Green"

Car.manufacturer = "Toyota"

print(Car.describeManufacturer()) // "The manufacturer is Toyota."
print(redCar.describeColor()) // "The color is Red."
print(greenCar.describeColor()) // "The color is Green."

This code defines a class Car with two properties, manufacturer and color, and two methods, describeManufacturer() and describeColor().

manufacturer is a static property with a default value of “Unknown”. color is an instance property with a default value of “Unknown”.

describeManufacturer() is a static function that returns a string describing the manufacturer of the car. describeColor() is an instance function that returns a string describing the color of the car.

The code then creates two instances of Car, redCar and greenCar, and sets their color properties to “Red” and “Green” respectively.

The code sets the manufacturer property of Car to “Toyota” and then prints the results of calling describeManufacturer() and describeColor() on Car and on both instances, redCar and greenCar.

Combine Static Functions & Properties to Create Singleton Objects

A singleton is a design pattern in which a class has only one instance, and provides a global point of access to that instance. Singletons can be useful for managing shared state or resources, such as database connections, network sockets, or shared configuration data.

In Swift, you can combine static functions and properties to create singleton objects. To create a singleton, you can define a class with a private initializer and a static property that holds a single instance of the class.

Here’s an example of a Settings class that implements a singleton pattern:


class Settings {
  static let shared = Settings()
  private init() {}

  var fontSize: Double = 14.0
}

let settings = Settings.shared
settings.fontSize = 16.0

let anotherSettings = Settings.shared
print(anotherSettings.fontSize) // 16.0

In this example, Settings has a private initializer, which means that it cannot be instantiated outside of the class. shared is a static property that holds a single instance of Settings. By making the initializer private, you prevent the class from being instantiated from outside of the class, ensuring that there is only one instance.

To access the shared instance, you use the Settings.shared property. Both settings and anotherSettings refer to the same instance, so changing fontSize in one instance also changes it in the other.

This demonstrates how you can use static functions and properties to create singleton objects in Swift, making it easy to manage shared state or resources that need to be accessible from multiple parts of your code.

Define Generic Types

Written by Team Kodeco In Swift, generic types allow you to write flexible and reusable code by abstracting away specific types. A generic type is defined by placing one or more placeholder types, called type parameters, within angle brackets (< >).

Here’s an example of how to define a generic type in Swift:


struct Stack<Element> {
  var items = [Element]()

  mutating func push(_ item: Element) {
    items.append(item)
  }

  mutating func pop() -> Element? {
    return items.popLast()
  }
}



var stackOfStrings = Stack<String>()
stackOfStrings.push("hello")
stackOfStrings.push("world")
if let item = stackOfStrings.pop() {
  print(item) // prints "world
}

var stackOfInts = Stack<Int>()
stackOfInts.push(1)
stackOfInts.push(2)
if let item = stackOfInts.pop() {
  print(item) // prints "2
}

In this example, the Stack struct is defined as a generic type, with a single type parameter Element. The type parameter is used to specify the type of elements that the stack holds. This allows the Stack struct to be used with any type, such as String or Int, as shown in the example above.

When creating a new instance of a generic type, you can specify the actual types to use for the type parameters by providing them within angle brackets after the type name. In the examples above, you used Stack and Stack to create instances of the Stack struct that hold String and Int elements, respectively.

Use Multiple Type Parameters on Swift Generics It’s possible to specify multiple type parameters by separating them with commas. For example:


struct Pair<T, U> {
  let first: T
  let second: U
}

Use Constraints on Swift Generics You can specify constraints on the type parameters. For example, to restrict the type parameter to be a subclass of a certain class or to conform to a certain protocol:

protocol Flyable {
  func fly()
}

class Bird: Flyable {
  func fly() {
    print("I can fly")
  }
}

struct Flight<T: Flyable> {
  let flyingObject: T
}

let flight = Flight(flyingObject: Bird())
flight.flyingObject.fly() // prints "I can fly"

Use Associated Types with Swift Generics

In Swift, you can use associated types to define a placeholder name for a type within a protocol and that type can be specified when the protocol is adopted. To define an associated type, you use the associatedtype keyword before the placeholder type name.

Here’s an example of how to use associated types with generics in Swift:

protocol Container {
  associatedtype Item
  var items: [Item] { get set }
  mutating func append(_ item: Item)
  mutating func pop() -> Item
  var count: Int { get }
}

struct Stack<T>: Container {
  var items = [T]()
  mutating func append(_ item: T) {
    items.append(item)
  }
  mutating func pop() -> T {
    return items.removeLast()
  }
  var count: Int {
    return items.count
  }
}

func printAndPop<U: Container>(container: inout U) {
  for _ in 0..<container.count {
    print(container.pop())
  }
}

var intStack = Stack<Int>()
intStack.append(1)
intStack.append(2)
intStack.append(3)
printAndPop(container: &intStack)

var stringStack = Stack<String>()
stringStack.append("Alice")
stringStack.append("Bob")
stringStack.append("Candice")
printAndPop(container: &stringStack)
// Output: 3
//         2
//         1
//         Candice
//         Bob
//         Alice

In this example, the Container protocol defines an associated type Item and several methods that operate on an array of Items. The Stack struct conforms to the Container protocol and specifies that its Item is of type T. This allows the Stack struct to be used with any type.

The printAndPop function takes a generic U that conforms to the Container protocol and a mutable reference to a U. It uses the count property and the pop() method to remove and print all items in the container.

The example also shows how to use the Stack struct with both Int and String types, demonstrating the flexibility and reusability of using associated types with generics in Swift.

Use Where Clauses with Swift Generics

In Swift, you can use where clauses to specify additional requirements for generic types and associated types. A where clause is added by placing the where keyword after the type parameter list, followed by one or more constraints.

Here’s an example of how to use where clauses with Swift generics:

protocol Container {
  associatedtype Item
  var items: [Item] { get set }
  mutating func append(_ item: Item)
  mutating func pop() -> Item
  var count: Int { get }
}

struct Stack<T>: Container {
  var items = [T]()
  mutating func append(_ item: T) {
    items.append(item)
  }
  mutating func pop() -> T {
    return items.removeLast()
  }
  var count: Int {
    return items.count
  }
}

func popAllAndTestMatch<C1: Container, C2: Container>(
  _ someContainer: inout C1,
  _ anotherContainer: inout C2
) -> Bool where C1.Item == C2.Item, C1.Item: Equatable {
  if someContainer.count != anotherContainer.count {
    return false
  }
  for _ in 0..<someContainer.count {
    if someContainer.pop() != anotherContainer.pop() {
      return false
    }
  }
  return true
}

var stackOfStrings1 = Stack<String>()
stackOfStrings1.append("uno")
stackOfStrings1.append("dos")
stackOfStrings1.append("tres")

var stackOfStrings2 = Stack<String>()
stackOfStrings2.append("uno")
stackOfStrings2.append("dos")
stackOfStrings2.append("tres")

if popAllAndTestMatch(&stackOfStrings1, &stackOfStrings2) {
  print("All items match.")
} else {
  print("Not all items match.")
}
// Output: All items match

In this example, the popAllAndTestMatch function is a generic function that takes two containers, C1 and C2, as its parameters. The function uses where clauses to specify that the items in both containers must be of the same type (C1.Item == C2.Item) and that the items must be equatable (C1.Item: Equatable).

The function compares the two containers by popping items from each container and comparing them. If the number of items in the two containers isn’t the same, the function returns false. If the number of items is the same, the function pops items from each container one by one and compares them. If all items are the same, the function returns true.

In this example, you created two Stack structures with the same string values, then pass them as arguments to the popAllAndTestMatch function. Since the function returns true, indicating that all items match, the output is “All items match.”

It’s important to note that the where clause is used to create constraints on the type parameter, allowing you to use certain methods or properties on the type, in this case the Equatable protocol. This can make your code more flexible and reusable.

Use Generic Subscripts

In Swift, you can use generic subscripts to define a subscript that can be used with any type of collection. A generic subscript is defined by using the subscript keyword, followed by a generic parameter list and a return type.

Here’s an example of how to use generic subscripts in Swift:

struct Stack<Element> {
  var items = [Element]()
  mutating func push(_ item: Element) {
    items.append(item)
  }

  mutating func pop() -> Element? {
    guard !items.isEmpty else {
      return nil
    }
    return items.removeLast()
  }

  subscript<Indices: Sequence>(
    indices: Indices
  ) -> [Element]
  where Indices.Iterator.Element == Int {
    var result = [Element]()
    for index in indices {
      guard index < items.count else {
        continue
      }
      result.append(items[index])
    }
    return result
  }
}

var stack = Stack<String>()
stack.push("uno")
stack.push("dos")
stack.push("tres")

print(stack[[0,2]]) // Prints ["uno", "tres"]

In this example, the Stack struct is defined as a generic struct, with a single generic parameter Element. The struct contains an array items of type [Element] and two mutating functions push(_:) and pop().

The generic subscript takes a single parameter indices of any type that conforms to the Sequence protocol and its Iterator.Element must be of type Int. The subscript then returns an array of elements that exist at the indices specified in the indices parameter and any index that is out of bound is ignored.

Extend Types with Generics

In Swift, it’s possible to extend existing types with genericss using the extension keyword. This allows you to add functionality to a type without modifying its original implementation.

Here’s an example of extending the Array type with a generic first(where:) function that returns the first element that satisfies a given predicate:

extension Array {
  func first<T>(where predicate: (Element) -> T?) -> T? {
    for element in self where predicate(element) != nil {
      return predicate(element)
    }
    return nil
  }
}

In this example, the first(where:) function takes a single argument, a closure that takes an element of the array and returns an optional of any type T. The function returns an optional of the same type T. The function iterates over the elements of the array and for the first element that satisfies the predicate, it returns the result of the predicate.

Here’s an example of how to use this function:

let numbers = [1, 2, 3, 4, 5, 6]
if let evenNumber = numbers.first(where: { $0 % 2 == 0 }) {
  print(evenNumber) // Output: "2"
}

In this example, you are using the first(where:) function to find the first even number in an array of integers. The closure passed as an argument to the function takes an element of the array and returns true if it’s even and false if it’s odd. The function iterates over the elements of the array and when it finds the first element that satisfies the predicate, it returns that element, which in this case is 2.

Since the first(where:) function uses generics, you can use this with other types of arrays as well:

let animals = ["zebra", "hamster", "dog", "cat", "anteater"]
if let firstAnimalWithShortName = animals.first(where: { $0.count <= 3}) {
  print(firstAnimalWithShortName)
}

You can also extend other types with generics, like classes, structs, enums and protocols, it’ll help you to add more functionality to them without modifying the original implementation.

Also, it’s important to note that this function isn’t part of the standard library but can be added to any project.

Recursive Constraints with Generics

Generics in Swift allow you to write code that is flexible and reusable by abstracting over types. Sometimes, you might need to define a generic type that has a recursive relationship with itself. In this case, you can use recursive constraints to define the relationship.

Here’s an example of a binary tree data structure that uses recursive constraints. A binary tree is a tree data structure in which each node has at most two children, which are referred to as the left child and the right child.

class BinaryTree<T> {
  var value: T
  var left: BinaryTree?
  var right: BinaryTree?
  
  init(_ value: T) {
    self.value = value
  }
}

In this example, the BinaryTree class is defined as a generic class with a type parameter T. The left and right properties are optional instances of BinaryTree, which means that each node in the tree can have at most two children.

However, this isn’t enough to constrain the type of children of the binary tree, since left and right are BinaryTree? which means that they can be of any types. You can add a constraint to the BinaryTree class to ensure that the type of the children is the same as the type of the parent.

class BinaryTree<T> {
  var value: T
  var left: BinaryTree<T>?
  var right: BinaryTree<T>?
  
  init(_ value: T) {
    self.value = value
  }
}

Now the class is constrained to only allow instances of BinaryTree with the same type as the parent.

In this way, you used recursive constraints to define a generic BinaryTree class that can have children of the same type as the parent. This allows you to create a binary tree with nodes of any type while maintaining the integrity of the data structure.

Metatypes in Swift

A meta type in Swift refers to the type that describes the type itself. The meta type of a class, structure, enumeration or protocol is written with a dot (.) followed by the keyword self. For example, the meta type of the class Vehicle would be Vehicle.self.

class Car {
  required init() {}
  func start() {
    print("Starting car")
  }
}

let metaType = Car.self

In this code, you define a class Car with a method start() that prints “Starting car” when called.

Next, you create a constant metaType that’s set to the meta type of Car using Car.self. The meta type of a class is a type that describes the class itself.

You can use the meta type to dynamically create instances of the class, access type-level properties and methods, and perform other type-related operations.

let instance = metaType.init()
instance.start() // Output: Starting car

Using Meta Types with Protocols Here’s an example of using the meta type to access functions defined in a protocol:

protocol Vehicle {
  init()
  func start()
}

class Car: Vehicle {
  required init() {}
  func start() {
    print("Starting car")
  }
}

let metaType: Vehicle.Type = Car.self

// Create an instance of the class using the meta type
let carInstance = metaType.init()

// Call the start() method of the instance
carInstance.start()   // Output: Starting car

In this code, you add an init() method to the Vehicle protocol, and Car implements it with the required keyword to indicate that it must be implemented by any class that conforms to the protocol.

Next, you create a constant metaType that is set to the meta type of Car, but you specify its type as Vehicle.Type. This allows you to access only the functions defined in the Vehicle protocol, not the functions defined in Car.

Finally, you create an instance of Car using the meta type by calling init() on the metaType and call the start() on the new instance.

Meta types are a way to access the type of a class, structure or enumeration in Swift. The meta type is a type that represents the type of the class, structure or enumeration. It can be used to create instances of the type, access its properties and call its methods. Meta types are a feature of the Swift language and aren’t tied to any particular framework.

Define Protocols in Swift

Protocols in Swift are a way to define a blueprint of methods, properties and other requirements that a class, struct, or enum must implement.

They allow you to define a common interface for different types of objects, making it easy to work with objects that have different implementations.

Here’s an example of how to define a protocol in Swift:

protocol Driveable {
  var speed: Double { get set }
  func start()
  func stop()
}

This defines a protocol called Driveable that requires any type that adopts it to have a property called speed (that is gettable and settable) and two methods called start() and stop().

You can now create a struct or a class that conforms to this protocol by implementing the required properties and methods:

struct Car: Driveable {
  var speed: Double
  func start() {
    print("Car started")
  }
  func stop() {
    print("Car stopped")
  }
}

This creates a struct called Car that conforms to the Driveable protocol and has a property speed and methods start() and stop().

You can now use the Car struct as a Driveable object like this:

func startDrivable(drivable: Driveable) {
  drivable.start()
}
startDrivable(drivable: Car(speed: 0)) // prints "Car started"

Defining Static Functions and Properties in Protocols Static functions and properties can also be defined within protocols in Swift. This allows for reusable code and shared data to be shared across all types that conform to the protocol.

To define a static function in a protocol, you use the static keyword followed by the function declaration. For example:

protocol Driveable {
  static func calculateFuelEfficiency(fuel: Double, distance: Double) -> Double
  static var topSpeed: Double { get }
}

struct SportsCar: Driveable {
  static func calculateFuelEfficiency(fuel: Double, distance: Double) -> Double {
    return distance / fuel
  }

  static var topSpeed: Double {
    return 150
  }
}

struct Sedan: Driveable {
  static func calculateFuelEfficiency(fuel: Double, distance: Double) -> Double {
    return distance / fuel
  }

  static var topSpeed: Double {
    return 120
  }
}

let sportsCarFuelEfficiency = SportsCar.calculateFuelEfficiency(fuel: 50, distance: 500)
print(sportsCarFuelEfficiency) // 10.0

let sedanFuelEfficiency = Sedan.calculateFuelEfficiency(fuel: 40, distance: 400)
print(sedanFuelEfficiency) // 10.0

print(SportsCar.topSpeed) // 150
print(Sedan.topSpeed) // 120

Conforming to Multiple Protocols in the Same Class It’s possible to use multiple protocols in the same class, struct or enum.

protocol Paintable {
  func paint(color: String)
}

class Truck: Driveable, Paintable {
  var speed: Double = 0
  func start() {
    print("Truck started")
  }
  func stop() {
    print("Truck stopped")
  }
  func paint(color: String) {
    print("Truck painted with color: \(color)")
  }
}

In this example, the Truck class conforms to both Driveable and Paintable protocols and it has to implement all the required methods and properties from both protocols.

Use Protocol Inheritance in Swift

In Swift, protocols can inherit from other protocols in a similar way to classes inheriting from superclasses. This allows you to build complex protocols by reusing existing ones.

Here is an example of how to use protocol inheritance:

protocol Shape {
  func draw() -> String
}

protocol Rectangle: Shape {
  var width: Double { get }
  var height: Double { get }
}

struct Square: Rectangle {
  let side: Double
  var width: Double { return side }
  var height: Double { return side }
  func draw() -> String {
    return "I am a square with side of length \(side)
  }
}

In this example, we define a Shape protocol that has a single draw method. We then define a Rectangle protocol that inherits from Shape and adds two properties, width and height. Finally, we define a Square struct that conforms to the Rectangle protocol.

You can use any protocol that inherits from another protocol in the same way as the parent protocol. So in this example, Square struct can be used wherever Rectangle protocol is used.

Use Protocol Extension in Swift

A protocol extension allows you to provide default implementations for the methods defined in a protocol. This can be useful when you want to add functionality to multiple types that conform to the same protocol.

Here’s an example of a protocol called Named that has a single requirement, a gettable name property:

protocol Named {
  var name: String { get }
}

We can create a struct and a class that conform to the Named protocol and provide their own implementation of the name property.

struct Person: Named {
  let name: String
}

class Dog: Named {
  let name: String
  init(name: String) {
    self.name = name
  }
}

Now, let’s say we want to add a method to the Named protocol that returns a greeting based on the name. We can do this by defining an extension to the Named protocol and providing a default implementation for the method.

extension Named {
  func sayHello() -> String {
    return "Hello, my name is \(name)."
  }
}

Now, all types that conform to the Named protocol will have access to the sayHello() method.

let john = Person(name: "John")
let spot = Dog(name: "Spot")
print(john.sayHello()) // Prints "Hello, my name is John.
print(spot.sayHello()) // Prints "Hello, my name is Spot.

In this example, the sayHello() method is defined in the protocol extension, but the name property is implemented by the conformers.

Note: If a conformer also provides an implementation of a method defined in a protocol extension, that conformer’s implementation will be used instead of the default implementation. This allows for the conformer to override the default implementation if necessary.

Use Protocol-oriented Programming in Swift

Protocol-Oriented Programming (POP) is a programming paradigm that emphasizes the use of protocols to define the blueprint of an object’s behavior, rather than inheritance. POP is a powerful technique for creating flexible and maintainable code in Swift.

Here’s an example of a protocol called Movable that defines the blueprint for an object that can move.

protocol Movable {
  func move()
}

extension Movable {
  func move() {
    print("Moving")
  }
}

You can then create a struct that conforms to the Movable protocol and implements the move() method.

struct Car: Movable {}

You can also create a class that conforms to the Movable protocol.

class Train: Movable {}

You can then create an array of Movable objects and call the move() method on each one, without knowing the specific type of the object:

let vehicles: [Movable] = [Car(), Train()]
for vehicle in vehicles {
  vehicle.move()
}
// Output: "Moving"
// Output: "Moving"

An analogy for Protocol-Oriented Programming would be a set of blueprints for building a house. The blueprints define the layout, the number of rooms and the plumbing and electrical systems. The builder can then build the house with these blueprints, but use different materials and decor to make it unique.

By using protocol-oriented programming, you can create code that is more flexible and maintainable. You can define the blueprint of an object’s behavior once in a protocol and then use that protocol to create multiple structs and classes that conform to it, each using the default implementation from the protocol. This allows you to write more reusable and testable code and makes it easier to add new functionality in the future.

Use Protocol Associated Types in Swift

In Swift, it’s possible to use associated types in a protocol to specify a placeholder type within the protocol. This allows a conforming type to provide a specific type for the associated type.

Here’s an example of how to use associated types in a protocol:

protocol Container {
  associatedtype Item
  var items: [Item] { get set }
  mutating func add(_ item: Item)
}

struct IntContainer: Container {
  var items = [Int]()
  mutating func add(_ item: Int) {
    items.append(item)
  }
}

var intContainer = IntContainer()
intContainer.add(3)
print(intContainer.items) // prints [3]

In this example, the Container protocol has an associated type called Item. The IntContainer struct conforms to the Container protocol and provides an implementation for the Item associated type, specifying that the items array should hold integers.

Here’s an example of a class that conforms to the Container protocol, but uses a different implementation for the Item associated type:

class StringContainer: Container {
  var items = [String]()
  func add(_ item: String) {
    items.append(item)
  }
}
var stringContainer = StringContainer()
stringContainer.add("hello")
print(stringContainer.items) // prints ["hello"]

In this example, the StringContainer class conforms to the Container protocol and provides an implementation for the Item associated type, specifying that the items array should hold strings.

Protocol associated types are useful when you want to create a protocol that describes a container, but you don’t know what type of items it will hold. It allows for the flexibility to use different types for the associated type depending on the implementation of the conforming type.

It’s important to note that when you use protocol associated types, the conforming type must provide an implementation for the associated type. Also, you can use the where clause to specify certain conditions that the associated type must conform to.

Use Self-requirement in Protocols in Swift

A protocol in Swift is a set of rules that a class, struct or enum must follow. Sometimes you need to make sure that the class or struct that conforms to a protocol must implement a certain method or property itself, instead of using an extension. This is where the Self keyword comes in.

Let’s take a look at an example. You have a protocol called SelfPrintable which requires a method called printSelf(). This method should return a string.

protocol SelfPrintable {
  func printSelf() -> String
}

Now you have a class called MyClass which conforms to the SelfPrintable protocol.

class MyClass: SelfPrintable {
  var name: String
  init(name: String) {
    self.name = name
  }
  func printSelf() -> String {
    return "My name is \(name)"
  }
}

Now let’s say you want to add a method to the SelfPrintable protocol that checks if two instances of the class that conforms to the protocol are equal. You can do this using an extension and the Self keyword.

extension SelfPrintable where Self: Equatable {
  func isEqual(to other: Self) -> Bool {
    return self == other
  }
}

The Self keyword in the where clause of the extension is used to specify that the extension should only be applied to types that conform to the SelfPrintable protocol and also conform to the Equatable protocol.

The isEqual(to:) function is defined in this extension and it uses the Self keyword in the parameter type to specify that the method should work with the same type that conforms to the SelfPrintable protocol. Inside the function, the self == other comparison is used to check if two instances of the conforming type are equal.

Finally, you’ll make MyClass conform to the Equatableprotocol and implement the == function.

class MyClass: SelfPrintable, Equatable {
  var name: String
  init(name: String) {
    self.name = name
  }
  func printSelf() -> String {
    return "My name is \(name)"
  }
  static func == (lhs: MyClass, rhs: MyClass) -> Bool {
    return lhs.name == rhs.name
  }
}

Now you can create two instances of MyClass and check if they are equal.

let myClassA = MyClass(name: "A")
let myClassB = MyClass(name: "B")
print(myClassA.isEqual(to: myClassB)) // prints "false"

In summary, using the Self keyword in a protocol’s requirement allows you to specify that a method or property must be implemented by the conforming type itself, not just by an extension. This can help to ensure that the class or struct that conforms to the protocol follows the rules of the protocol correctly. ####Use Delegation Pattern in Swift

In Swift, the delegation pattern is a way to separate responsibilities between objects. The delegation pattern involves:

One object, known as the delegate is responsible for handling certain events or actions Another object, known as the delegator, is responsible for initiating those events or actions. Here’s an example of how to use the delegation pattern in Swift:

protocol SchoolDelegate: AnyObject {
  func classDidStart()
  func classDidEnd()
}

class School {
  weak var delegate: SchoolDelegate?
  
  func startClass() {
    print("Class started.")
    delegate?.classDidStart()
  }
  
  func endClass() {
    print("Class ended.")
    delegate?.classDidEnd()
  }
}

class Student: SchoolDelegate {
  func classDidStart() {
    print("Opening notebook.")
  }
  
  func classDidEnd() {
    print("Packing up backpack.")
  }
}

let student = Student()
let school = School()
school.delegate = student
school.startClass()
school.endClass()
// Output: Class started.
//         Opening notebook.
//         Class ended.
//         Packing up backpack.

In this example, the School class is the delegator, responsible for starting and stopping classes. The Student class is the delegate, responsible for handling certain events when classes start or stop, such as opening their notebook or packing up their backpack.

Automatic Reference Counting

Automatic Reference Counting (ARC) is a memory management mechanism used in Swift to automatically manage the memory of objects.

ARC works by keeping track of the number of references to an object and automatically release it when there are no more references to it.

This way, objects that are no longer needed are automatically deallocated and objects that are still needed aren’t deallocated.

You don’t have to do anything special to use ARC - it works for you behind the scenes. Here is an example that shows you a bit about how it works:

class MyClass {
  var name: String
  init(name: String) {
    self.name = name
  }
  deinit {
    print("MyClass \(name) is being deinitialized")
  }
}

var myObject: MyClass? = MyClass(name: "object1")
myObject = nil
// Output: MyClass object1 is being deinitialized

In this example, you created an instance of the MyClass class and assign it to myObject. Then, you set it to nil, which releases the reference to the object and triggers its deinitialization.

When an object is deinitialized, it calls the deinitializer on an object (deinit). In this example, MyClass overwrites the deinitializer and prints out a message when it’s called, to help you better understand when ARC automatically deallocates the object (i.e. once there are no longer any references to the object).

Strong & Weak References in Swift

In Swift, references to objects can be either strong or weak.

  • A strong reference means that an object will not be deallocated as long as at least one strong reference to it exists.
  • A weak reference, on the other hand, doesn’t prevent an object from being deallocated. When an object has only weak references, it’ll be deallocated.

Here is an example of how to use strong and weak references in Swift:

class Child {
  var name: String
  weak var parent: Parent?
  init(name: String, parent: Parent) {
    self.name = name
    self.parent = parent
  }
  deinit {
    print("Child \(name) is being deinitialized")
  }
}

class Parent {
  var name: String
  var children: [Child] = []
  init(name: String) {
    self.name = name
  }
  func addChild(name: String) {
    let child = Child(name: name, parent: self)
    children.append(child)
  }
  deinit {
    print("Parent \(name) is being deinitialized")
  }
}

var parent: Parent? = Parent(name: "Sandy")
parent!.addChild(name: "Patrick")
parent!.addChild(name: "Emily")
parent!.addChild(name: "Joanna")
parent = nil

// Output: Parent Sandy is being deinitialized
//         Child Patrick is being deinitialized
//         Child Emily is being deinitialized
//         Child Joanna is being deinitialized

In this example, You have two classes: Child and Parent.

Each Child object has a weak reference to its parent, represented by the weak var parent: Parent? property. Each Parent object has a strong reference to its children, represented by the var children: [Child] property. When the parent variable is set to nil, the only remaining refererence to the parent variable is the references from its children, but those are weak, so ARC knows it’s safe to deallocate the parent.

Once the parent is deallocated, there are no longer any references to the children, so ARC can then deallocate them as well.

Beware of Retain Cycles

Consider what would have happened if weak var parent: Parent? wasn’t set to weak (and it was a strong reference instead).

In this case, when the parent variable is set to nil, the Parent object won’t be deallocated, because its children still have a strong reference to it.

This would cause a strong reference cycle, also known as a retain cycle, where both the Parent and Child objects will never be deallocated, and it’ll cause memory leak.

使用说明

  1. xxxx
  2. xxxx
  3. xxxx

参与贡献

  1. Fork 本仓库
  2. 新建 Feat_xxx 分支
  3. 提交代码
  4. 新建 Pull Request

特技

  1. 使用 Readme_XXX.md 来支持不同的语言,例如 Readme_en.md, Readme_zh.md
  2. Gitee 官方博客 blog.gitee.com
  3. 你可以 https://gitee.com/explore 这个地址来了解 Gitee 上的优秀开源项目
  4. GVP 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
  5. Gitee 官方提供的使用手册 https://gitee.com/help
  6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 https://gitee.com/gitee-stars/

空文件

简介

Many of the leading iOS libraries continue to be written for Objective-C, but I’m seeing new Swift libraries emerging regularly — almost daily at times. The trend is shifting toward Swift. You’re see 展开 收起
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Swift
1
https://gitee.com/xiyg/swift_common_lib.git
git@gitee.com:xiyg/swift_common_lib.git
xiyg
swift_common_lib
swift_common_lib
master

搜索帮助