26 min read

In this article, Jon Hoffman, the author of the book Mastering Swift 3, talks about how he was really excited when Apple announced that they were going to release a version of Swift for Linux, so he could use Swift for his Linux and embedded development as well as his Mac OS and iOS development. When Apple first released Swift 2.2 for Linux, he was very excited but was also a little disappointed because he could not read/write files, access network services, or use Grand Central Dispatch (GCD or libdispatch) on Linux like he could on Apple’s platforms. With the release of Swift 3, Apple has corrected this with the release of Swift’s core libraries.

(For more resources related to this topic, see here.)

In this article, you will learn about the following topics:

  • What are the Swift core libraries
  • How to use Apple’s URL loading system
  • How to use the Formatter classes
  • How to use the File Manager class

The Swift Core Libraries are written to provide a rich set of APIs that are consistent across the various platforms that Swift supports. By using these libraries, developers will be able to write code that will be portable to all platforms that Swift supports. These libraries provide a higher level of functionality as compared to the Swift standard library.

The core libraries provide functionality in a number of areas, such as:

  • Networking
  • Unit testing
  • Scheduling and execution of work (libdispatch)
  • Property lists, JSON parsing, and XML parsing
  • Support for dates, times, and calendar calculations
  • Abstraction of OS-specific behavior
  • Interaction with the file system
  • User preferences

We are unable to cover all of the Core Libraries in this single article; however, we will look at some of the more useful ones. We will start off by looking at Apple’s URL loading system that is used for network development.

Apple’s URL loading system

Apple’s URL loading system is a framework of classes available to interact with URLs. We can use these classes to communicate with services that use standard Internet protocols. The classes that we will be using in this section to connect to and retrieve information from REST services are as follows:

  • URLSession: This is the main session object.
  • URLSessionConfiguration: This is used to configure the behavior of the URLSession object.
  • URLSessionTask: This is a base class to handle the data being retrieved from the URL. Apple provides three concrete subclasses of the URLSessionTask class.
  • URL: This is an object that represents the URL to connect to.
  • URLRequest: This class contains information about the request that we are making and is used by the URLSessionTask service to make the request.
  • HTTPURLResponse: This class contains the response to our request.

Now, let’s look at each of these classes a little more in depth so that we have a basic understanding of what each does.

URLSession

A URLSession object provides an API for interacting with various protocols such as HTTP and HTTPS. The session object, which is an instance of the URLSession, manages this interaction. These session objects are highly configurable, which allows us to control how our requests are made and how we handle the data that is returned.

Like most networking APIs, URLSession is asynchronous. This means that we have to provide a way to return the response from the service back to the code that needs it. The most popular way to return the results from a session is to pass a completion handler block (closure) to the session. This completion handler is then called when the service successfully responds or we receive an error. All of the examples in this article use completion handlers to process the data that is returned from the services.

URLSessionConfiguration

The URLSessionConfiguration class defines the behavior and policies to use when using the URLSession object to connect to a URL. When using the URLSession object, we usually create a URLSessionConfiguration instance first, because an instance of this class is required when we create an instance of the URLSession class.

The URLSessionConfiguration class defines three session types:

  • Default session configuration: Manages the upload and download tasks with default configurations
  • Ephemeral session configuration: This configuration behaves similar to the default session configuration, except that it does not cache anything to disk
  • Background session configuration: This session allows for uploads and downloads to be performed, even when the app is running in the background

It is important to note that we should make sure that we configure the URLSessionConfiguration object appropriately before we use it to create an instance of the URLSession class. When the session object is created, it creates a copy of the configuration object that we provide. Any changes made to the configuration object once the session object is created are ignored by the session. If we need to make changes to the configuration, we must create another instance of the URLSession class.

URLSessionTask

The URLSession service uses an instance of the URLSessionTask class to make the call to the service that we are connecting to. The URLSessionTask class is a base class, and Apple has provided three concrete subclasses that we can use:

  • URLSessionDataTask: This returns the response, in memory, directly to the application as one or more Data objects. This is the task that we generally use most often.
  • URLSessionDownloadTask: This writes the response directly to a temporary file.
  • URLSessionUploadTask: This is used for making requests that require a request body, such as a POST or PUT request.

It is important to note that a task will not send the request to the service until we call the resume() method.

URL

The URL object represents the URL that we are going to connect to. The URL class is not limited to URLs that represent remote servers, but it can also be used to represent a local file on disk. In this article, we will be using the URL class exclusively to represent the URL of the remote service that we are connecting to.

URLRequest

We use the URLRequest class to encapsulate our URL and the request properties. It is important to understand that the URLRequest class is used to encapsulate the necessary information to make our request, but it does not make the actual request. To make the request, we use instances of the URLSession and URLSessionTask classes.

HTTPURLResponse

The HTTPURLResponse class is a subclass of the URLResponse class that encapsulates the metadata associated with the response to a URL request. The HTTPURLResponse class provides methods for accessing specific information associated with an HTTP response. Specifically, this class allows us to access the HTTP header fields and the response status codes.

We briefly covered a number of classes in this section and it may not be clear how they all actually fit together; however, once you see the examples a little further in this article, it will become much clearer. Before we go into our examples, let’s take a quick look at the type of service that we will be connecting to.

REST web services

REST has become one of the most important technologies for stateless communications between devices. Due to the lightweight and stateless nature of the REST-based services, its importance is likely to continue to grow as more devices are connected to the Internet.

REST is an architecture style for designing networked applications. The idea behind REST is that instead of using complex mechanisms, such as SOAP or CORBA to communicate between devices, we use simple HTTP requests for the communication. While, in theory, REST is not dependent on the Internet protocols, it is almost always implemented using them. Therefore, when we are accessing REST services, we are almost always interacting with web servers in the same way that our web browsers interact with these servers.

REST web services use the HTTP POST, GET, PUT, or DELETE methods. If we think about a standard Create, Read, Update, Delete (CRUD) application, we would use a POST request to create or update data, a GET request to read data, and a DELETE request to delete data.

When we type a URL into our browser’s address bar and hit Enter, we are generally making a GET request to the server and asking it to send us the web page associated with that URL. When we fill out a web form and click the submit button, we are generally making a POST request to the server. We then include the parameters from the web form in the body of our POST request.

Now, let’s look at how to make an HTTP GET request using Apple’s networking API.

Making an HTTP GET request

In this example, we will make a GET request to Apple’s iTunes search API to get a list of items related to the search term “Jimmy Buffett”. Since we are retrieving data from the service, by REST standards, we should use a GET request to retrieve the data.

While the REST standard is to use GET requests to retrieve data from a service, there is nothing stopping a developer of a web service from using a GET request to create or update a data object. It is not recommended to use a GET request in this manner, but just be aware that there are services out there that do not adhere to the REST standards.

The following code makes a request to Apple’s iTunes search API and then prints the results to the console:

public typealias dataFromURLCompletionClosure = (URLResponse?, Data?) -> Void
    
public func sendGetRequest (
    _ handler: @escaping dataFromURLCompletionClosure) {

    let sessionConfiguration = URLSessionConfiguration.default;    
    let urlString =
        "https://itunes.apple.com/search?term=jimmy+buffett"
        
    if let encodeString =
        urlString.addingPercentEncoding(
            withAllowedCharacters: CharacterSet.urlQueryAllowed),
        let url = URL(string: encodeString) {
        
        var request = URLRequest(url:url)
        request.httpMethod = "GET"
        let urlSession = URLSession(
            configuration:sessionConfiguration, delegate: nil, 
   delegateQueue: nil)

        let sessionTask = urlSession.dataTask(with: request) {
            (data, response, error) in
                
            handler(response, data)
        }
        sessionTask.resume()
    }
}

We start off by creating a type alias named DataFromURLCompletionClosure. The DataFromURLCompletionClosure type will be used for both the GET and POST examples of this article. If you are not familiar with using a typealias object to define a closure type.

We then create a function named sendGetRequest(), which will be used to make the GET request to Apple’s iTunes API. This function accepts one argument named handler, which is a closure that conforms to the DataFromURLCompletionClosure type. The handler closure will be used to return the results from the request.

Stating with Swift 3, the default for closure arguments to functions is not escaping, which means that, by default, the closures argument cannot escape the function body. A closure is considered to escape a function when that closure, which is passed as an argument to the function, is called after the function returns. Since the closure will be called after the function returns, we use the @escaping attribute before the parameter type to indicate it is allowed to escape.

Within our sendGetRequest() method, we begin by creating an instance of the URLSessionConfiguration class using the default settings. If we need to, we can modify the session configuration properties after we create it, but in this example, the default configuration is what we want.

After we create our session configuration, we create the URL string. This is the URL of the service we are connecting to. With a GET request, we put our parameters in the URL itself. In this specific example, https://itunes.apple.com/search is the URL of the web service. We then follow the web service URL with a question mark (?), which indicates that the rest of the URL string consists of parameters for the web service.

The parameters take the form of key/value pairs, which means that each parameter has a key and a value. The key and value of a parameter, in a URL, are separated by an equals sign (=). In our example, the key is term and the value is jimmy+buffett. Next, we run the URL string that we just created through the addingPercentEncoding() method to make sure our URL string is encoded properly. We use the CharacterSet.urlQueryAllowed character set with this method to ensure we have a valid URL string.

Next, we use the URL string that we just built to create a URL instance named url. Since we are making a GET request, this URL instance will represent both the location of the web service and the parameters that we are sending to it.

We create an instance of the URLRequest class using the URL instance that we just created. In this example, we set the HTTPMethod property; however, we can also set other properties, such as the timeout interval or add items to our HTTP header.

Now, we use the sessionConfiguration constant that we created at the beginning of the sendGetRequest() function to create an instance of the URLSession class. The URLSession class provides the API that we will use to connect to Apple’s iTunes search API. In this example, we use the dataTask(with:) method of the URLSession instance to return an instance of the URLSessionDataTask type named sessionTask.

The sessionTask instance is what makes the request to the iTunes search API. When we receive the response from the service, we use the handler callback to return both the URLResponse object and the Data object. The URLResponse contains information about the response, and the Data instance contains the body of the response.

Finally, we call the resume() method of the URLSessionDataTask instance to make the request to the web service. Remember, as we mentioned earlier, a URLSessionTask instance will not send the request to the service until we call the resume() method.

Now, let’s look at how we would call the sendGetRequest() function. The first thing we need to do is to create a closure that will be passed to the sendGetRequest() function and called when the response from the web service is received. In this example, we will simply print the response to the console. Since the response is in the JSON format, we could use the JSONSerialization class, which we will see later in this article, to parse the response; however, since this section is on networking, we will simply print the response to the console. Here is the code:

let printResultsClosure: dataFromURLCompletionClosure = {

    if let data = $1 {
        let sString = let sString = String(data: data, 
                encoding: String.Encoding(rawValue: 
                String.Encoding.utf8.rawValue))
        print(sString)
    } else {
        print("Data is nil")
    }
}

We define this closure, named printResultsClosure, to be an instance of the DataFromURLCompletionClosure type. Within the closure, we unwrap the first parameter and set the value to a constant named data. If the first parameter is not nil, we convert the data constant to an instance of the String class, which is then printed to the console.

Now, let’s call the sendGetRequest() method with the following code:

let aConnect = HttpConnect()
aConnect.sendGetRequest(printResultsClosure)

This code creates an instance of the HttpConnect class and then calls the sendGetRequest() method, passing the printResultsClosure closure as the only parameter. If we run this code while we are connected to the Internet, we will receive a JSON response that contains a list of items related to Jimmy Buffett on iTunes.

Now that we have seen how to make a simple HTTP GET request, let’s look at how we would make an HTTP POST request to a web service.

Making an HTTP POST request

Since Apple’s iTunes, APIs use GET requests to retrieve data. In this section, we will use the free http://httpbin.org service to show you how to make a POST request. The POST service that http://httpbin.org provides can be found at http://httpbin.org/post. This service will echo back the parameters that it receives so that we can verify that our request was made properly.

When we make a POST request, we generally have some data that we want to send or post to the server. This data takes the form of key/value pairs. These pairs are separated by an ampersand (&) symbol, and each key is separated from its value by an equals sign (=). As an example, let’s say that we want to submit the following data to our service:

firstname: Jon
lastname: Hoffman
age: 47 years

The body of the POST request would take the following format:

firstname=Jon&lastname=Hoffman&age=47

Once we have the data in the proper format, we will then use the dataUsingEncoding() method, as we did with the GET request to properly encode the POST data.

Since the data going to the server is in the key/value format, the most appropriate way to store this data, prior to sending it to the service, is with a Dictionary object. With this in mind, we will need to create a method that will take a Dictionary object and return a string object that can be used for the POST request. The following code will do that:

private func dictionaryToQueryString(_ dict: [String : String]) -> String {
    var parts = [String]()
    for (key, value) in dict {
        let part : String = key + "=" + value
        parts.append(part);
    }
    return parts.joined(separator: "&")
}

This function loops through each key/value pair of the Dictionary object and creates a String object that contains the key and the value separated by the equals sign (=). We then use the joinWithSeperator() function to join each item in the array, separated by the specified sting. In our case, we want to separate each string with the ampersand symbol (&). We then return this newly created string to the code that called it.

Now, let’s create the sendPostRequest() function that will send the POST request to the http://httpbin.org post service. We will see a lot of similarities between this sendPostRequest() function and the sendGetRequest() function, which we showed you in the Making an HTTP GET request section. Let’s take a look at the following code:

public func sendPostRequest(_ handler: @escaping 
dataFromURLCompletionClosure) {
        
    let sessionConfiguration =
    URLSessionConfiguration.default
        
    let urlString = "http://httpbin.org/post"
    if let encodeString =
        urlString.addingPercentEncoding(
            withAllowedCharacters: CharacterSet.urlQueryAllowed),
        let url = URL(string: encodeString) {
            
        var request = URLRequest(url:url)
        request.httpMethod = "POST"
        let params = dictionaryToQueryString(["One":"1 and 1", 
"Two":"2 and 2"])
        request.httpBody = params.data(
            using: String.Encoding.utf8, allowLossyConversion: 
true)
                
        let urlSession = URLSession(
            configuration:sessionConfiguration, delegate: nil, 
   delegateQueue: nil)
            
        let sessionTask = urlSession.dataTask(with: request) {
            (data, response, error) in
                
            handler(response, data)
        }
        sessionTask.resume()
    }
}

This code is very similar to the sendGetRequest() function that we saw earlier in this section. The two main differences are the httpMethod of the URLRequest is set to POST rather than GET and how we set the parameters. In this function, we set the httpBody property of the URLRequest instance to the parameters we are submitting.

Now that we have seen how to use the URL Loading System, let’s look at how we can use Formatters.

Formatter

Formatter is an abstract class that declares an interface for an object that creates, converts, or validates a human readable form of some data. The types that subclass the Formatter class are generally used when we want to take a particular object, like an instance of the Date class, and present the value in a form that the user of our application can understand.

Apple has provided several concrete implementations of the Formatter class, and in this section we will look at two of them. It is important to remember that the formatters that Apple provides will provide the proper format for the default locale of the device the application is running on.

We will start off by looking at the DateFormatter type.

DateFormatter

The DateFormatter class is a subclass of the Formatter abstract class that can be used to convert a Date object into a human readable string. It can also be used to convert a String representation of a date into a Date object. We will look at both of these use cases in this section. Let’s begin by seeing how we could covert a Date object into a human readable string.

The DateFormatter type has five predefined styles that we can use when we are converting a Date object to a human readable string. The following chart shows what the five styles look like for an en-US locale.

DateFormatter Style

Date

Time

.none

No Format

No Format

.short

12/25/16

6:00 AM

.medium

Dec 25, 2016

6:00:00 AM

.long

December 25, 2016

6:00:00 AM EST

.full

Sunday December 25, 2016

6:00:00 AM Eastern Standard Time

The following code shows how we would use the predefined DateFormatter styles:

let now = Date()

let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium

let dateStr = formatter.string(from: now)

We use the string(from:) method to convert the now date to a human readable string. In this example, for the en-US locale, the dateStr constant would contain text similar to “Aug 19, 2016, 6:40 PM”.

There are numerous times when the predefined styles do not meet our needs. For those times, we can define our own styles using a custom format string. This string is a series of characters that the DateFormatter type knows are stand-ins for the values we want to show. The DateFormatter instance will replace these stand-ins with the appropriate values. The following table shows some of the formatting values that we can use to format our Date objects:

Stand-in Format

Description

Example output

yy

Two digit year

16, 14, 04

yyyy

Four digit year

2016, 2014, 2004

MM

Two digit month

06, 08, 12

MMM

Three letter Month

Jul, Aug, Dec

MMMM

Full month name

July, August

dd

Two digit day

10, 11, 30

EEE

Three letter Day

Mon, Sat, Sun

EEEE

Full day

Monday, Sunday

a

Period of day

AM, PM

hh

Two digit hour

02, 03, 04

HH

Two digit hour for 24 hour clock

11, 14, 16

mm

Two digit minute

30, 35, 45

ss

Two digit seconds

30, 35, 45

The following code shows how we would use these custom formatters:

let now = Date()
let formatter2 = DateFormatter()
formatter2.dateFormat = "YYYY-MM-dd HH:mm:ss"

let dateStr2 = formatter2.string(from: now)

We use the string(from:) method to convert the now date to a human readable string. In this example, for the en-US locale, the dateStr2 constant would contain text similar to 2016-08-19 19:03:23.

Now let’s look at how we would take a formatted date string and convert it to a Date object. We will use the same format string that we used in our last example:

formatter2.dateFormat = "YYYY-MM-dd HH:mm:ss"

let dateStr3 = "2016-08-19 16:32:02"
let date = formatter2.date(from: dateStr3)

In this example, we took the human readable date string and converted it to a Date object using the date(from:) method. If the format of the human readable date string does not match the format specified in the DateFormatter instance, then the conversion will fail and return nil.

Now let’s take a look at the NumberFormatter type.

NumberFormatter

The NumberFormatter class is a subclass of the Formatter abstract class that can be used to convert a number into a human readable string with a specified format. This formatter is especially useful when we want to display a currency string since it will convert the number to the proper currency to the proper locale.

Let’s begin by looking at how we would convert a number into a currency string:

let formatter1 = NumberFormatter()
formatter1.numberStyle = .currency
let num1 = formatter1.string(from: 23.99)

In the previous code, we define our number style to be .currency, which tells our formatter that we want to convert our number to a currency string. We then use the string(from:) method to convert the number to a string. In this example, for the en-US locale, the num1 constant would contain the string “$23.99”.

Now let’s see how we would round a number using the NumberFormatter type. The following code will round the number to two decimal points:

let formatter2 = NumberFormatter()
formatter2.numberStyle = .decimal
formatter2.maximumFractionDigits = 2
let num2 = formatter2.string(from: 23.2357)

In this example, we set the numberStyle property to the .decimal style and we also define the maximum number of decimal digits to be two using the maximumFractionDigits property. We use the string(from:) method to convert the number to a string. In this example, the num2 constant will contain the string “23.24”.

Now let’s see how we can spell out a number using the NumberFormatter type. The following code will take a number and spell out the number:

let formatter3 = NumberFormatter()
formatter3.numberStyle = .spellOut
let num3 = formatter3.string(from: 2015)

In this example, we set the numberStyle property to the .spellout style. This style will spell out the number. For this example, the num3 constant will contain the string two thousand fifteen.

Now let’s look at how we can manipulate the file system using the FileManager class

FileManager

The file system is a complex topic and how we manipulate it within our applications is generally specific to the operating system that our application is running on. This can be an issue when we are trying to port code from one operating system to another. Apple has addressed this issue by putting the FileManager object in the core libraries. The FileManager object lets us examine and make changes to the file system in a uniform manner across all operating systems that Swift supports.

The FileManager class provides us with a shared instance that we can use. This instance should be suitable for most of our file system related tasks. We can access this shared instance using the default property.

When we use the FileManager object we can provide paths as either an instance of the URL or String types. In this section, all of our paths will be String types for consistency purposes.

Let’s start off by seeing how we could list the contents of a directory using the FileManager object. The following code shows how to do this:

let fileManager = FileManager.default

do {
    let path = "/Users/jonhoffman/"
    let dirContents = try fileManager.contentsOfDirectory(atPath: path)
    for item in dirContents {
        print(item);
    }
} catch let error {
    print("Failed reading contents of directory: (error)")
}

We start off by getting the shared instance of the FileManager object using the default property. We will use this same shared instance for all of our examples in this section rather than redefining it for each example. Next, we define our path and use it with the contentsOfDirectory(atPath:) method. This method returns an array of String types that contains the names of the items in the path. We use a for loop to list these items.

Next let’s look at how we would create a directory using the file manager. The following code shows how to do this:

do {
    let path = "/Users/jonhoffman/masteringswift/test/dir"
    try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true)
} catch let error {
    print("Failed creating directory, (error) ")
}

In this example, we use the createDirectory(atPath: withIntermediateDirectories:) method to create the directory. When we set the withIntermediateDirectories parameter to true, this method will create any parent directories that are missing. When this parameter is set to false, if any parent directories are missing the operation will fail. Now let’s look at how we would copy an item from one location to another:

do {
    let pathOrig = "/Users/jonhoffman/masteringswift/"
    let pathNew = "/Users/jonhoffman/masteringswift2/"
    try fileManager.copyItem(atPath: pathOrig, toPath: pathNew)
} catch let error {
    print("Failed copying directory, (error) ")
}

In this example, we use the copyItem(atPath: toPath:) method to copy one item to another location. This method can be used to copy either directories or files. If it is used for directories, the entire path structure below the directory specified by the path is copied. The file manager also has a method that will let us move an item rather than copying it. Let’s see how to do this:

do {
    let pathOrig = "/Users/jonhoffman/masteringswift2/"
    let pathNew = "/Users/jonhoffman/masteringswift3/"
    try fileManager.moveItem(atPath: pathOrig, toPath: pathNew)
} catch let error {
    print("Failed moving directory, (error) ")
}

To move an item, we use the moveItem(atPath: toPath:) method. Just like the copy example we just saw, the move method can be used for either files or directories. If the path specifies a directory, then the entire directory structure below that path will be moved. Now let’s see how we can delete an item from the file system:

do {
    let path = "/Users/jonhoffman/masteringswift/"
    try fileManager.removeItem(atPath: path)
} catch let error {
    print("Failed Removing directory, (error) ")
}

In this example, we use the removeItem(atPath:) method to remove the item from the file system. A word of warning, once you delete something it is gone and there is no getting it back.

Next let’s look at how we can read permissions for items in our file system. For this we will need to create a file named test.file, which our path will point to:

let path = "/Users/jonhoffman/masteringswift3/test.file"
if fileManager.fileExists(atPath: path) {
    let isReadable = fileManager.isReadableFile(atPath: path)
    let isWriteable = fileManager.isWritableFile(atPath: path)
    let isExecutable = fileManager.isExecutableFile(atPath: path)
    let isDeleteable = fileManager.isDeletableFile(atPath: path)
    print("can read (isReadable)")
    print("can write (isWriteable)")
    print("can execute (isExecutable)")
    print("can delete (isDeleteable)")
}

In this example, we use four different methods to read the file system permissions for the file system item. These methods are:

  • isReadableFile(atPath:): true if the file is readable
  • isWritableFile(atPath:):true if the file is writable
  • isExecutableFile(atPath:): true if the file is executable
  • isDeletableFile(atPath:): true if the file is deletable

If we wanted to read or write text files, we could use methods provided by the String type rather than the FileManager type. Even though we do not use the FileManager class, for this example, we wanted to show how to read and write text files. Let’s see how we would write some text to a file:

let filePath = "/Users/jonhoffman/Documents/test.file"
let outString = "Write this text to the file"
do {
    try outString.write(toFile: filePath, atomically: true, encoding: .utf8)
} catch let error  {
    print("Failed writing to path: (error)")
}

In this example, we start off by defining our path as a String type just as we did in the previous examples. We then create another instance of the String type that contains the text we want to put in our file. To write the text to the file we use the write(toFile: atomically: encoding:) method of the String instance. This method will create the file if needed and write the contents of the String instance to the file. It is just as easy to read a text file using the String type. The following example shows how to do this:

let filePath = "/Users/jonhoffman/Documents/test.file"
var inString = ""
do {
    inString = try String(contentsOfFile: filePath)
} catch let error {
    print("Failed reading from path: (error)")
}
print("Text Read: (inString)")

In this example, we use the contentsOfFile: initiator to create an instance of the String type that contains the contents of the file specified by the file path. Now that we have seen how to use the FileManager type, let’s look at how we would serialize and deserialize JSON documents using the JSONSerialization type.

Summary

In this article, we looked at some of the libraries that make up the Swift Core Libraries. While these libraries are only a small portion of the libraries that make up the Swift Core Libraries they are arguable some of the most useful libraries. To explore the other libraries that, you can refer to Apple’s GitHub page: https://github.com/apple/swift-corelibs-foundation.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here