NSURLSession is the native network operation library provided by iOS system. It provides a series of features related to network operation support, such as cache control, Cookie management, HTTP authentication processing and so on. It is a set of overall network operation processing solution.

The basic features of NSURLSession were described in detail in our previous article, the NSURLSession Network library – the gift of native systems. If you haven’t used this library before, you should check it out.

This time we will not introduce any basic concepts of NSURLSession, we will use a real download tool APP development to get a real feel for most features of NSURLSession. So stop gossiping and let’s get started

Create a project

First, we use Xcode to create the Project of our download tool APP, and click On the Xcode menu File -> New -> Project… Then in the window that pops up, select the project template as Tabbed Application:

Next, enter Product Name as downloader and select Swift as the project language:

Then click the Next button to select a suitable location for your project files.

After the project is created and the APP is run, we should see the default interface generated for us:

Build a model

To develop a download management tool, first we need to model the download task.

DownloadTask

We create a DownloadTask structure to represent the DownloadTask:

struct DownloadTask {
    
    var url: NSURL
    var localURL:NSURL?
    var taskIdentifier: Int
    var finished:Bool = false
    
    init(url:NSURL, taskIdentifier: Int) {
        
        self.url = url
        self.taskIdentifier = taskIdentifier
        
    }
    
}Copy the code

It contains four attributes, and the URL represents the URL address of the current download task. LocalURL indicates the location to save the download to the local file after success, and taskIdentifier is the unique identifier for the download task. Finished Indicates whether the download task is complete.

TaskManager

Once the model is established, we create a TaskManager class to manage the download tasks:

class TaskManager: NSObject, NSURLSessionDownloadDelegate {

    private var session:NSURLSession?
    
    var taskList:[DownloadTask] = [DownloadTask]()
    
    static var sharedInstance:TaskManager = TaskManager()
    
    override init() {
        
        super.init()
        
        let config = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("downloadSession")
        self.session = NSURLSession(configuration: config, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
        self.taskList = [DownloadTask]()
        self.loadTaskList()

    }Copy the code

Let’s take a look at the definition of the TaskManager, it inherits from NSObject, and implement the NSURLSessionDownloadDelegate protocol. This protocol is used to detect download progress, which we’ll cover later.

  • It also defines a propertyprivate var session:NSURLSession?As an instance of NSURLSession for all of our download tasks.
  • var taskList:[DownloadTask] = [DownloadTask]()This property is used to save and track all of our download tasks. Let’s use what we defined at the beginningDownloadTaskStructure representation.
  • static var sharedInstance:TaskManager = TaskManager()saidTaskManagerClass.

In the init () method, we use backgroundSessionConfigurationWithIdentifier initialize a background types of NSURLSession configuration information. This configuration allows the download session to continue even if the application is switched to the background.

We then initialize our NSURLSession with this configuration information object, specifying its proxy class, and the thread queue on which the proxy class resides. Then through:

self.taskList = [DownloadTask]()
self.loadTaskList()
Copy the code

These two lines of code initialize the application’s download task list, and self.loadTaskList() is also a method in the TaskManager class, which we’ll cover in more detail later.

Now that the TaskManager task management class is set up, we also need it to provide two task lists, one for downloading tasks and one for downloading tasks, so that our front-end UI can display the corresponding tasks to the user:

func unFinishedTask() -> [DownloadTask] {
    
    return taskList.filter{ task in
        
        return task.finished == false
        
    }
    
}

func finishedTask() -> [DownloadTask] {
    
    return taskList.filter { task in
        
        return task.finished
        
    }
    
}Copy the code

The unFinishedTask() and finishedTask() methods object the list of tasks being downloaded and the list of tasks that have been downloaded, respectively. And their implementation is very simple. We use the filter method provided by Array in Swift to filter the data in the list. The filter method iterates through the collection and provides a closure for each collection item, in which we simply return a bool indicating whether the current collection item meets the criteria.

With the two list methods provided, we need another pair of utility methods. Our download tasks need to be persisted, so that each time we reopen the APP, we will not lose the previously established tasks, which is also a necessary function of the download tool. Here, we convert the task information into JSON data and store it so that the task data can be restored when we open the APP next time:

func saveTaskList() { let jsonArray = NSMutableArray() for task in taskList { let jsonItem = NSMutableDictionary() jsonItem["url"] = task.url.absoluteString jsonItem["taskIdentifier"] = NSNumber(long: task.taskIdentifier) jsonItem["finished"] = NSNumber(bool: task.finished) jsonArray.addObject(jsonItem) } do { let jsonData = try NSJSONSerialization.dataWithJSONObject(jsonArray,  options: NSJSONWritingOptions.PrettyPrinted) NSUserDefaults.standardUserDefaults().setObject(jsonData, forKey: "taskList") NSUserDefaults.standardUserDefaults().synchronize() }catch { } } func loadTaskList() { if let jsonData:NSData = NSUserDefaults.standardUserDefaults().objectForKey("taskList") as? NSData { do { guard let jsonArray:NSArray = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.AllowFragments) as? NSArray else { return } for jsonItem in jsonArray { if let item:NSDictionary = jsonItem as? NSDictionary { guard let urlstring = item["url"] as? String else { return } guard let taskIdentifier = item["taskIdentifier"]? .longValue else { return } guard let finished = item["finished"]? .boolValue else { return } let downloadTask = DownloadTask(url: NSURL(string: urlstring)! , taskIdentifier: taskIdentifier) self.taskList.append(downloadTask) } } } catch { } } }Copy the code

The saveTaskList() and loadTaskList() methods are used to save and load task data, respectively. Both methods are implemented using JSON data and NSUserDefaults, which we won’t expand into in detail.

We need another method to create a new download task:

func newTask(url: String) { if let url = NSURL(string: url) { let downloadTask = self.session? .downloadTaskWithURL(url) downloadTask? .resume() let task = DownloadTask(url: url, taskIdentifier: downloadTask! .taskIdentifier) self.taskList.append(task) self.saveTaskList() } }Copy the code

The newTask method takes a url argument that represents the download address of our newTask, using self.session? DownloadTaskWithURL (URL) to create a new NSURLSession download task. Next, we call downloadTask? .resume() starts the task.

Once the DownloadTask is set up and we need to save its information, we need to instantiate a DownloadTask class we defined earlier and add it to our store list self.tasklist. Finally, call self.savetAskList () to save the task data to the persistence layer.

At this point, we have a basic mechanism in place to create and start download tasks. And, of course, we also need to download some processing tasks, such as when the download is complete the task status to mark again, and the task of real-time display download progress, this requires us to begin to implement the NSURLSessionDownloadDelegate agreement method of, now let’s perfect it:

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) { var fileName = "" for var i = 0; i < self.taskList.count; i++ { if self.taskList[i].taskIdentifier == downloadTask.taskIdentifier { self.taskList[i].finished = true fileName = self.taskList[i].url.lastPathComponent! } } if let documentURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first { let destURL = documentURL.URLByAppendingPathComponent(fileName) do { try NSFileManager.defaultManager().moveItemAtURL(location, toURL: destURL) } catch { } } self.saveTaskList() NSNotificationCenter.defaultCenter().postNotificationName(DownloadTaskNotification.Finish.rawValue, object: downloadTask.taskIdentifier) } func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) { } func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { let progressInfo = ["taskIdentifier": downloadTask.taskIdentifier, "totalBytesWritten": NSNumber(longLong: totalBytesWritten), "totalBytesExpectedToWrite": NSNumber(longLong: totalBytesExpectedToWrite)] NSNotificationCenter.defaultCenter().postNotificationName(DownloadTaskNotification.Progress.rawValue, object: progressInfo) }Copy the code

NSURLSessionDownloadDelegate protocol defines three methods:

URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL)
URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64)
URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)
Copy the code

DidFinishDownloadingToURL when download task is invoked, didResumeAtOffset when download task is invoked, didWriteData when there is a change in the download task schedule is invoked.

Let’s look at some of the didFinishDownloadingToURL implementation, first we traverse the whole self. TaskList list, find download we completed this task through taskIdentifier corresponding DownloadTask object, Then set its finished property to true and temporarily save the fileName of the download task through fileName.

The location parameter in this protocol method indicates the temporary location of the file after a successful download. Files downloaded using NSURLSession are stored in a temporary directory, and we need to move this file from the temporary directory to our APP’s persistent directory, which is what we are going to do:

if let documentURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first {
    
    let destURL = documentURL.URLByAppendingPathComponent(fileName)
    do {

        try NSFileManager.defaultManager().moveItemAtURL(location, toURL: destURL)
        
    } catch {
        
    }

    
}Copy the code

After the move is complete, we call self.savetAskList () to save the current task information. Finally, we send a notification message to refresh the front-end UI accordingly:

NSNotificationCenter.defaultCenter().postNotificationName(DownloadTaskNotification.Finish.rawValue, object: downloadTask.taskIdentifier)
Copy the code

So our didFinishDownloadingToURL method was completed. Pay attention to notice the name of the DownloadTaskNotification. Finish. RawValue is an enumeration values, using the enumeration to define the notification of the name of the benefits is to reuse is more convenient, let’s take a look at the definition of the enumeration:

enum DownloadTaskNotification: String {
    
    case Progress = "downloadNotificationProgress"
    case Finish = "downloadNotificationFinish"
    
}Copy the code

Two enumerations, Finish and Progress, are defined to indicate download completion and download Progress changes, respectively.

Let’s move on to the definition of the didWriteData method, which does nothing more than load the current task progress into a Dictionary and send notifications to the front end for processing:

let progressInfo = ["taskIdentifier": downloadTask.taskIdentifier,
                    "totalBytesWritten": NSNumber(longLong: totalBytesWritten),
                    "totalBytesExpectedToWrite": NSNumber(longLong: totalBytesExpectedToWrite)]

NSNotificationCenter.defaultCenter().postNotificationName(DownloadTaskNotification.Progress.rawValue, object: progressInfo)
Copy the code

At this point, we have built the basic model of the download tool, and then we can start to create the front-end UI, and we will see the effect.

Interact with the UI

Our download APP is tab-structured, as we saw when we first created the project. We can open the storyboard file for the project and see the current UI structure:

Two tabs, and each Tab is directly associated with a UIViewController. We need to modify the existing structure a little bit, so let’s add another layer of UIViewController between UIViewController and Tab.

Drag and drop two UinavigationControllers into the storyboard to disassociate the First View and Second View controllers from the Tab controller. Then associate these two controllers with the rootViewControllers of the two UInavigationControllers, and after associating the UINavigationController with the Tab controller, the modified structure is as follows:

Let’s go to the FirstViewController code (the controller of the first Tab) and add two properties to it:

class FirstViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { private var mainTableView: UITableView? private var taskList: [DownloadTask]? / /... }Copy the code

FirstViewController implements both the UITableViewDelegate and UITableViewDataSource protocols. The mainTableView is used to display our list of tasks, and the taskList represents the list of tasks we are currently displaying.

Next, we implement the ViewDidLoad method:

Override func viewDidLoad () {super viewDidLoad () the self. The title = "is downloading" self. NavigationController? .navigationBar.translucent = false self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Add, target: self, action: Selector("addTask")) self.mainTableView = UITableView(frame: CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)) self.mainTableView?.delegate = self self.mainTableView?.dataSource = self self.view.addSubview(self.mainTableView!) NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("reloadData"), name: DownloadTaskNotification.Finish.rawValue, object: nil) }Copy the code

The title property of the controller is set here, and the transparency of the navigationBar. I then created a navigation bar button to add tasks. The UITableView object is then created to display the list of tasks. In the end, will be our registered to DownloadTaskNotification reloadData method of this class. The Finish. Each time a task is downloaded, we reload the list.

Next, we create a method to reload the data:

override func viewDidAppear(animated: Bool) { self.reloadData() } func reloadData() { taskList = TaskManager.sharedInstance.unFinishedTask() self.mainTableView? .reloadData() }Copy the code

Every time in viewDidAppear, we call self.reloadData() to reload the data, ReloadData () method is used in our previously defined TaskManager. SharedInstance. UnFinishedTask () method to obtain is to download the task list. And then finally call self.mainTableView? The.reloadData() method refreshes the display of the UITableView.

Let’s implement a series of proxy methods for UITableView:

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    
    return 1
    
}

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    
    return 70.0
    
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
    return self.taskList == nil ? 0 : self.taskList!.count
    
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    
    var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as? DownloadTaskCell
    
    if cell == nil {
        
        cell = DownloadTaskCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
        
    }
    
    cell?.updateData((self.taskList?[indexPath.row])!)
    return cell!
    
}Copy the code

Are more commonly used logic, do not need to expand too much. The only thing to note here is that we use our own custom DownloadTaskCell class, which we will create next:

class DownloadTaskCell: UITableViewCell { var labelName: UILabel = UILabel() var labelSize: UILabel = UILabel() var labelProgress: UILabel = UILabel() var downloadTask: DownloadTask? / /... }Copy the code

This class inherits from the UITableViewCell, which defines three Uilabels that display the name of the download task, the completed status, and the download progress. A downloadTask property is also defined to indicate which downloadTask is represented by the current cell.

Let’s take a look at its initialization method:

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    
    self.addSubview(labelName)
    self.addSubview(labelSize)
    self.addSubview(labelProgress)
    
    self.labelName.font = UIFont.systemFontOfSize(14)
    self.labelSize.font = UIFont.systemFontOfSize(14)
    self.labelProgress.font = UIFont.systemFontOfSize(14)
    
    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("updateProgress:"), name: DownloadTaskNotification.Progress.rawValue, object: nil)
    
}Copy the code

Add UILabel to view and set up their font, finally will be registered for DownloadTaskNotification updateProgress method. Notice the Progress, so that we in the download of task schedule change over time, you can update the UI shows.

Next, define the updateProgress method:

func updateProgress(notification: NSNotification) { guard let info = notification.object as? NSDictionary else { return } if let taskIdentifier = info["taskIdentifier"] as? NSNumber { if taskIdentifier.integerValue == self.downloadTask?.taskIdentifier { guard let written = info["totalBytesWritten"] as? NSNumber else { return } guard let total = info["totalBytesExpectedToWrite"] as? NSNumber else { return } let formattedWrittenSize = NSByteCountFormatter.stringFromByteCount(written.longLongValue, countStyle: NSByteCountFormatterCountStyle.File) let formattedTotalSize = NSByteCountFormatter.stringFromByteCount(total.longLongValue, countStyle: NSByteCountFormatterCountStyle.File) self.labelSize.text = "\(formattedWrittenSize) / \(formattedTotalSize)" let Percentage = Int((writing.doubleValue/total.doubleValue) * 100.0) self. labelprogress. text = "\(percentage)%"}}}Copy the code

First we parse the task progress information passed in from the wrapper. Remember the structure we defined in TaskManager:

let progressInfo = ["taskIdentifier": downloadTask.taskIdentifier,
                    "totalBytesWritten": NSNumber(longLong: totalBytesWritten),
                    "totalBytesExpectedToWrite": NSNumber(longLong: totalBytesExpectedToWrite)]
Copy the code

This data is being parsed here. First we take the info[” taskIdentifier “] and compare it to the taskIdentifier of our current DownloadTask:

if taskIdentifier.integerValue == self.downloadTask?.taskIdentifier
Copy the code

If the comparison is successful and the current task progress changes, then the UI needs to be updated. Next, we take out the rest of the task progress information and set them to the text of labelSize and labelProgress:

guard let written = info["totalBytesWritten"] as? NSNumber else { return }
guard let total = info["totalBytesExpectedToWrite"] as? NSNumber else { return }

let formattedWrittenSize = NSByteCountFormatter.stringFromByteCount(written.longLongValue, countStyle: NSByteCountFormatterCountStyle.File)            
let formattedTotalSize = NSByteCountFormatter.stringFromByteCount(total.longLongValue, countStyle: NSByteCountFormatterCountStyle.File)

self.labelSize.text = "\(formattedWrittenSize) / \(formattedTotalSize)"
let percentage = Int((written.doubleValue / total.doubleValue) * 100.0)
self.labelProgress.text = "\(percentage)%"Copy the code

We continue to define an updateData method that binds the DownloadTask information:

func updateData(task : DownloadTask) { self.downloadTask = task labelName.text = self.downloadTask? .url.lastPathComponent }Copy the code

Then override the layoutSubviews method to layout the UI:

 override func layoutSubviews() {
        
	super.layoutSubviews()
	self.labelName.frame = CGRectMake(20, 10, self.contentView.frame.size.width - 50, 20)
	self.labelSize.frame = CGRectMake(20, 40, self.contentView.frame.size.width - 50, 20)
	self.labelProgress.frame = CGRectMake(self.contentView.frame.size.width - 45, 20, 40, 30)
	
}Copy the code

This completes the implementation of the DownloadTaskCell class.

Add tasks

Now that we’re done with the task display and update, we also need a UI to add a new download task. Create a new class NewTaskViewController:

class NewTaskViewController: UIViewController { var textView:UITextView? Override func viewDidLoad () {super viewDidLoad () the self. The title = "add task" is the self. The backgroundColor = UIColor. WhiteColor () self.navigationController? . The navigationBar. Translucent = false self. NavigationItem. RightBarButtonItem = UIBarButtonItem (title: "download", style: UIBarButtonItemStyle.Plain, target: self, action: The Selector (" add ")) self. NavigationItem. LeftBarButtonItem = UIBarButtonItem (title: "cancel", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("close")) self.textView = UITextView(frame: CGRectMake(10, 10, self.view.frame.size.width - 20, 250)) self.textView?.layer.borderWidth = 1 self.textView?.layer.borderColor = UIColor.grayColor().CGColor self.textView?.layer.cornerRadius = 5 self.textView?.font = UIFont.systemFontOfSize(14) self.view.addSubview(self.textView!) } override func viewDidAppear(animated: Bool) { self.textView? .becomeFirstResponder() } func close() { self.dismissViewControllerAnimated(true, completion: nil) } func add() { TaskManager.sharedInstance.newTask(self.textView! .text) self.dismissViewControllerAnimated(true, completion: nil) } }Copy the code

This class has a textView control for entering the download link, and the two buttons in the navigation bar correspond to the close() and Add () methods. The close method closes the interface directly. The add method invokes the TaskManager. SharedInstance. NewTask (self textView! .text) method to start and add the download task, and then close the Add screen.

We also need to create an entry for the add screen. Back in our FirstViewController class, we add a method:

 func addTask() {
        
    let viewController = NewTaskViewController()
    let navController = UINavigationController(rootViewController: viewController)
    self.presentViewController(navController, animated: true, completion: nil)
    
}Copy the code

Now that the infrastructure for downloading the tool is set up, you can give the program a try.

After clicking the Add Task button, we can enter a download address, such as nodejs.org/dist/v4.2.3… :

Click OK to start the download task:

At this point, try switching your APP to the background and re-entering the APP a few minutes later. You may find that your download progress has increased significantly, or that the download has already been completed. This is because while you’re in the background, the NSURLSession is still downloading in the background.

After the APP runs for a while, the download task is completed. At this time, you will see that the task in the “Downloading” task list “disappears”, because we registered the notification. When the download task is completed, we will refresh the list display, and only the tasks that have not been downloaded will be displayed after refreshing. So we lost sight of the task we just completed.

The downloaded tasks are displayed

So we also need a place to display the tasks we have downloaded and completed. We open up SecondViewController and, again, add a mainTableView and taskList property to it:

class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { private var mainTableView:UITableView? private var taskList: [DownloadTask]? / /... }Copy the code

The viewDidLoad method implements it:

Override func viewDidLoad () {super viewDidLoad () the self. The title = "completed" self. NavigationController? .navigationBar.translucent = false self.mainTableView = UITableView(frame: CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)) self.mainTableView?.delegate = self self.mainTableView?.dataSource = self self.view.addSubview(self.mainTableView!) NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("reloadData"), name: DownloadTaskNotification.Finish.rawValue, object: nil) }Copy the code

After we initialize the control and register the notification, let’s create the reloadData method that handles the notification:

override func viewDidAppear(animated: Bool) { self.reloadData() } func reloadData() { self.taskList = TaskManager.sharedInstance.finishedTask() self.mainTableView? .reloadData() }Copy the code

And “download” list, this time we use the TaskManager. SharedInstance. FinishedTask () method to get the task list, we only get the download tasks have been completed, then reload the UITableView data.

Finally, we implement the UITableView proxy method:

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) - > CGFloat 70.0} {return func numberOfSectionsInTableView (tableView: UITableView) -> Int { return 1 } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.taskList == nil ? 0 : self.taskList! .count } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { var cell = tableView.dequeueReusableCellWithIdentifier("FinishedCell") as? DownloadTaskCell if cell == nil { cell = DownloadTaskCell(style: UITableViewCellStyle.Default, reuseIdentifier: "FinishedCell") } cell?.updateData(self.taskList! [indexPath.row]) return cell! }Copy the code

The implementation hasn’t changed much. Let’s re-run the application and click on the second “Completed” Tab to see the tasks we just downloaded.

conclusion

So let’s go back. This time through a complete APP development to show you the application of NSURLSession in the actual development. More mainly in the download operation of the application. We first created the Background type NSURLSession, which establishes the download task and can continue to run after the application is switched to the background. Moreover, we realized the timely update of UI interface very easily through the proxy and notification mechanism.

Of course, there are many other features of NSURLSession, for example, we have not implemented the resumable breakpoint, and the corresponding processing if the download is completed in the background. You can also refer to the relevant documentation for your own thinking, again, I will continue to refine the example for you later, as well as the article on the breakpoint continuation features mentioned above.

The Guard keyword is used in many places. This is a new feature in Swift 2.0. If you need to learn about it, you can refer to the Guard keyword

You can also find the full code for the sample APP on Github at github.com/swiftcafex/…

This site articles are original content, if you need to reprint, please indicate the source, thank you.

We recommend following the wechat public platform



swift-cafe
  • Recommended articles on wechat:
  • IOS developer’s compass in the journey – LLDB debugging technology
  • Using Kingfisher to handle network image reading and caching
  • Carthage package management tool, another agile and light development experience
  • ITerm – Make your command line colorful
  • SwiftyJSON is used to create a bitCoin price APP in Swift.
  • Swift open source that thing