Tag Archives: Web Development

PHP LOGIN SYSTEM RELOADED V1.1

Here’s an updated version of the PHP Login System. You can check the old version here. The following was added: 1. Registration fields: email confirmation password confirmation country recaptcha Email confirmation and password confirmation are configurable fields. By default they are shown, but if you wish to remove one or both of them you have …

Here’s an updated version of the PHP Login System.

The following was added:

1. Registration fields:

  • email confirmation
  • password confirmation
  • country
  • recaptcha

Email confirmation and password confirmation are configurable fields. By default they are shown, but if you wish to remove one or both of them you have to set it in the file constants.php:

define("REPEAT_EMAIL",true);
define("REPEAT_PASSWORD",true);

2. Table fields:

  • the user ip,
  • number of logins of a user,
  • flag is_admin
  • flag is_blocked
  • new table – Country table

3. dbcontroller class sanitizes user input data

4. Edit Account Area

5. Admin Area – incomplete (for next version)

For now just the list of users is shown and a world map indicating where the users come from.  You can delete Users and set them as Admins. If there are no users to list, no map is shown. Also the User seeing the panel is not shown.
Note that in the demo you will not be able to see the admin part – for admin reasons :)

6. New CSS

7. Some other small details in the php code.

For the next version I am planning to :

  • add all admin functions
  • improve and improve ….

To use the recaptcha you need to get a public/private key . Then you need to define them in constants.php:

define("PUBLICKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
define("PRIVATEKEY","XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");

In order to use the demo for those who have registered in the previous version’s demo, you need to register again (in the demo login system), since I had to set up another database for it.

Hope you like it. Any suggestions or improvements are welcome!

SOLID: Part 3 – Liskov Substitution & Interface Segregation Principles

The Single Responsibility (SRP)Open/Closed (OCP)Liskov Substitution, Interface Segregation, and Dependency Inversion. Five agile principles that should guide you every time you write code.

Because both the Liskov Substitution Principle (LSP) and the Interface Segregation Principle (ISP) are quite easy to define and exemplify, in this lesson we will talk about both of them.

Liskov Substitution Principle (LSP)

Child classes should never break the parent class’ type definitions.

The concept of this principle was introduced by Barbara Liskov in a 1987 conference keynote and later published in a paper together with Jannette Wing in 1994. Their original definition is as follows:

Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

Later on, with the publication of the SOLID principles by Robert C. Martin in his bookAgile Software Development, Principles, Patterns, and Practices and then republished in the C# version of the book Agile Principles, Patterns, and Practices in C#, the definition became known as the Liskov Substitution Principle.

This leads us to the definition given by Robert C. Martin:

Subtypes must be substitutable for their base types.

As simple as that, a subclass should override the parent class’ methods in a way that does not break functionality from a client’s point of view. Here is a simple example to demonstrate the concept.

01
02
03
04
05
06
07
08
09
10
class Vehicle {
    function startEngine() {
        // Default engine start functionality
    }
    function accelerate() {
        // Default acceleration functionality
    }
}

Given a class Vehicle – it may be abstract – and two implementations:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Car extends Vehicle {
    function startEngine() {
        $this->engageIgnition();
        parent::startEngine();
    }
    private function engageIgnition() {
        // Ignition procedure
    }
}
class ElectricBus extends Vehicle {
    function accelerate() {
        $this->increaseVoltage();
        $this->connectIndividualEngines();
    }
    private function increaseVoltage() {
        // Electric logic
    }
    private function connectIndividualEngines() {
        // Connection logic
    }
}

A client class should be able to use either of them, if it can use Vehicle.

1
2
3
4
5
6
class Driver {
    function go(Vehicle $v) {
        $v->startEngine();
        $v->accelerate();
    }
}

Which leads us to a simple implementation of the Template Method Design Pattern as we used it in the OCP tutorial.

template_method

Based on our previous experience with the Open/Closed Principle, we can conclude that Liskov’s Substitution Principle is in strong relation with OCP. In fact, “a violation of LSP is a latent violation of OCP” (Robert C. Martin), and the Template Method Design Pattern is a classic example of respecting and implementing LSP, which in turn is one of the solutions to respect OCP also.

The Classic Example of LSP Violation

To illustrate this completely, we will go with a classic example because it is highly significant and easily understandable.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Rectangle {
    private $topLeft;
    private $width;
    private $height;
    public function setHeight($height) {
        $this->height = $height;
    }
    public function getHeight() {
        return $this->height;
    }
    public function setWidth($width) {
        $this->width = $width;
    }
    public function getWidth() {
        return $this->width;
    }
}

We start with a basic geometrical shape, a Rectangle. It is just a simple data object with setters and getters for width and height. Imagine that our application is working and it is already deployed to several clients. Now they need a new feature. They need to be able to manipulate squares.

In real life, in geometry, a square is a particular form of rectangle. So we could try to implement a Square class that extends a Rectangle class. It is frequently said that a child class is a parent class, and this expression also conforms to LSP, at least at first sight.

SquareRect

But is a Square really a Rectangle in programming?

01
02
03
04
05
06
07
08
09
10
11
12
class Square extends Rectangle {
    public function setHeight($value) {
        $this->width = $value;
        $this->height = $value;
    }
    public function setWidth($value) {
        $this->width = $value;
        $this->height = $value;
    }
}

A square is a rectangle with equal width and height, and we could do a strange implementation like in the above example. We could overwrite both setters to set the height as well as the width. But how would that affect client code?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
class Client {
    function areaVerifier(Rectangle $r) {
        $r->setWidth(5);
        $r->setHeight(4);
        if($r->area() != 20) {
            throw new Exception('Bad area!');
        }
        return true;
    }
}

It is conceivable to have a client class that verifies the rectangle’s area and throws an exception if it is wrong.

1
2
3
function area() {
    return $this->width * $this->height;
}

Of course we added the above method to our Rectangle class to provide the area.

1
2
3
4
5
6
7
8
9
class LspTest extends PHPUnit_Framework_TestCase {
    function testRectangleArea() {
        $r = new Rectangle();
        $c = new Client();
        $this->assertTrue($c->areaVerifier($r));
    }
}

And we created a simple test by sending an empty rectangle object to area verifier and the test passes. If our Square class is correctly defined, sending it to the Client’sareaVerifier() should not break its functionality. After all, a Square is a Rectanglein all mathematical sense. But is our class?

1
2
3
4
5
function testSquareArea() {
    $r = new Square();
    $c = new Client();
    $this->assertTrue($c->areaVerifier($r));
}

Testing it is very easy and it breaks big time. An exception is thrown to us when we run the test above.

1
2
3
4
5
PHPUnit 3.7.28 by Sebastian Bergmann.
Exception : Bad area!
#0 /paht/: /.../.../LspTest.php(18): Client->areaVerifier(Object(Square))
#1 [internal function]: LspTest->testSquareArea()

So, our Square class is not a Rectangle after all. It breaks the laws of geometry. It fails and it violates the Liskov Substitution Principle.

I especially love this example because it not only violates LSP, it also demonstrates that object oriented programming is not about mapping real life to objects. Each object in our program must be an abstraction over a concept. If we try to map one-to-one real objects to programmed objects, we will almost always fail.

The Interface Segregation Principle

The Single Responsibility Principle is about actors and high level architecture. The Open/Closed Principle is about class design and feature extensions. The Liskov Substitution Principle is about subtyping and inheritance. The Interface Segregation Principle (ISP) is about business logic to clients communication.

In all modular applications there must be some kind of interface that the client can rely on. These may be actual Interface typed entities or other classic objects implementing design patterns like Facades. It doesn’t matter which solution is used. It always has the same scope: to communicate to the client code on how to use the module. These interfaces can reside between different modules in the same application or project, or between one project as a third party library serving another project. Again, it doesn’t matter. Communication is communication and clients are clients, regardless of the actual individuals writing the code.

So, how should we define these interfaces? We could think about our module and expose all the functionalities we want it to offer.

hugeInterface

This looks like a good start, a great way to define what we want to implement in our module. Or is it? A start like this will lead to one of two possible implementations:

  • A huge Car or Bus class implementing all the methods on the Vehicleinterface. Only the sheer dimensions of such classes should tell us to avoid them at all costs.
  • Or, many small classes like LightsControlSpeedControl, or RadioCD which are all implementing the whole interface but actually providing something useful only for the parts they implement.

It is obvious that neither solution is acceptable to implement our business logic.

specializedImplementationInterface

We could take another approach. Break the interface into pieces, specialized to each implementation. This would help to use small classes that care about their own interface. The objects implementing the interfaces will be used by the different type of vehicles, like car in the image above. The car will use the implementations but will depend on the interfaces. So a schema like the one below may be even more expressive.

carUsingInterface

But this fundamentally changes our perception of the architecture. The Car becomes the client instead of the implementation. We still want to provide to our clients ways to use our whole module, that being a type of vehicle.

oneInterfaceManyClients

Assume we solved the implementation problem and we have a stable business logic. The easiest thing to do is to provide a single interface with all the implementations and let the clients, in our case BusStationHighWayDriver and so on, to use whatever thew want from the interface’s implementation. Basically, this shifts the behavior selection responsibility to the clients. You can find this kind of solution in many older applications.

The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use.

However, this solution has its problems. Now all the clients depend on all the methods. Why should a BusStation depend on the state of lights of the bus, or on the radio channels selected by the driver? It should not. But what if it does? Does it matter? Well, if we think about the Single Responsibility Principle, it is a sister concept to this one. IfBusStation depends on many individual implementations, not even used by it, it may require changes if any of the individual small implementations change. This is especially true for compiled languages, but we can still see the effect of theLightControl change impacting BusStation. These things should never happen.

Interfaces belong to their clients and not to the implementations. Thus, we should always design them in a way to best suite our clients. Some times we can, some times we can not exactly know our clients. But when we can, we should break our interfaces in many smaller ones, so they better satisfy the exact needs of our clients.

segregatedInterfaces

Of course, this will lead to some degree of duplication. But remember! Interfaces are just plain function name definitions. There is no implementation of any kind of logic in them. So the duplications is small and manageable.

Then, we have the great advantage of clients depending only and only on what they actually need and use. In some cases, clients may use and need several interfaces, that is OK, as long as they use all the methods from all the interfaces they depend on.

Another nice trick is that in our business logic, a single class can implement several interfaces if needed. So we can provide a single implementation for all the common methods between the interfaces. The segregated interfaces will also force us to think of our code more from the client’s point of view, which will in turn lead to loose coupling and easy testing. So, not only have we made our code better to our clients, we also made it easier for ourselves to understand, test and implement.

Final Thoughts

LSP taught us why reality can not be represented as a one-to-one relation with programmed objects and how subtypes should respect their parents. We also put it in light of the other principles that we already knew.

ISP teaches us to respect our clients more than we thought necessary. Respecting their needs will make our code better and our lives as programmers easier.

Thank you for your time.

The Beginner’s Guide to WordPress SEO by Yoast: Final Tweaking

In my previous article, I discussed the social settings of Yoast’s WordPress SEO plugin. In this tutorial, you will learn the final steps to configuring the WordPress SEO plugin with the ultimate goal of making it as rock-solid as possible for your blog.

In this tutorial, we will cover the following material:

  • XML sitemaps
  • permalinks
  • internal links
  • RSS
  • import and export
  • edit files

If this seems daunting, don’t worry! The goal of this tutorial is to make configuration much easier by demystifying many of the features. Throughout this tutorial, I’ll first explain a particular setting and then suggest my own approach for setting it up.

Let’s get started.

XML Sitemaps

As stated in Wikipedia:

A site map (or sitemap) is a list of pages of a web site accessible to crawlers or users.

WordPress SEO offers XML sitemap-generating functionality, and I consider it to be perfect because of the fact that the generated sitemap can communicate with your SEO plugin. Not all sitemap-generating plugins do this.

Confused? Let me explain!

When you nofollow a post, your post still remains in the sitemap. On one hand, this means that you’re asking your SEO plugin to nofollow the page so that Google will ignore it, but on the other hand your third party site map generator put its link inside your sitemap, which instructs Google to index it.

This happens because your sitemap generating plugin doesn’t communicate with your SEO plugin. That is to say that the plugin doesn’t know if you have set nofollow on a particular post or page.

This can result in an alarming situation: Google can decrease your site’s ranking.

To make XML Sitemap generation less complicated, I recommend avoiding using any other sitemap-generation plugins when running WordPress SEO. Additionally, the XML sitemaps by WordPress SEO are readable by humans because of the XSLT stylesheet applied to them.

Configuration

First, under the “XML Sitemaps” menu option, check and enable the XML Sitemaps.

Next, if you want to disable the site maps, check the box under “Disable author/user sitemap.” I normally do not disable this as I like to tell Google who has written on my blog—I even have custom author pages. If your authors aren’t as important, you should disable the author maps by checking this box.

After that, you’ll see the “General Settings.” These allow WordPress SEO to ping Yahoo & Ask. It helps in getting your content indexed.

For excluding post types, you can opt to exclude anything you want. This is useful especially if a page type isn’t optimized. For example, I care about my posts, so I don’t check it. Similarly, my pages are very well-optimized and thus are not excluded.

Next up, we’ve got the “Media Attachment” pages. As per our first tutorial, we set anofollow on the media, so if you’ve been following along, you should check the box of media here to disable the sitemap option for images.

However, if you didn’t and if you have images with attachment pages filled with content, then make sure your settings are set to what you see below. (Normally image attachment pages have nothing but a single image—in that case, I  recommend that you check the box of “Media” to disable its inclusion into the sitemap.)

2_xml

Next up: taxonomies. If you haven’t optimized your tags or your category pages, then you should make sure that you exclude them as well.

Ultimately, this tells Google that the most important part of your site is the posts. If categories and/or tags are more important to you, then you should keep these boxes unchecked.

After that, you can look at the entries per page. In short, you can leave it blank, but if you have a specific number, then enter it in the available text box. This is a subjective matter.

3_XML
You should resubmit your sitemap in Google or Bing Webmaster, and the link of sitemap in case of WordPress SEO is YourDomains.com/sitemap_index.xml

Permalinks

According to Wikipedia:

A permalink (portmanteau of permanent link) is a URL that points to a specific blog or forum entry after it has passed from the front page to the archives.

Here, the configuration options are self-explanatory. Configure your settings just like I’ve set mine in the image below with the exception of “Redirect attachment URL’s to parent post URL.”

If you set nofollow on the  “Media” in the first part of this series, then you should check this box; otherwise, don’t select this option.

2_permalinks

Internal Links

Internal Links refer to links within your own domain. Here, you can set up breadcrumbs, if your theme doesn’t already support them. The settings are quite simple and self-explanatory.

If you want to display breadcrumbs in your theme (and your theme has the proper code to display them), then all you need to check the “Enable Breadcrumbs” element.

3_Internal_Links

As previously mentioned, your theme must have the code specifically for displaying breadcrumbs.

If you enable this option and the breadcrumbs don’t appear, then you’ll need to add the code to your theme (if you’re comfortable doing that). The plugin provides instructions on how to do exactly that:

3_bread

RSS

From Wikipedia:

RSS (Rich Site Summary) feeds enable publishers to syndicate data automatically. A standard XML file format ensures compatibility with many different machines/programs. RSS feeds also benefit users who want to receive timely updates from favourite websites or to aggregate data from many sites.

When you have people or bots scraping your blog’s content to populate their own site, you need to provide some type of protection to indicate where the content first originated.

Luckily, WordPress SEO makes this really easy. All you need to do is put some content at the beginning and/or at the end of your RSS feed to claim your content. WordPress SEO goes a step further and allows you to even add links before and after your feed.

4_RSS

Import & Export

This section helps you to shift from different plugins of SEO to the WordPress SEO plugin, so that you don’t lose your rankings, or migrate settings from one site to another.

In case you have a new site, you don’t need to use this section, but if you have had been using another SEO plugin, this is where you convert it to a format that’s compatible with WordPress SEO. All you need to do is explained by the plugin author:

If you’ve used another SEO plugin, try the SEO Data Transporter plugin to move your data into this plugin, it rocks!

import

In case you want to export your SEO settings to use them in another WordPress blog, use the export option.

All you need to do is download the exported data from this section, then, in your other site, import the file that you just exported. (If you import like this all the settings will come along, but you might need to set the general settings for homepage of your new site.)

Export

Edit Files

In the Edit Files section, you get to edit the robots.txt file and .htaccessfile. robots.txt prevents bots from looking into particular directories or links.

It works likes this: a robot wants to visit a site URL, say http://www.example.com/welcome.html. Before it does so, it first checks for http://www.example.com/robots.txt, if robots file allows to do that, the bot visits the URL; otherwise, it stops and will not crawl that particular page.

Robots

From Wikipedia:

An .htaccess—that is, hypertext accessfile is a directory-level configuration file supported by several web servers, that allows for decentralized management of web server configuration. They are placed inside the web tree, and are able to override a subset of the server’s global configuration for the directory that they are in, and all sub-directories.

This file is used for redirecting links, manipulating directories and different other optimizations. You should not edit it unless until you know what you are doing.

Feeling geeky? Read more here.

htaccess

What’s Next?

This wraps up our discussion on how to configure your WordPress SEO plugin by Yoast. What’s next? If you have any questions, I’ll be happy to answer them so leave them in the comments!

Working with NSURLSession: Part 4

In the previous tutorial, we started creating a simple podcast client to put what we’ve learned about NSURLSession into practice. So far, our podcast client can query the iTunes Search API, download a podcast feed, and display a list of episodes. In this tutorial, we zoom in on another interesting aspect of NSURLSession, out-of-process downloads. Let me show you how this works.

Introduction

In this fourth and final tutorial about NSURLSession, we’ll take a closer look at out-of-process tasks, download tasks in particular. Our podcast client is already able to show a list of episodes, but it currently lacks the ability to download individual episodes. That’ll be the focus of this tutorial.

Background Uploads and Downloads

Adding support for background uploads and downloads is surprisingly easy withNSURLSession. Apple refers to them as out-of-process uploads and downloads as the tasks are managed by a background daemon, not your application. Even if your application crashes during an upload or download task, the task continues in the background.

Overview

I’d like to take a few moments to take a closer look at how out-of-process tasks work. It’s pretty simple once you have a complete picture of the process. Enabling background uploads and downloads is nothing more than flipping a switch in your session’s configuration. With a properly configured session object, you are ready to schedule upload and download tasks in the background.

When an upload or download is initiated, a background daemon comes into existence. The daemon takes care of the task and sends updates to the application through the delegate protocols declared in the NSURLSession API. If your application stops running for some reason, the task continues in the background as it’s the daemon managing the task. The moment the task finishes, the application that created the task is notified. It reconnects with the background session that created the task and the daemon managing the task informs the session that the task finished and, in the case of a download task, hands the file over to the session. The session then invokes the appropriate delegate methods to make sure your application can take the appropriate actions, such as moving the file to a more permanent location. That’s enough theory for now. Let’s see what we need to do to implement out-of-process downloads in Singlecast.

1. Subclass UITableViewCell

Step 1: Update Main Storyboard

At the moment, we are using prototype cells to populate the table view. To give us a bit more flexibility, we need to create a UITableViewCell subclass. Open the main storyboard, select the table view of the MTViewController instance and set the number of prototype cells to 0.

Update the project's main storyboard.

Step 2: Create Subclass

Open Xcode’s File menu and choose New > File…. Create a new Objective-C class, name it MTEpisodeCell, and make sure it inherits from UITableViewCell. Tell Xcode where you’d like to store the class files and hit Create.

Create a subclass of UITableViewCell.

Step 3: Update Class Interface

The interface of MTEpisodeCell is simple as you can see in the code snippet below. All we do is declare a property progress of type float. We’ll use this to update and display the progress of the download task that we’ll use for downloading an episode.

1
2
3
4
5
6
7
#import <UIKit/UIKit.h>
@interface MTEpisodeCell : UITableViewCell
@property (assign, nonatomic) float progress;
@end

Step 4: Implement Class

The implementation of MTEpisodeCell is a bit more involved, but it isn’t complicated. Instead of using an instance of UIProgressView, we’ll fill the cell’s content view with a solid color to show the progress of the download task. We do this by adding a subview to the cell’s content view and updating its width whenever the cell’s progress property changes. Start by declaring a private property progressView of type UIView.

1
2
3
4
5
6
7
#import "MTEpisodeCell.h"
@interface MTEpisodeCell ()
@property (strong, nonatomic) UIView *progressView;
@end

We override the class’s designated initializer as shown below. Note how we ignore thestyle argument and pass UITableViewCellStyleSubtitle to the superclass’s designated initializer. This is important, because the table view will passUITableViewCellStyleDefault as the cell’s style when we ask it for a new cell.

In the initializer, we set the background color of the text and detail text labels to[UIColor clearColor] and create the progress view. Two details are especially important. First, we insert the progress view as a subview of the cell’s content view at index 0 to make sure that it’s inserted below the text labels. Second, we invokeupdateView to make sure that the frame of the progress view is updated to reflect the value of progress, which is set to 0 during the cell’s initialization.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier];
    if (self) {
        // Helpers
        CGSize size = self.contentView.bounds.size;
        // Configure Labels
        [self.textLabel setBackgroundColor:[UIColor clearColor]];
        [self.detailTextLabel setBackgroundColor:[UIColor clearColor]];
        // Initialize Progress View
        self.progressView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, size.width, size.height)];
        // Configure Progress View
        [self.progressView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleWidth)];
        [self.progressView setBackgroundColor:[UIColor colorWithRed:0.678 green:0.886 blue:0.557 alpha:1.0]];
        [self.contentView insertSubview:self.progressView atIndex:0];
        // Update View
        [self updateView];
    }
    return self;
}

Before we take a look at the implementation of updateView, we need to override the setter method of the progress property. The only change we make to the default implementation of setProgress: is invoke updateView when the _progressinstance variable is updated. This ensures that the progress view is updated whenever we update the cell’s progress property.

1
2
3
4
5
6
7
8
- (void)setProgress:(CGFloat)progress {
    if (_progress != progress) {
        _progress = progress;
        // Update View
        [self updateView];
    }
}

In updateView, we calculate the new width of the progress view based on the value of the cell’s progress property.

1
2
3
4
5
6
7
8
9
- (void)updateView {
    // Helpers
    CGSize size = self.contentView.bounds.size;
    // Update Frame Progress View
    CGRect frame = self.progressView.frame;
    frame.size.width = size.width * self.progress;
    self.progressView.frame = frame;
}

Step 5: Use MTEpisodeCell

To make use of the MTEpisodeCell, we need to make a few changes in theMTViewController class. Start by adding an import statement for MTEpisodeCell.

01
02
03
04
05
06
07
08
09
10
11
12
13
#import "MTViewController.h"
#import "MWFeedParser.h"
#import "SVProgressHUD.h"
#import "MTEpisodeCell.h"
@interface MTViewController () <MWFeedParserDelegate>
@property (strong, nonatomic) NSDictionary *podcast;
@property (strong, nonatomic) NSMutableArray *episodes;
@property (strong, nonatomic) MWFeedParser *feedParser;
@end

In the view controller’s viewDidLoad method, invoke setupView, a helper method we’ll implement next.

01
02
03
04
05
06
07
08
09
10
11
12
- (void)viewDidLoad {
    [super viewDidLoad];
    // Setup View
    [self setupView];
    // Load Podcast
    [self loadPodcast];
    // Add Observer
    [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"MTPodcast" options:NSKeyValueObservingOptionNew context:NULL];
}

In setupView, we invoke setupTableView, another helper method in which we tell the table view to use the MTEpisodeCell class whenever it needs a cell with a reuse identifier of EpisodeCell.

1
2
3
4
- (void)setupView {
    // Setup Table View
    [self setupTableView];
}
1
2
3
4
- (void)setupTableView {
    // Register Class for Cell Reuse
    [self.tableView registerClass:[MTEpisodeCell class] forCellReuseIdentifier:EpisodeCell];
}

Before we build the project and run the application, we need to update our implementation of tableView:cellForRowAtIndexPath: as shown below.

01
02
03
04
05
06
07
08
09
10
11
12
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MTEpisodeCell *cell = (MTEpisodeCell *)[tableView dequeueReusableCellWithIdentifier:EpisodeCell forIndexPath:indexPath];
    // Fetch Feed Item
    MWFeedItem *feedItem = [self.episodes objectAtIndex:indexPath.row];
    // Configure Table View Cell
    [cell.textLabel setText:feedItem.title];
    [cell.detailTextLabel setText:[NSString stringWithFormat:@"%@", feedItem.date]];
    return cell;
}

Step 6: Build and Run

Run your application in the iOS Simulator or on a test device to see the result. If nothing has changed, then you’ve followed the steps correctly. All that we’ve done so far is replacing the prototype cells with instances of MTEpisodeCell.

2. Create Background Session

To enable out-of-process downloads, we need a session that is configured to support out-of-process downloads. This is surprisingly easy to do with the NSURLSession API. There a few gotchas though.

Step 1: Create session Property

Start by declaring a new property session of type NSURLSession in theMTViewController class and make the class conform to the NSURLSessionDelegateand NSURLSessionDownloadDelegate protocols.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
#import "MTViewController.h"
#import "MWFeedParser.h"
#import "SVProgressHUD.h"
#import "MTEpisodeCell.h"
@interface MTViewController () <NSURLSessionDelegate, NSURLSessionDownloadDelegate, MWFeedParserDelegate>
@property (strong, nonatomic) NSDictionary *podcast;
@property (strong, nonatomic) NSMutableArray *episodes;
@property (strong, nonatomic) MWFeedParser *feedParser;
@property (strong, nonatomic) NSURLSession *session;
@end

In viewDidLoad, we set the session property by invoking backgroundSession on the view controller instance. This is one of the gotchas I was talking about.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
- (void)viewDidLoad {
    [super viewDidLoad];
    // Setup View
    [self setupView];
    // Initialize Session
    [self setSession:[self backgroundSession]];
    // Load Podcast
    [self loadPodcast];
    // Add Observer
    [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"MTPodcast" options:NSKeyValueObservingOptionNew context:NULL];
}

Let’s take a look at the implementation of backgroundSession. InbackgroundSession, we statically declare a session variable and usedispatch_once (Grand Central Dispatch) to instantiate the background session. Even though this isn’t strictly necessary, it emphasizes the fact that we only need one background session at any time. This is a best practice that’s also mentioned in theWWDC session on the NSURLSession API.

In the dispatch_once block, we start by creating a NSURLSessionConfigurationobject by invoking backgroundSessionConfiguration: and passing a string as an identifier. The identifier we pass uniquely identifies the background session, which is key as we’ll see a bit later. We then create a session instance by invokingsessionWithConfiguration:delegate:delegateQueue: and passing the session configuration object, setting the session’s delegate property, and passing nil as the third argument.

01
02
03
04
05
06
07
08
09
10
11
12
13
- (NSURLSession *)backgroundSession {
    static NSURLSession *session = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // Session Configuration
        NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.mobiletuts.Singlecast.BackgroundSession"];
        // Initialize Session
        session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
    });
    return session;
}
By passing nil as the third argument ofsessionWithConfiguration:delegate:delegateQueue:, the session creates a serial operation queue for us. This operation queue is used for performing the delegate method calls and completion handler calls.

3. Download Episode

Step 1: Create Download Task

It’s time to make use of the background session we created and put theMTEpisodeCell to use. Let’s start by implementingtableView:didSelectRowAtIndexPath:, a method of the UITableViewDelegateprotocol. Its implementation is straightforward as you can see below. We fetch the correct MWFeedItem instance from the episodes array and pass it todownloadEpisodeWithFeedItem:.

1
2
3
4
5
6
7
8
9
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    // Fetch Feed Item
    MWFeedItem *feedItem = [self.episodes objectAtIndex:indexPath.row];
    // Download Episode with Feed Item
    [self downloadEpisodeWithFeedItem:feedItem];
}

In downloadEpisodeWithFeedItem:, we extract the remote URL from the feed item by invoking urlForFeedItem:, create a download task by callingdownloadTaskWithURL: on the background session, and send it a message ofresume to start the download task.

1
2
3
4
5
6
7
8
9
- (void)downloadEpisodeWithFeedItem:(MWFeedItem *)feedItem {
    // Extract URL for Feed Item
    NSURL *URL = [self urlForFeedItem:feedItem];
    if (URL) {
        // Schedule Download Task
        [[self.session downloadTaskWithURL:URL] resume];
    }
}

As you may have guessed, urlForFeedItem: is a convenience method that we use. We’ll use it a few more times in this project. We obtain a reference to the feed item’senclosures array, extract the first enclosure, and pull out the object for the url key. We create and return an NSURL instance.

01
02
03
04
05
06
07
08
09
10
11
12
13
- (NSURL *)urlForFeedItem:(MWFeedItem *)feedItem {
    NSURL *result = nil;
    // Extract Enclosures
    NSArray *enclosures = [feedItem enclosures];
    if (!enclosures || !enclosures.count) return result;
    NSDictionary *enclosure = [enclosures objectAtIndex:0];
    NSString *urlString = [enclosure objectForKey:@"url"];
    result = [NSURL URLWithString:urlString];
    return result;
}

We’re not done yet. Is the compiler giving you three warnings? That’s not surprising as we haven’t implemented the required methods of the NSURLSessionDelegate andNSURLSessionDownloadDelegate protocols yet. We also need to implement these methods if we want to show the progress of the download tasks.

Step 2: Implementing Protocol(s)

The first method we need to implement isURLSession:downloadTask:didResumeAtOffset:. This method is invoked if a download task is resumed. Because this is something we won’t cover in this tutorial, we simply log a message to Xcode’s console.

1
2
3
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

More interesting is the implementation ofURLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:. This method is invoked every time a few bytes have been downloaded by the session. In this delegate method, we calculate the progress, fetch the correct cell, and update the cell’s progress property, which in turn updates the cell’s progress view. Have you spotted the dispatch_async call? There’s no guarantee that the delegate method is invoked on the main thread. Since we update the user interface by setting the cell’s progress, we need to update the cell’s progress property on the main thread.

01
02
03
04
05
06
07
08
09
10
11
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    // Calculate Progress
    double progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;
    // Update Table View Cell
    MTEpisodeCell *cell = [self cellForForDownloadTask:downloadTask];
    dispatch_async(dispatch_get_main_queue(), ^{
        [cell setProgress:progress];
    });
}

The implementation of cellForForDownloadTask: is straightforward. We pull the remote URL from the download task using its originalRequest property and loop over the feed items in the episodes array until we have a match. When we’ve found a match, we ask the table view for the corresponding cell and return it.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
- (MTEpisodeCell *)cellForForDownloadTask:(NSURLSessionDownloadTask *)downloadTask {
    // Helpers
    MTEpisodeCell *cell = nil;
    NSURL *URL = [[downloadTask originalRequest] URL];
    for (MWFeedItem *feedItem in self.episodes) {
        NSURL *feedItemURL = [self urlForFeedItem:feedItem];
        if ([URL isEqual:feedItemURL]) {
            NSUInteger index = [self.episodes indexOfObject:feedItem];
            cell = (MTEpisodeCell *)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]];
            break;
        }
    }
    return cell;
}

The third delegate method of the NSURLSessionDownloadDelegate protocol that we need to implement is URLSession:downloadTask:didFinishDownloadingToURL:. As I mentioned in the previous tutorials, one of the advantages of the NSURLSession API is that downloads are immediately written to disk. The result is that we are passed a local URL in URLSession:downloadTask:didFinishDownloadingToURL:. However, the local URL that we receive, points to a temporary file. It is our responsibility to move the file to a more permanent location and that’s exactly what we do inURLSession:downloadTask:didFinishDownloadingToURL:.

1
2
3
4
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    // Write File to Disk
    [self moveFileWithURL:location downloadTask:downloadTask];
}

In moveFileWithURL:downloadTask:, we extract the episode’s file name from the download task and create a URL in the application’s Documents directory by invokingURLForEpisodeWithName:. If the temporary file that we received from the background session points to a valid file, we move that file to its new home in the application’s Documents directory.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
- (void)moveFileWithURL:(NSURL *)URL downloadTask:(NSURLSessionDownloadTask *)downloadTask {
    // Filename
    NSString *fileName = [[[downloadTask originalRequest] URL] lastPathComponent];
    // Local URL
    NSURL *localURL = [self URLForEpisodeWithName:fileName];
    NSFileManager *fm = [NSFileManager defaultManager];
    if ([fm fileExistsAtPath:[URL path]]) {
        NSError *error = nil;
        [fm moveItemAtURL:URL toURL:localURL error:&error];
        if (error) {
            NSLog(@"Unable to move temporary file to destination. %@, %@", error, error.userInfo);
        }
    }
}
I use a lot of helper methods in my iOS projects, because it makes for DRY code. It’s also good practice to create methods that only do one thing. Testing becomes much easier that way.

URLForEpisodeWithName: is another helper method, which invokesepisodesDirectory. In URLForEpisodeWithName:, we append the name argument to the Episodes directory, which is located in the application’s Documents directory.

1
2
3
4
- (NSURL *)URLForEpisodeWithName:(NSString *)name {
    if (!name) return nil;
    return [self.episodesDirectory URLByAppendingPathComponent:name];
}

In episodesDirectory, we create the URL for the Episodes directory and create the directory if it doesn’t exist yet.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
- (NSURL *)episodesDirectory {
    NSURL *documents = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    NSURL *episodes = [documents URLByAppendingPathComponent:@"Episodes"];
    NSFileManager *fm = [NSFileManager defaultManager];
    if (![fm fileExistsAtPath:[episodes path]]) {
        NSError *error = nil;
        [fm createDirectoryAtURL:episodes withIntermediateDirectories:YES attributes:nil error:&error];
        if (error) {
            NSLog(@"Unable to create episodes directory. %@, %@", error, error.userInfo);
        }
    }
    return episodes;
}

Step 3: Build and Run

Run the application and test the result by downloading an episode from the list of episodes. You should see the table view cell’s progress view progress from left to right reflecting the progress of the download task. There are a few issues though. Have you tried scrolling through the table view? That doesn’t look right. Let’s fix that.

4. Create a Progress Buffer

Because the table view reuses cells as much as possible, we need to make sure that each cell properly reflects the download state of the episode that it represents. We can fix this in several ways. One approach is to use an object that keeps track of the progress of each download task, including the download tasks that have already completed.

Step 1: Declare a Property

Let’s start by declaring a new private property progressBuffer of typeNSMutableDictionary in the MTViewController class.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
#import "MTViewController.h"
#import "MWFeedParser.h"
#import "SVProgressHUD.h"
#import "MTEpisodeCell.h"
@interface MTViewController () <NSURLSessionDelegate, NSURLSessionDownloadDelegate, MWFeedParserDelegate>
@property (strong, nonatomic) NSDictionary *podcast;
@property (strong, nonatomic) NSMutableArray *episodes;
@property (strong, nonatomic) MWFeedParser *feedParser;
@property (strong, nonatomic) NSURLSession *session;
@property (strong, nonatomic) NSMutableDictionary *progressBuffer;
@end

Step 2: Initialize Buffer

In viewDidLoad, we initialize the progress buffer as shown below.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
- (void)viewDidLoad {
    [super viewDidLoad];
    // Setup View
    [self setupView];
    // Initialize Session
    [self setSession:[self backgroundSession]];
    // Initialize Progress Buffer
    [self setProgressBuffer:[NSMutableDictionary dictionary]];
    // Load Podcast
    [self loadPodcast];
    // Add Observer
    [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"MTPodcast" options:NSKeyValueObservingOptionNew context:NULL];
}

Step 3: Update Table View Cells

The key that we’ll use in the dictionary is the remote URL of the corresponding feed item. With this in mind, we can update the tableView:cellForRowAtIndexPath:method as shown below. We pull the remote URL from the feed item and askprogressBuffer for the value for the key that corresponds to the remote URL. If the value isn’t nil, we set the cell’s progress property to that value, otherwise we set theprogress property of the cell to 0.0, which hides the progress view by setting its width to 0.0.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MTEpisodeCell *cell = (MTEpisodeCell *)[tableView dequeueReusableCellWithIdentifier:EpisodeCell forIndexPath:indexPath];
    // Fetch Feed Item
    MWFeedItem *feedItem = [self.episodes objectAtIndex:indexPath.row];
    NSURL *URL = [self urlForFeedItem:feedItem];
    // Configure Table View Cell
    [cell.textLabel setText:feedItem.title];
    [cell.detailTextLabel setText:[NSString stringWithFormat:@"%@", feedItem.date]];
    NSNumber *progress = [self.progressBuffer objectForKey:[URL absoluteString]];
    if (!progress) progress = @(0.0);
    [cell setProgress:[progress floatValue]];
    return cell;
}

Step 4: Avoid Duplicates

We can also use the progress buffer to prevent users from downloading the same episode twice. Take a look at the updated implementation oftableView:didSelectRowAtIndexPath:. We take the same steps we took intableView:cellForRowAtIndexPath: to extract the progress value from the progress buffer. Only when the progress value is nil, we download the episode.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    // Fetch Feed Item
    MWFeedItem *feedItem = [self.episodes objectAtIndex:indexPath.row];
    // URL for Feed Item
    NSURL *URL = [self urlForFeedItem:feedItem];
    if (![self.progressBuffer objectForKey:[URL absoluteString]]) {
        // Download Episode with Feed Item
        [self downloadEpisodeWithFeedItem:feedItem];
    }
}

Step 5: Update Buffer

The progress buffer only works in its current implementation if we keep it up to date. This means that we need to update theURLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:method as well. All we do is store the new progress value in the progress buffer.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    // Calculate Progress
    double progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;
    // Update Progress Buffer
    NSURL *URL = [[downloadTask originalRequest] URL];
    [self.progressBuffer setObject:@(progress) forKey:[URL absoluteString]];
    // Update Table View Cell
    MTEpisodeCell *cell = [self cellForForDownloadTask:downloadTask];
    dispatch_async(dispatch_get_main_queue(), ^{
        [cell setProgress:progress];
    });
}

In downloadEpisodeWithFeedItem:, we set the progress value to 0.0 when the download task starts.

01
02
03
04
05
06
07
08
09
10
11
12
- (void)downloadEpisodeWithFeedItem:(MWFeedItem *)feedItem {
    // Extract URL for Feed Item
    NSURL *URL = [self urlForFeedItem:feedItem];
    if (URL) {
        // Schedule Download Task
        [[self.session downloadTaskWithURL:URL] resume];
        // Update Progress Buffer
        [self.progressBuffer setObject:@(0.0) forKey:[URL absoluteString]];
    }
}

The session delegate is notified when a download task finishes. InURLSession:downloadTask:didFinishDownloadingToURL:, we set the progress value to 1.0.

1
2
3
4
5
6
7
8
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    // Write File to Disk
    [self moveFileWithURL:location downloadTask:downloadTask];
    // Update Progress Buffer
    NSURL *URL = [[downloadTask originalRequest] URL];
    [self.progressBuffer setObject:@(1.0) forKey:[URL absoluteString]];
}

Step 6: Restore Buffer

At the moment, the progress buffer is only stored in memory, which means that it’s cleared between application launches. We could write its contents to disk, but to keep this application simple we are going to restore or recreate the buffer by checking which episodes have already been downloaded. The feedParser:didParseFeedItem:method, part of the MWFeedParserDelegate protocol, is invoked for every item in the feed. In this method, we pull the remote URL from the feed item, create the corresponding local URL, and check if the file exists. If it does, then we set the corresponding progress value for that feed item to 1.0 to indicate that it’s already been downloaded.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
- (void)feedParser:(MWFeedParser *)parser didParseFeedItem:(MWFeedItem *)item {
    if (!self.episodes) {
        self.episodes = [NSMutableArray array];
    }
    [self.episodes addObject:item];
    // Update Progress Buffer
    NSURL *URL = [self urlForFeedItem:item];
    NSURL *localURL = [self URLForEpisodeWithName:[URL lastPathComponent]];
    if ([[NSFileManager defaultManager] fileExistsAtPath:[localURL path]]) {
        [self.progressBuffer setObject:@(1.0) forKey:[URL absoluteString]];
    }
}

Step 7: Rinse and Repeat

Run the application one more time to see if the issues with the table view are resolved. The application should now also remember which episodes have already been downloaded.

5. Being a Good Citizen

It’s important that our application is a good citizen by not wasting more CPU cycles or consume more battery power than needed. What does this mean for our podcast client. When a download task is started by our application and the application goes to the background, the background daemon that manages our application’s download task notifies our application through the background session that the download task has finished. If necessary, the background daemon will launch our application so that it can respond to these notifications and process the downloaded file.

In our example, we don’t need to do anything special to make sure that our application reconnects to the original background session. This is taken care of by theMTViewController instance. However, we do have to notify the operating system when our application has finished processing the download(s) by invoking a background completion handler.

When our application is woken up by the operating system to respond to the notifications of the background session, the application delegate is sent a message ofapplication:handleEventsForBackgroundURLSession:completionHandler:. In this method, we can reconnect to the background session, if necessary, and invoke the completion handler that is passed to us. By invoking the completion handler, the operating system knows that our application no longer needs to run in the background. This is important for optimizing battery life. How do we do this in practice?

Step 1: Declare a Property

We first need to declare a property on the MTAppDelegate class to keep a reference to the completion handler that we get fromapplication:handleEventsForBackgroundURLSession:completionHandler:. The property needs to be public. The reason for this will become clear in a moment.

1
2
3
4
5
6
7
8
#import <UIKit/UIKit.h>
@interface MTAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (copy, nonatomic) void (^backgroundSessionCompletionHandler)();
@end

Step 2: Implement Callback

In application:handleEventsForBackgroundURLSession:completionHandler:, we store the completion handler in backgroundSessionCompletionHandler, which we declared a moment ago.

1
2
3
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
    [self setBackgroundSessionCompletionHandler:completionHandler];
}

Step 3: Invoke Background Completion Handler

In the MTViewController class, we start by adding an import statement for theMTAppDelegate class.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
#import "MTViewController.h"
#import "MWFeedParser.h"
#import "MTAppDelegate.h"
#import "SVProgressHUD.h"
#import "MTEpisodeCell.h"
@interface MTViewController () <NSURLSessionDelegate, NSURLSessionDownloadDelegate, MWFeedParserDelegate>
@property (strong, nonatomic) NSDictionary *podcast;
@property (strong, nonatomic) NSMutableArray *episodes;
@property (strong, nonatomic) MWFeedParser *feedParser;
@property (strong, nonatomic) NSURLSession *session;
@property (strong, nonatomic) NSMutableDictionary *progressBuffer;
@end

We then implement another helper method,invokeBackgroundSessionCompletionHandler, which invokes the background completion handler stored in the application delegate’sbackgroundSessionCompletionHandler property. In this method, we ask the background session for all its running tasks. If there are no tasks running, we get a reference to the application delegate’s background completion handler and, if it isn’tnil, we invoke it and set it to nil.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
- (void)invokeBackgroundSessionCompletionHandler {
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        NSUInteger count = [dataTasks count] + [uploadTasks count] + [downloadTasks count];
        if (!count) {
            MTAppDelegate *applicationDelegate = (MTAppDelegate *)[[UIApplication sharedApplication] delegate];
            void (^backgroundSessionCompletionHandler)() = [applicationDelegate backgroundSessionCompletionHandler];
            if (backgroundSessionCompletionHandler) {
                [applicationDelegate setBackgroundSessionCompletionHandler:nil];
                backgroundSessionCompletionHandler();
            }
        }
    }];
}

Wait a minute. When do we invoke invokeBackgroundSessionCompletionHandler? We do this every time a download task finishes. In other words, we invoke this method in URLSession:downloadTask:didFinishDownloadingToURL: as shown below.

01
02
03
04
05
06
07
08
09
10
11
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    // Write File to Disk
    [self moveFileWithURL:location downloadTask:downloadTask];
    // Update Progress Buffer
    NSURL *URL = [[downloadTask originalRequest] URL];
    [self.progressBuffer setObject:@(1.0) forKey:[URL absoluteString]];
    // Invoke Background Completion Handler
    [self invokeBackgroundSessionCompletionHandler];
}

6. Wrapping Up

I hope you agree that our podcast client isn’t ready for the App Store just yet since one of the key features, playing podcasts, is still missing. As I mentioned in the previous tutorial, the focus of this project wasn’t creating a full-featured podcast client. The goal of this project was illustrating how to leverage the NSURLSession API to search the iTunes Search API and download podcast episodes using data and out-of-process download tasks respectively. You should now have a basic understanding of theNSURLSession API as well as out-of-process tasks.

Conclusion

By creating a simple podcast client, we have taken a close look at data and download tasks. We’ve also learned how easy it is to schedule download tasks in the background. The NSURLSession API is an important step forward for both iOS and OS X, and I encourage you to take advantage of this easy to use and flexible suite of classes. In the final installment of this series, I will take a look at AFNetworking 2.0. Why is it a milestone release? When should you use it? And how does it compare to theNSURLSession API?