Making the Twitter Code More General Purpose

When we’re ready to write these other view controllers to show tweet details and user information, we’re going to need to make new calls to the Twitter API. And considering all the work we did to get our first call working—talking to the ACAccountStore and getting an account and using it to make a request and so on—we really don’t want to repeat all that, right? But right now, that code is all in RootViewController. To make it more general purpose, we’re going to need to extract it, and then generalize it.

To do this, let’s think about how to make a more generic Twitter API caller. The current reloadTweets method uses the ACAccountStore to construct an SLRequest, then calls its performRequestWithHandler, which then calls back to our handleTwitterData in the completion handler closure. The things that are specific to RootViewController are the URL and the parameters sent to the SLRequest (in this case, they specify the home_timeline.json call and its parameters), and the response handling, which is all in another method (handleTwitterData). Put another way, everything in reloadTweets other than the URL and the parameters is something we would do for any Twitter request, and is therefore reusable.

So what we can do to refactor is to move this code to a general-purpose version that can be called with any URL and parameters, and replace reloadTweets with a one-line call to this new function, passing in the current URL and parameters. Everything we do in the response is already factored out into handleTwitterData, so we don’t need to change anything there.

The big difference is that the genericized Twitter request code should be in a reusable location so that classes other than RootViewController can call it. This class will go in the Twitter Utilities group, since it will work with the ParsedTweet class we’ve already created.

What should go here? Do we want a class? Well, no, because the Twitter request handler doesn’t have to manage any mutable state; it just gets an account, makes a request, and runs arbitrary code when the data comes back. The classic iOS approach from the Objective-C days would be to create our own delegate: we could make a Twitter-calling class, and a protocol declaring a delegate method for it to call back to.

Swift lets us do things in a much more lightweight fashion. We don’t need state, so we don’t need a class, structure, or enumeration. Honestly, a simple function gives us everything we need: we can pass in the URL and parameters, plus a closure to execute if and when the Twitter JSON data comes back.

Creating a sendTwitterRequest() Function

With the Twitter Utilities group selected in the File Navigator, choose File > New > File to create a new file. Select the Swift File template, and name the file TwitterAPIRequestUtilities. This is where we’re going to write our helper function.

We’ll start at the top of TwitterAPIRequestUtilities.swift by importing the Social and Accounts frameworks, as we’ll be using classes from both of them.

 import​ ​Social
 import​ ​Accounts

Let’s start declaring our generic Twitter-calling method:

 func​ sendTwitterRequest (requestURL: ​NSURL​,
  params : [​String​ : ​String​],
  completion : ​SLRequestHandler​) {

This method declaration takes three parameters: the Twitter URL and parameters for the request are what we need to create the SLRequest object that makes our Twitter call. The third parameter is the SLRequestHandler type that is sent to SLRequest’s performRequestWithHandler method. We haven’t seen this type in a while, but we’ve been using it constantly: it’s the method signature for a closure executed when the request comes back. Specifically, it’s a closure that takes the argument types (NSData!, NSHTTPURLResponse!, NSError!) and returns Void. If that rings a bell, it might be because it’s precisely the signature we pass through to our RootViewController’s handleTwitterData method.

Now we’re ready to write the implementation. It’s a lot of code, but it should look very familiar too:

 let​ accountStore = ​ACAccountStore​()
 let​ twitterAccountType =
  accountStore.accountTypeWithAccountTypeIdentifier(
 ACAccountTypeIdentifierTwitter​)
  accountStore.requestAccessToAccountsWithType(twitterAccountType,
  options: ​nil​,
  completion: {
  (granted: ​Bool​, error: ​NSError​!) -> ​Void​ ​in
 guard​ granted ​else​ {
 NSLog​ (​"account access not granted"​)
 return
  }
 let​ twitterAccounts =
  accountStore.accountsWithAccountType(twitterAccountType)
 guard​ twitterAccounts.count > 0 ​else​ {
 NSLog​ (​"no twitter accounts configured"​)
 return
  }
 let​ request = ​SLRequest​(forServiceType: ​SLServiceTypeTwitter​,
  requestMethod: .​GET​,
 URL​: requestURL,
  parameters: params)
  request.account = twitterAccounts.first ​as!​ ​ACAccount
  request.performRequestWithHandler(completion)
  })
 }

That’s a lot of code. We can write it fresh, or just copy and paste the contents from reloadTweets in RootViewController, with the following changes:

  • Remove the line that creates the twitterParams local variable.

  • Remove the line that creates the twitterAPIURL local variable.

  • In the SLRequest initializer, replace the twitterAPIURL and twitterParams local variables with the requestURL and params arguments.

  • Simplify the call to performRequestWithHandler by just passing through the completion closure that was passed to our function as a parameter.

Now we have a generic Twitter API request-maker that can be used by any and all view controllers that will want to make Twitter requests. To try it out, we’ll finish our refactoring by having RootViewController use this class.

Back in RootViewController.swift, we can now rewrite a much simpler reloadTweets to take just the arguments relevant to what this view controller needs, namely the user’s home timeline:

 func​ reloadTweets() {
 let​ twitterParams = [​"count"​ : ​"100"​]
 guard​ ​let​ twitterAPIURL = ​NSURL​(string:
 "https://api.twitter.com/1.1/statuses/home_timeline.json"​) ​else​ {
 return
  }
  sendTwitterRequest(twitterAPIURL,
  params: twitterParams,
  completion: { (data, urlResponse, error) -> ​Void​ ​in
 self​.handleTwitterData(data, urlResponse: urlResponse, error: error)
  })
 }

We still set up our NSURL with a guard let, but after that, all the Twitter stuff has moved to our utility function. Plus, our splitting off the response-handling still pays off, as the completion closure is just a trivial call to handleTwitterData.

Run it and…nothing’s changed! And that’s exactly what we want! The point of refactoring is to change the code while maintaining the same behavior, and that’s just what we’ve done—only now, it will be easier to grow the project, since much of the Twitter-specific code (and everything relating to the Accounts framework) is no longer in this view controller class, and what’s left is directly related to the specifics of this view controller’s Twitter request, and the specifics of the handling the response. The latter is still in handleTwitterData, unchanged from where we started the chapter.

Now that we’ve completed this refactoring, any other view controllers we write that need to call the Twitter API can use code much like what’s now in reloadTweets in that they’ll just need to provide a URL and a parameters dictionary, and parse the response in the completion handler closure. The details will be specific to the Twitter API they’re calling (tweet details, user info), but that’s exactly what we’d want anyway: genericize the common Twitter behavior, and let callers specify their unique details.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.154.220