📜 ⬆️ ⬇️

Retrieving deleted data in iOS

This is an author's translation of Chapter 6 Retrieving remote data from the book iOS7 in Action . Unlike the book, the entire interface is made programmatically, and the text describing how to do all this in the storyboard is removed accordingly. For simplicity, selected is the single position of the Portrait screen and the target iPhone platform.

We will create an application with a single Label on the screen, which will display a random joke about Chuck Norris, downloaded via the api.icndb.com/jokes/random site API at the time the application is launched.


Fig.1 Our application showing a joke about Chuck Norris.

Retrieving data using NSURLSession


Theory on HTTP request

Recall, for example, how HTTP communication occurs. Let us type in the browser URL: google.com/?q=Hello&safe=off google.com/?q=Hello&safe=off .
')
It can be divided into the following components:


With an HTTP request, we always specify a method. The method determines what the server needs to do with the information we sent.

In short, the browser connects to google.com and makes a GET request corresponding to the HTTP protocol to the root directory /, passing q = Hello & safe = off as the parameter.

When the browser parses the URL, the HTTP request will be presented like this:

 GET /?q=Hello&safe=off HTTP/1.1 Host: google.com Content-Length: 133 (…) 

The request consists of ASCII lines of text. In the first line of the GET method, followed by the path with parameters, then the HTTP version. Then follows a header containing details such as the requested host and the length of the request. The heading is separated from the body by two blank lines. For GET the body is empty.

Figure 2 shows the query diagram. First, a request is created, then a connection to the remote server is established, and the request is sent in plain text. Depending on the size of the request and the quality of the network, the request may take seconds, hours and even days.


Fig.2. The steps in the HTTP request: (a) normal HTTP interaction and (b) equivalents for each step from the point of view of the Objective-C implementation.

We create the software interface of our application

We create Empty Application in Xcode and in it the following files:

THSAppDelegate.h

 #import <UIKit/UIKit.h> @class THSViewController; @interface THSAppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) THSViewController *viewController; @end 

THSAppDelegate.m

 #import "THSAppDelegate.h" #import "THSViewController.h" @implementation THSAppDelegate - (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.viewController = [[THSViewController alloc] initWithNibName:nil bundle:nil]; self.window.rootViewController = self.viewController; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; } @end 

THSViewController.h

 #import <UIKit/UIKit.h> @interface THSViewController : UIViewController @property (nonatomic, strong) UILabel *jokeLabel; @end 

THSViewController.m

 #import "THSViewController.h" #import "THSViewController+Interface.h" @implementation THSViewController - (void)viewDidLoad { [super viewDidLoad]; [self addLabel]; } @end 

THSViewController + Interface.h

 #import "THSViewController.h" @interface THSViewController (Interface) - (void)addLabel; @end 

THSViewController + Interface.m

 #import "THSViewController+Interface.h" @implementation THSViewController (Interface) - (void)addLabel { CGFloat width = self.view.frame.size.width - 40.0f; CGFloat y = self.view.frame.size.height / 2.0f - 200.0f; CGRect labelFrame = CGRectMake(20.0f, y, width, 200.0f); self.jokeLabel = [[UILabel alloc] initWithFrame:labelFrame]; self.jokeLabel.text = @"Quotation goes here and continues and continues until I am fed up to type."; self.jokeLabel.lineBreakMode = NSLineBreakByWordWrapping; self.jokeLabel.textAlignment = NSTextAlignmentCenter; self.jokeLabel.numberOfLines = 0; self.jokeLabel.font = [UIFont systemFontOfSize:16.0f]; [self.view addSubview:self.jokeLabel]; } @end 

Creating an interface is rendered into a category so as not to mix the main functionality of the chapter with the label code and further buttons.

Run the application and see the interface:


Fig.3 Starting interface of the application.

Create Cocoa THSHTTPCommunication class

All UI operations are performed in the main thread. If you block the main thread, then all events will be blocked by touch, drawing graphics, animation, sounds, eventually the application will hang. Therefore, you cannot simply interrupt the application to wait for a request. To solve this problem, there are two techniques: create a new execution thread to control two simultaneous operations or configure an instance of the class as a delegate and implement the methods defined in the delegate protocol.

Most methods in Cocoa were originally designed as a delegate pattern to make time-consuming operations asynchronous. This means that the main thread will continue until the operation is completed, after which a certain method will be called.

With iOS 5, Objective-C supports blocks.

We will use the delegates in this example for a deeper understanding of networking. But remember that in iOS7 there is syntactic sugar, which uses blocks to simplify the execution of HTTP requests.

Create a class THSHTTPCommunication.

THSHTTPCommunication.h:

 #import <Foundation/Foundation.h> @interface THSHTTPCommunication : NSObject @end 

THSHTTPCommunication.m:

 #import "THSHTTPCommunication.h" @interface THSHTTPCommunication () @property(nonatomic, copy) void(^successBlock)(NSData *); @end @implementation THSHTTPCommunication @end 

where successBlock contains a block that will be called when the request is completed.

We implement HTTP interaction

Next, create a method in THSHTTPCommunication.m, responsible for HTTP communication. This method pulls jokes from a public API called icndb and returns information asynchronously using blocks.

THSHTTPCommunication.m:

 @implementation THSHTTPCommunication - (void)retrieveURL:(NSURL *)url successBlock:(void(^)(NSData *))successBlock { //   successBlock    self.successBlock = successBlock; //  ,   url NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url]; //  ,           NSURLSessionConfiguration *conf = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:conf delegate:self delegateQueue:nil]; //   NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request]; //  HTTP  [task resume]; } @end 

This method takes two parameters: the URL from which we get the content (in our case, the icndb API URL for receiving random jokes) and the block that will be called immediately after the request is completed. First you need to save this block to call later when the request is completed. The next step is to create an NSURLRequst object for this URL and use this request to establish an HTTP connection. [task resume] will not block execution. In this method, the compiler will show a warning, because we have not yet reported that the THSHTTPCommunication class complies with the NSURLSessionDownloadDelegate protocol. We will deal with this further.

Session delegate

We implement the NSURLSessionDownloadDelegate protocol to catch some of the messages, such as when we receive a new request.

First let us inform the compiler that THSHTTPCommunication is subject to this protocol.

THSHTTPCommunication.h:

 @interface THSHTTPCommunication : NSObject<NSURLSessionDownloadDelegate> @end 

The NSURLSessionDownloadDelegate protocol defines a set of methods that an instance of the NSURLConnection class can perform during an HTTP connection. We use URLSession: downloadTask: didFinishDownloadingToURL:

There are two more methods that we may need for more complex cases, like tracking the download process and the ability to resume a request. The names speak for themselves:

URLSession: downloadTask: didResumeAtOffset: expectedTotalBytes:
URLSession: downloadTask: didWriteData: totalBytesWritten: totalBytesExpectedToWrite:

But that's not all. In addition, the NSURLSession API provides three protocols:

NSURLSessionDelegate — This protocol defines delegate methods for handling session-level events as session or credential invalidation.
NSURLSessionTaskDelegate — This protocol defines delegate methods for handling connection events as redirects, errors, and data transfer.
NSURLSessionDataDelegate — This protocol defines delegate methods for handling data-level task-specific events and load tasks.

We receive the data from the answer

Now we are implementing a method that will allow us to receive data from a response in THSHTTPCommunication.m. NSURLSession will call this method as soon as the data becomes available and the download is complete.

THSHTTPCommunication.m:

 @implementation THSHTTPCommunication … - (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { //       NSData *data = [NSData dataWithContentsOfURL:location]; // ,   successBlock     dispatch_async(dispatch_get_main_queue(), ^{ //       self.successBlock(data); }); } @end 

This code is the last step in the interaction. We get a complete response and immediately call the block that we saved earlier. First we get the locally stored data that we received from the server. Note that the repository is represented by an instance of the NSURL class, but this time the URL is the path to the file containing the response data, not the remote URL.

We need to be sure that the call to the successBlock callback comes from the main thread. This is common practice, because most likely the method that implements the class does a specific task-specific task flow, such as the UI action.

In some cases, when we receive information from a remote server, the request may pass through several servers before reaching the destination. Fig.4 We are trying to get a picture located on t.co t.co , but the first answer is a redirect to the server containing the image. We need only the last answer (the picture itself).


Fig.4 Receiving pictures by abbreviated Twitter url generates a redirect.

Despite the fact that we can control redirects by implementing NSURLSessionTaskDelegate, we can allow NSURLSession to cope with all the details, which is the default behavior.

Make the newly created retrieveURL: successBlock: method available for the main controller. Open THSHTTPCommunication.h and add the method declaration:

THSHTTPCommunication.h:

 @interface THSHTTPCommunication : NSObject <NSURLSessionDownloadDelegate> - (void)retrieveURL:(NSURL *)url successBlock:(void(^)(NSData *))successBlock; @end 

Understanding data serialization and interaction with third-party services


We created an application and wrote logic to get data from a remote server. Now we’ll get random jokes about Chuck Norris using the API provided by icndb.com. This API, like all services that provide interaction with third-party services, have a normalized way of formatting information. Therefore, it is necessary to transform this format into something simple to use and manipulate. In other words, you need a way to convert formatted data into Objective-C objects.

Serialization

Figure 5 illustrates how the serialization process works. We see the sender (icndb server) on the left and the recipient (client) on the right. First a joke is generated, icndb saves it as binary data (it can be saved as a database, memory, file system or any other kind of storage). When a request comes from an application, the joke information is serialized and sent to us (the recipient). The application parses information and converts the data into native Objective-C objects.


Fig.5. Architecture of serialization and deserialization of messages.

There are different ways to share information, but we will focus on the most widely used serialization format: JavaScript Object Notation (JSON). JSON is the standard way of representing various types of data structures in text form. JSON defines a small set of rules for representing strings, numbers, and booleans. Along with XML, this is one of the most used serialization methods today. Let's look at an example of JSON in action:

 { "name": "Martin Conte Mac Donell", "age": 29, "username": "fz" } 

This code represents a dictionary enclosed in {} and consisting of key / value pairs. Keys can not be repeated. In our example, name, age and username are keys, Martin Conte Mac Donell, 29 and fz are values.

Code for jokes

Now we know that the JSON format is defined and how the serialization process works, back to the application. We implement the code to get jokes. In THSViewController.m, we add the jokeID internal variable to an unnamed category and the retrieveRandomJokes method.

THSViewController.m:

 #import "THSViewController.h" #import "THSViewController+Interface.h" #import "THSHTTPCommunication.h" @interface THSViewController () { NSNumber *jokeID; } @end @implementation THSViewController ... - (void)retrieveRandomJokes { THSHTTPCommunication *http = [[THSHTTPCommunication alloc] init]; NSURL *url = [NSURL URLWithString:@"http://api.icndb.com/jokes/random"]; //  ,    THSHTTPCommunication [http retrieveURL:url successBlock:^(NSData *response) { NSError *error = nil; //    NSDictionary *data = [NSJSONSerialization JSONObjectWithData:response options:0 error:&error]; if (!error) { NSDictionary *value = data[@"value"]; if (value && value[@"joke"]) { jokeID = value[@"id"]; [self.jokeLabel setText:value[@"joke"]]; } } }]; } @end 

We define the retrieveRandomJokes method and see how serialization comes from a code perspective. In this method, we use the previously created THSHTTPCommunication class to get data from icndb.com. Therefore, we immediately create an instance of the THSHTTPCommunication class and then call retrieveURL: successBlock :, responsible for receiving the data. As soon as THSHTTPCommunication receives a response from icndb.com, it calls the code inside the block passed as a parameter. At this point we have available data ready for analysis.

When information is received, it needs to be understood. You need a way to convert the newly loaded text into something you can easily manipulate. You need to select a joke and id from the answer. The process of converting serialized data (JSON) into data structures is called deserialization. Fortunately, starting with iOS 5, the Cocoa framework includes a class for parsing JSON. This is the NSJSONSerialization class, and parsing the response data is the first thing we do in a block.

The response from the icndb API is an associative array, represented with JSON as

 { "type": "success", "value": { "id": 201, "joke": "Chuck Norris was what Willis was talkin' about" } } 

We see that the answer is an associative array and the key “value” contains another associative array. Once NSJSONSerialization has completed deserialization, the associative JSON array will be converted to Objective-C NSDictionaries, arrays to NSArray, numbers to NSNumber, and strings to NSString. After that we get an object that can be used in the application.

Returning to retrieveRandomJokes: after deserialization, we assign an associative array from the key “value” of the deserialized response to the NSDictionary dictionary. Finally, we make the resulting jokes text with text labels in our interface.

It remains to call the retrieveRandomJokes: method when the view was loaded.

THSViewController.m:

 - (void)viewDidLoad { [super viewDidLoad]; [self addLabel]; [self retrieveRandomJokes]; } 

That's all, now run the application and see a new joke every time you start.

Source: https://habr.com/ru/post/270049/


All Articles