Chapter     11

Foundation Framework System Services

In this chapter, you will learn about Foundation Framework classes that provide system services. These classes implement a variety of operating system services for networking, file management, interprocess communication, system information retrieval, text processing, threading, and concurrency.

Network Services

The Foundation Framework includes various APIs to support network services. In the next few chapters, you’ll learn about APIs used to access network host information, and explore how to utilize the Bonjour network protocol to publish and discover network services.

NSHost

The NSHost class contains methods to access information for a network host, which is a collection of network names and addresses for a computer or device connected to a network. An NSHost object represents an individual network host. Its methods can be used to look up the network name and address information for the host or other objects on a network. The code in Listing 11-1 uses the NSHost currentHost and name methods to retrieve the NSHost object for the host the program is running on, and then logs the host name to the console.

Listing 11-1.  Retrieving the Network Host Name Using NSHost

NSHost myHost = [NSHost currentHost];
NSLog(@"Host name is %@", [myHost name]);

Listing 11-2 uses the NSHost hostWithName: and address methods to create an NSHost object with the domain name www.apress.com , and then logs the host address to the console.

Listing 11-2.  Retrieving a Host Address Using NSHost

NSHost *apress = [NSHost hostWithName:@"www.apress.com"];
NSLog(@"Apress host address is %@", [apress address]);

Bonjour Network Services

Bonjour is a protocol that automatically creates a functional IP network without the need for configuration or manual intervention, also known as zero configuration networking. Bonjour is designed to enable devices, such as computers and printers, to connect to a network automatically. It performs a variety of functions, including service discovery, address assignment, and hostname resolution.

The NSNetService and NSNetServiceBrowser classes provide functionality to manage Bonjour network services. The NSNetService class represents a network service that an application publishes or uses as a client. A service can be either a local service that your application is publishing or a remote service that your application wants to use. The NSNetServiceBrowser class is used to find published network services. Each class must provide a delegate object that performs processing upon completion of method invocation. The methods for each class are invoked asynchronously and performed within a run loop. Later in this chapter, you’ll learn how to use the NSRunLoop class to manage run loops.

Application Services

These classes consist of several general-purpose APIs used to retrieve and/or set information associated with an application process. The NSProcessInfo class is used to retrieve information about the currently executing program (i.e., process). The class method processInfo retrieves the single NSNProcessInfo object for the current process. NSProcessInfo includes instance methods used to retrieve process information, such as its command-line arguments, environment variables, host name, process name, and so forth. The following statement uses the processInfo and processName methods to retrieve the name of the current process.

NSString *name = [[NSProcessInfo processInfo] processName];

The NSUserDefaults class provides an API for managing user preferences, information stored persistently on the local file system and used to configure an application. An application provides preferences to enable users to customize its behavior. NSUserDefaults includes methods to both set and retrieve preferences, and manage other aspects of the preferences system. The set and retrieve methods support primitive types along with objects that are property list types (i.e., objects of type NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary). Preferences are stored in a user’s defaults database as key-value pairs.

The code in Listing 11-3 uses the standardUserDefaults, setObject:forKey: and objectForKey: methods to set a user preference, and then retrieve and log the value set to the console.

Listing 11-3.  Managing User Preferences Using NSUserDefaults

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"Hello" forKey:@"USER_SALUTATION"];
NSLog(@"Salutation is %@", [defaults objectForKey:@" USER_SALUTATION"];

Regular Expressions and Text Processing

NSRegularExpression is used to apply regular expression processing on strings. A regular expression is a pattern that provides a means of both specifying and identifying text. The class includes methods to create and initialize an NSRegularExpression object, and methods to perform a variety of regular expression operations (find matches, number of matches, etc.).

The Foundation Framework NSOrthography, NSSpellServer, and NSTextCheckingResult classes are primarily used for text processing. NSOrthography describes the linguistic content of text (the scripts the text contains, the dominant language and possibly other languages for the scripts, the dominant script and language for the text overall). NSSpellServer provides a mechanism for making an application’s spell checker available to any application as a spelling service. It includes methods for configuring a spelling server, providing spelling services, and managing the spell-checking process. The NSSpellServer class is available for use on the OS X platform only. The NSTextCheckingResult class is used to describe the items located by text checking. Each object represents the occurrence of the requested text found during the analysis of a block of text. The code in Listing 11-4 uses an NSRegularExpression object to retrieve an NSTextCheckingResult object and display its data.

Listing 11-4.  Retrieving an NSTextCheckingResult from a Regular Expression

NSError *error;
NSRegularExpression *regex = [NSRegularExpression
  regularExpressionWithPattern:@"World"
                       options:NSRegularExpressionCaseInsensitive
                         error:&error];
NSString *greeting = @"Hello, World!";
NSTextCheckingResult *match = [regex
  firstMatchInString:greeting
             options:0
               range:NSMakeRange(0, [greeting length])];
NSRange range = [match range];
NSLog(@"Match begins at %ldth character in string",
  (unsigned long)range.location);

As shown in Listing 11-4, a regular expression is created using the NSRegularExpression regularExpressionWithPattern: method. Next, the code uses the firstMatchInString:options:range: method to retrieve the result of the regular expression query as an NSTextCheckingResult. The pattern located by the query is then logged to the console with the NSTextCheckingResult range method.

File System Utilities

The File I/O classes consist of a collection of APIs for using files and directories. The classes enable you to represent file paths, perform basic operations with files and directories, find directories on the system, and use streams to perform input/output (I/O) operations with files, in-memory, or to/from a network resource.

Application Bundles

The NSBundle class groups code and resources that can be used in a program. They are used to locate program resources, dynamically load and unload executable code, and assist in localization. The NSBundle class includes methods that enable you to create and initialize a bundle, retrieve a bundle or bundle class, load a bundle’s code, find resources in a bundle, retrieve bundle information, and manage localization. In Chapters 7 and 9, you learned about loadable bundles and used the NSBundle class to dynamically load a bundle and create an object from a bundle class, so you can refer to these chapters for further information and code examples.

File Management

The NSFileHandle class provides low-level file management utilities. An NSFileHandle class instance is an object-oriented wrapper for a file descriptor. It includes methods for creating and initializing file handlers, retrieving a file descriptor, performing file I/O using a file handler, and closing a file when you are done. It also supports asynchronous file operations. Many of the NSFileHandle creation methods also acquire ownership of the file descriptor, and automatically close the file when the object is deallocated. The class also includes methods to create an NSFileHandle object with an input file descriptor. In these scenarios, the closeFile method can be used to close the descriptor. Listing 11-5 demonstrates the use of NSFileHandle APIs to read a file named Example.txt from the system’s default location to store temporary files.

Listing 11-5.  Using NSFileHandle to Read a File

NSString *tmpDir = NSTemporaryDirectory();
NSString *myFile = [NSString stringWithFormat:@"%@/%@", tmpDir,
                   @"Example.txt"];
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:myFile];
if (fileHandle)
{
  NSData *fileData = [fileHandle readDataToEndOfFile];
  NSLog(@"%lu bytes read from file %@", [fileData length], myFile);
}

The NSFileManager class provides general-purpose methods for performing basic file-system operations. It includes methods to create a file manager, locate directories, query directory contents, manage file and directory items, create soft/hard links to files, query file access, get/set file system attributes, and even manage iCloud-storage items. Listing 11-6 uses the NSFileManager APIs to list the contents at the current directory path, and then query whether the first file found in this directory is executable.

Listing 11-6.  Using NSFileManager to List the Contents of a Directory

NSFileManager *filemgr = [NSFileManager defaultManager];
NSString *currentPath = [filemgr currentDirectoryPath];
NSArray *contents = [filemgr contentsOfDirectoryAtPath:currentPath error:&error];
NSLog(@"Contents: %@", contents);
if (contents)
{
  NSString *file = [NSString stringWithFormat:@"%@/%@", currentPath, contents[0]];
  if ([filemgr isExecutableFileAtPath:file])
  {
    NSLog(@"%@ is executable", file);
  }
}

Stream Input-Output

Streams provide a convenient mechanism for reading and writing data sequentially, whether that data be located in memory, a file, or over a network. The NSStream, NSInputStream, and NSOutputStream classes provide functionality for reading from/writing to streams. NSStream is an abstract class, and NSInputStream and NSOutputStream are its concrete subclasses. A stream can be read/written asynchronously (usually the preferred approach) or synchronously by polling. To access a stream asynchronously, a delegate object is set for the stream (i.e., an NSInputStream or NSOutputStream object) and the stream is then scheduled in a run loop. The delegate conforms to the NSStreamDelegate protocol. The run loop invokes its stream:handleEvent: to handle stream events (status, data available, error conditions). Listing 11-7 demonstrates use of the NSInputStream APIs to create an input stream from a file, and then synchronously read data from a file named Example.txt (stored in the current directory) into a buffer.

Listing 11-7.  Using NSInputStream to Read a File

NSString *currentPath = [[NSFileManager defaultManager] currentDirectoryPath];
NSString *myFile = [NSString stringWithFormat:@"%@/%@", currentPath,
                   @"Example.txt"];
NSInputStream *ins = [NSInputStream inputStreamWithFileAtPath:myFile];
[ins open];
if (ins && [ins hasBytesAvailable])
{
  uint8_t buffer[1024];
  NSUInteger len = [ins read:buffer maxLength:1024];
  NSLog(@"Bytes read = %lu", len);
}

After creating the input stream, the stream is opened. Next, the NSInputStream hasBytesAvailable method is used to determine whether the stream has any bytes to read, and if so, the NSInputStream read method is used to read the data into a buffer. Finally, the number of bytes read is logged to the console.

Metadata Queries

The NSMetadataItem and NSMetadataQuery set of classes provide APIs for metadata queries that enable an application to search for files based on data that is part of the file or file system. Together they provide a programmatic way to perform file searches using file metadata, as provided with the Apple desktop Spotlight search tool.

The NSMetadataItem and NSMetadataQuery classes are available for use on the OS X platform only (i.e., this class is not available for the iOS platform).

Concurrency and Threading

The concurrency and threading support classes implement functionality that both manage threads and support the concurrent execution of multiple sections of code using threads. The following paragraphs provide a general introduction to these classes, refer to Chapter 17 for an in-depth guide to concurrent programming with the Objective-C platform.

Thread Management

The NSTask and NSThread classes are used to manage threads and processes. NSTask enables the creation and management of processes within the Objective-C runtime. An NSTask instance operates as a separate process that does not share memory space with another process, including the process that created it. An NSTask object can only be run once and its environment is configured prior to the task being started. The launchedTaskWithLaunchPath:arguments: method is used to create and start a new task that executes the program named greeting in the current directory, as shown in the following statement.

NSTask *hello = [NSTask launchTaskWithLaunchPath:@"./greeting"
                                       arguments:nil];

The init method can be used to create a new task; subsequently, the methods setLaunchPath:, setArguments:,(if the method has arguments) and launch would then be used to start the task.

NSTask *hello = [[NSTask alloc] init];
[hello setLaunchPath:@"./greeting"];
[hello launch];

The isRunning method can be used to query the state of a task, as shown in the following statement.

BOOL running = [hello isRunning];

A thread is an operating system mechanism that enables the concurrent execution of multiple sequences of instructions. A thread is typically implemented as a lightweight process, in that multiple threads can exist concurrently within the same process. Threads within a process can share computer memory and other resources. NSThread is used to create and control threads. The class includes methods to create and initialize an NSThread object, start and stop a thread, configure a thread, and query a thread and its execution environment. The NSThread class method detachNewThreadSelector:toTarget:withObject: is used to create and start a new thread. The following statement uses this method to create and start a new thread on the execute: method of an object named processEngine, with an input parameter of myData.

NSThread *myThread = [NSThread detachNewThreadSelector:@selector(execute:)
                                              toTarget:processEngine
                                            withObject:myData];

The thread priority can be set with the setThreadPriority: method, as shown in the following statement which sets the thread myThread to a priority of 1.0, the highest priority.

[myThread setThreadPriority:1.0];

A thread can be signaled to cancel with cancel method; the following statement sends this message to the thread myThread.

[myThread cancel];

Concurrent Operations

NSOperation, NSBLockOperation, and NSInvocationOperation are used to manage concurrent execution of one or more operations, code, and data associated with a single task. An operation queue is an Objective-C object that provides the ability to execute tasks concurrently. Each task (i.e., operation) defines the work to be performed, along with its associated data, and is encapsulated in either a block object or a concrete subclass of NSOperation. NSOperation is an abstract class that encapsulates the code and data associated with a single task. For nonconcurrent tasks, a concrete subclass typically only needs to override the main method. For concurrent tasks, you must override at a minimum the methods start, isConcurrent, isExecuting, and isFinished. Listing 11-8 provides an implementation of the class GreetingOperation, a concrete subclass of NSOperation that logs a simple greeting to the output pane.

Listing 11-8.  GreetingOperation Class

@interface GreetingOperation : NSOperation
@end

@implementation GreetingOperation
- (void)main
{
  NSLog(@"Hello, world!");
}
@end

NSOperationQueue controls the execution of NSOperation objects through a queuing system. Operation queues may employ threads to execute their operations; however, this implementation detail is hidden, thereby simplifying application development and reducing the potential for errors. Listing 11-9 creates an instance of a GreetingOperation, and then submits it to an NSOperationQueue to execute concurrently.

Listing 11-9.  Submitting GreetingOperation Instance to a Queue

NSOperation *greetingOp = [[GreetingOperation alloc] init];
[[NSOperationQueue mainQueue] addOperation:greetingOp];

Operation queues provide a simpler, more efficient mechanism for implementing concurrency and are therefore recommended in lieu of threads for implementing concurrent programming.

Locking

NSLock, NSDistributedLock, NSConditionLock, and NSRecursiveLock are used to create locks for synchronizing code execution. NSLock implements a basic mutual exclusion (mutex) lock for concurrent programming. It conforms to the NSLocking protocol and thus implements the lock and unlock methods to acquire and release a lock accordingly.

The NSDistributedLock class defines a lock that can be used by multiple applications on multiple hosts to control access to a shared resource.

The NSConditionLock class defines a lock that can be acquired and released only under certain conditions.

The NSRecursiveLock class defines a lock that can be acquired multiple times by the same thread without causing deadlock. It keeps track of the number of times it was acquired, and must be balanced by corresponding calls to unlock the object before the lock is released.

Timers and Run Loops  

A run loop is a thread-based mechanism used to schedule work and coordinate the receipt of input events. If a program thread needs to respond to incoming events, you need to attach it to a run loop in order to wake up this thread when new events arrive. The run method of the iOS UIApplication class (or NSApplication in OS X) starts an application’s main loop as part of the normal startup sequence; hence, if you use the Xcode template projects for creating iOS or OS X programs, you typically won’t need to create a run loop in your code. In other scenarios—when creating a command-line program, for example—you need to create a run loop if your program needs to respond to events from input sources. The Foundation Framework includes numerous classes that provide inputs asynchronously (for example, the networking, URL handling, and stream I/O APIs), so you would use a run loop in conjunction with instances of these classes.

The NSRunLoop class provides the API for managing run loops. This class includes methods to access run loops and modes, manage timers and ports, run a loop, and manage messages. The NSRunLoop currentRunLoop method retrieves the current run loop, the NSRunLoop object for the current thread. If a run loop doesn’t already exist for the thread, it is created and returned. The following statement retrieves the run loop for the current thread.

NSRunLoop *loop = [NSRunLoop currentRunLoop];

The run mode defines the set of input sources for a run loop. The Foundation Framework defines a set of constants that specify the available run loop modes. The constant NSDefaultRunLoopMode is the most commonly used run-loop mode.

There are several NSRunLoop methods used to run a loop. The run method puts the thread in a permanent loop in the NSDefaultRunLoopMode, during which it processes events from all input sources. If you want a run loop to terminate, you shouldn’t use this method, but rather one of the other methods used to conditionally run a loop based on receipt of input and/or a specified amount of time has elapsed.

The runMode:beforeDate: method runs the loop once in the NSDefaultRunLoopMode, blocking for input until an event is received from an attached input source or the date specified by the beforeDate: parameter arrives. The following statement invokes this method, waiting indefinitely until input is received.

[loop runMode: NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

Providing a date parameter value of [NSDate distantFuture] evaluates to an infinite wait until input is received.

The NSTimer class is used to create timers that send a specified message to a target object after a certain time has elapsed. They work in conjunction with NSRunLoop objects to provide synchronous delivery of events to a thread. An NSTimer determines the maximum amount of time a run loop object should wait for input. NSTimer includes methods to create and initialize a timer, fire a timer, stop a timer, and retrieve information about a timer. The scheduledTimerWithTimeInterval:invocation:repeats: method creates a timer that fires after the specified interval, using the invocation specified, and repeating the timer if requested. The invalidate method stops a timer from firing again. The isValid method returns a Boolean value indicating whether or not the timer is currently valid.

Creating a Bonjour Network Service Client

Now you’re going to create a program that demonstrates the use of the Bonjour network services APIs and run loops. This program will create a service browser that asynchronously looks for registered services of specified types.

In Xcode, create a new project by selecting New image Project . . . from the Xcode File menu. In the New Project Assistant pane, create a command-line application. In the Project Options window, specify BonjourClient for the Product Name, choose Foundation for the Project Type, and select ARC memory management by selecting the Use Automatic Reference Counting check box. Specify the location in your file system where you want the project to be created (if necessary, select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

Now you’re going to create a class that downloads a URL resource using the URL loading APIs. Select New image File . . . from the Xcode File menu, then select the Objective-C class template, and name the class BonjourClient. Then select the BonjourClient folder for the files location and the BonjourClient project as the target, and then click the Create button. Next, in the Xcode project navigator pane, select the resulting header file named BonjourClient.h and update the interface, as shown in Listing 11-10.

Listing 11-10.  BonjourClient Interface

#import <Foundation/Foundation.h>

@interface BonjourClient : NSObject <NSNetServiceBrowserDelegate>

@property (retain) NSNetServiceBrowser *serviceBrowser;
@property BOOL finishedLoading;

@end

The BonjourClient interface adopts the NSNetServiceBrowserDelegate protocol, thereby enabling it to asynchronously load data from an NSNetServiceBrowser instance. The serviceBrowser property is the NSNetServiceBrowser instance. The finishedLoading property is used to indicate when the NSNetServiceBrowser instance is finished loading data. Now select the BonjourClient.m file and update the implementation, as shown in Listing 11-11.

Listing 11-11.  BonjourClient Implementation

#import "BonjourClient.h"

@implementation BonjourClient

- (id)init
{
  if ((self = [super init]))
  {
    _finishedLoading = NO;
    _serviceBrowser = [[NSNetServiceBrowser alloc] init];
    [_serviceBrowser setDelegate:self];
}
  return self;
}

#pragma mark -
#pragma mark NSNetServiceBrowserDelegate methods

- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)netServiceBrowser
{
  NSLog(@"Beginning search");
}

- (void)netServiceBrowser:(NSNetServiceBrowser *)sb
           didFindService:(NSNetService *)ns
               moreComing:(BOOL)moreComing
{
  NSLog(@"Found service: %@", ns);
  if (!moreComing)
  {
    // No more services, stop search
    [self.serviceBrowser stop];
  }
}

- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)netServiceBrowser
{
  // Stopped search, set flag to exit run loop
  NSLog(@"Stopped search");
  self.finishedLoading = YES;
}

@end

All right, let’s analyze this code one method at a time. The init method initializes the finishedLoading property to a value of NO to indicate that the BrowserClient instance has not finished browsing for services. It then creates a service browser instance and assigns it to the corresponding property, setting itself as the delegate object. This means that the BrowserClient instance will implement the necessary NSNetServiceBrowserDelegate protocol methods, thereby responding to asynchronous events when its service browser instance is searching for registered services.

The netServiceBrowserWillSearch: message is sent to the delegate by the service browser instance when it begins searching for services. The implementation here just logs a message to the output pane, indicating that the browser is beginning a search for services.

The netServiceBrowser:didFindService:moreComing: message is sent to the delegate by the service browser instance when it finds a registered service. The method implementation here just logs the method found to the output pane, and then performs a conditional check to see if the service browser is waiting for additional services. If there are no more services available, the browser is sent a stop message with the following statement.

[self.serviceBrowser stop];

The netServiceBrowserDidStopSearch: message is sent to the delegate by the service browser instance when a search for services is stopped. The implementation logs a message to the output pane, indicating that the search was stopped, and then sets the finishedLoading property to YES.

OK, now that you have implemented the BonjourClient class, let’s use this to search for registered services using the Bonjour protocol.  In the Xcode project navigator, select the main.m file and update the main() function, as shown in Listing 11-12.

Listing 11-12.  BonjourClient main( ) Function

#import <Foundation/Foundation.h>
#import "BonjourClient.h"

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    // Retrieve the current run loop for browsing
    NSRunLoop *loop = [NSRunLoop currentRunLoop];
    // Create a browser client and add its service browser to the current run loop
    BonjourClient *client = [[BonjourClient alloc] init];
    // Browse for the specified service types
    [client.serviceBrowser searchForServicesOfType:@"_ipp._tcp."
                                          inDomain:@"local."];
    // Loop until the browser is stopped
    while (!client.finishedLoading &&
           [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
  }
  return 0;
}

The main() function begins by retrieving the current run loop, required for asynchronous URL loading with NSNetServiceBrowser objects. Next, a BonjourClient instance is created and initialized. Then the service browser begins browsing for registered services using the searchForServicesOfType:inDomain: method. Here you specify as types services those that use the IPP (internet printing protocol, used for printers) and TCP (transport control protocol) protocols, and are registered in the local domain. The next statement is a run loop used to keep the application running until the service browser has finished searching for registered services.

while (!client.finishedLoading &&
       [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

The while loop has a conditional expression composed of two parts, and is followed by a semicolon, signifying an empty statement. Thus there is no logic executed within the body of the while loop; it is used just to keep the application running until the asynchronous search being performed by the service browser has finished. The first part of the conditional expression, !client.finishedLoading, checks the value of this Boolean property. If it is YES, then the while loop is exited; otherwise, you continue on to process the remainder of the expression.

Next, the expression [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]] executes the run loop once, blocking for input from an input source (for example, receipt of an NSNetServiceBrowser message). Once the input is received, the message is processed on the current thread (i.e., the corresponding delegate method is invoked) and the loop begins again. Thus the while loop will not be exited until the finishedLoading property is set. As shown in Listing 11-10, this property is set by the BonjourClient delegate method netServiceBrowserDidStopSearch:, the matching message is sent by the service browser when it has finished the search.

Earlier in this chapter, you learned that the Bonjour protocol enables the detection of devices (such as computers and printers) connected on a network. Hence, if possible, make sure that you have one or more devices connected to your local area network when you test this program. Once you have performed these configuration steps and compiled and run the BonjourClient program, you should observe messages in the output pane similar to those shown in Figure 11-1.

9781430250500_Fig11-01.jpg

Figure 11-1. BonjourClient program output

As shown in Figure 11-1, the services retrieved are listed in the output pane, along with messages indicating the beginning and end of the search. And that’s it. This hands-on exercise showed you how to write a program that uses the Foundation Framework APIs to asynchronously find registered services. Feel free to take some time to review this program and the key features covered here—using run loops and the Bonjour network protocol. Once you’re done, let’s move on to URL handling!

URL Handling

The URL handling classes are used for interacting with URLs and communicating with resources using standard Internet protocols (ftp, http, https, local files). The classes provide the following functionality:

  • URL loading
  • Cache management
  • Authentication and credentials management
  • Cookie management
  • Protocol support

These classes also support proxy servers and SOCKET Secure (SOCKS) gateways per the user’s system preferences.

URL Loading

NSURL objects are used to manage URLs and the resources they refer to. The class includes methods for creating and initializing NSURL objects, querying a URL, and accessing the components of a URL (e.g., host, port, etc.). NSURL also provides methods for working with bookmark data. A bookmark is a persistent reference to the location of a file. It is a valuable tool for locating files because it can be used to re-create a URL to a file, even if the file is moved or renamed. The following code fragment demonstrates the use of the NSURL APIs to create an NSURL object and access its components.

NSURL *url = [NSURL URLWithString:@"http://www.apress.com:80"];
NSLog(@"URL: scheme %@, host %@, port %@", url.scheme, url.host, url.port);

The classes NSURLRequest and NSMutableURLRequest are used to represent a URL load request. NSURLRequest is independent of the network protocol and URL scheme. The class includes methods to create and initialize a URL request, and to retrieve request properties. NSMutableURLRequest is a subclass of NSURLRequest that provides methods that enable you to change a URL and its component parts. The following statement creates a mutable URL request, and then uses the setHTTPMethod: method to set the HTTP request method value.

NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:
                            [NSURL URLWithString:@"http://www.apress.com:80"]];
[req setHTTPMethod:@"GET"];

NSURLResponse and NSHTTPURLResponse classes are used to represent the response returned by a URL request. An NSURLConnection object is created by either an object that performs a synchronous load of a URL request (sendSynchronousRequest:returningResponse:error:) or objects that conform to the NSURLConnectionDataDelegate protocol. The NSHTTPURLResponse class is a subclass of NSURLResponse that provides methods for accessing HTTP-specific information returned by a URL request.

The NSURLConnection and NSURLDownload classes are used to download the contents of a resource identified by a URL. NSURLConnection supports synchronous and asynchronous resource loading, starting and stopping a connection, and managing a delegate object used to control various aspects of a URL connection (i.e., cache management, authentication and credentials, protocol support, and cookie management). For asynchronous loading, a delegate object must be set. This object must conform to the NSURLConnectionDelegate protocol and thus implement, at a minimum, the following methods:

  • connection:didReceiveData: (to retrieve data loaded)
  • connection:didFailWithError: (to handle connection errors
  • connection:didFinishLoading: (to perform processing after a connection has finished loading data)

Listing 11-13 demonstrates use of various URL loading APIs to synchronously load the data from a URL.

Listing 11-13.  Using NSURLConnection to Synchronously Load the Contents of a URL

NSURL *url = [NSURL URLWithString:@"http://www.apress.com/index.html"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response;
NSError *aerror;
NSData *data = [NSURLConnection sendSynchronousRequest:request
                                     returningResponse:&response
                                                 error:&aerror];
NSLog(@"Expected content length = %lld; loaded %lu bytes",
      [response expectedContentLength], [data length]);

As shown in Listing 11-13, the code creates an NSURL instance and then a corresponding NSURLRequest. It then creates a connection and loads the data using the NSURLConnection sendSynchronousRequest:returningResponse:error: method. The result is returned in an NSData object.

The NSURLDownload class is used to asynchronously download the contents of a URL to a file. It includes methods to initialize a download (set request and delegate), set the destination path, resume a partial download, and cancel loading the request. The class also provides support for decoding files stored in the MacBinary, BinHex, and gzip formats. The delegate object must conform to the NSURLDownloadDelegate protocol and thus implement, at a minimum, the following methods:

  • download:didFailWithError: (to handle connection errors)
  • downloadDidFinish: (to perform processing after a connection has finished loading data)

The NSURLDownload class is available for use on the OS X platform only (i.e., this class is not available for the iOS platform).

Cache Management

The cache management classes (NSURLCache and NSCachedURLResponse) provide a cache memory for responses to URL requests. The NSURLCache class provides a general-purpose cache for URLs, whereas the NSCachedURLResponse encapsulates a URL response (NSURLResponse object) and the data loaded from the URL (an NSData object). An NSURLConnection delegate object implements the connection:willCacheResponse: method, providing a NSCachedURLResponse object initialized with an NSURLCache, to manage caching. How this works is best illustrated by an example; so now you’ll create a program that uses the URL loading classes to download a resource. Let’s begin!

Downloading a Resource with the URL Loading APIs

In Xcode, create a new project by selecting New image Project . . . from the Xcode File menu. In the New Project Assistant pane create a command-line application. In the Project Options window, specify NetConnector for the Product Name, choose Foundation for the Project Type, and select ARC memory management by selecting the Use Automatic Reference Counting check box. Specify the location in your file system where you want the project to be created (if necessary select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

Now you’re going to create a class that downloads a URL resource using the URL loading APIs. Select New image File . . . from the Xcode File menu, select the Objective-C class template, and name the class NetConnector. Select the NetConnector folder for the files location and the NetConnector project as the target, and then click the Create button. Next, in the Xcode project navigator pane, select the resulting header file named NetConnector.h and update the interface, as shown in Listing 11-14.

Listing 11-14.  NetConnector Interface

#import <Foundation/Foundation.h>
#define HTTP_SCHEME       @"http"
#define CACHE_MEMORY_SIZE (4 * 1024 * 1024)

@interface NetConnector : NSObject <NSURLConnectionDelegate>

@property (readonly) BOOL finishedLoading;

- (id) initWithRequest:(NSURLRequest *)request;
- (void) reloadRequest;

@end

The NetConnector interface adopts the NSURLConnectionDelegate protocol, thereby enabling it to asynchronously load data from an NSURLConnection instance. The property finishedLoading is used to indicate when the NSURLConnection instance is finished loading data. The initWithRequest: method initializes a NetConnector object and loads a URL using the input NSURLRequest. The reloadRequest method is used to reload the input NSURLRequest. Now select the NetConnector.m file and update the implementation, as shown in Listing 11-15.

Listing 11-15.  NetConnector Implementation

#import "NetConnector.h"

// Extension to declare provide properties
@interface NetConnector()

@property NSURLRequest *request;
@property BOOL finishedLoading;
@property NSURLConnection *connector;
@property NSMutableData *receivedData;

@end

@implementation NetConnector

- (id) initWithRequest:(NSURLRequest *)request
{
  if ((self = [super init]))
  {
    _request = request;
    // Create URL cache with appropriate in-memory storage
    NSURLCache *URLCache = [[NSURLCache alloc] init];
    [URLCache setMemoryCapacity:CACHE_MEMORY_SIZE];
    [NSURLCache setSharedURLCache:URLCache];
    // Create connection and begin downloading data from resource
    _connector = [NSURLConnection connectionWithRequest:request delegate:self];
  }
  return self;
}

- (void) reloadRequest
{
  self.finishedLoading = NO;
  self.connector = [NSURLConnection connectionWithRequest:self.request
                                                 delegate:self];
}

#pragma mark -
#pragma mark Delegate methods

- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
  if (self.receivedData != nil)
  {
    [self.receivedData setLength:0];
  }
}

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                  willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
  NSURLResponse *response = [cachedResponse response];
  NSURL *url = [response URL];
  if ([[url scheme] isEqualTo:HTTP_SCHEME])
  {
    NSLog(@"Downloaded data, caching response");
    return cachedResponse;
  }
  else
  {
    NSLog(@"Downloaded data, not caching response");
    return nil;
  }
}

- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
   if (self.receivedData != nil)
   {
     [self.receivedData appendData:data];
   }
   else
   {
     self.receivedData = [[NSMutableData alloc] initWithData:data];
   }
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
  NSUInteger length = [self.receivedData length];
  NSLog(@"Downloaded %lu bytes from request %@", length, self.request);
  // Loaded data, set flag to exit run loop
  self.finishedLoading = YES;
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
  NSLog(@"Error loading request %@", [error localizedDescription]);
  self.finishedLoading = YES;
}

@end

OK, so I know that this is a lot of code, but don’t worry, you’ll take it one step at a time!. The file begins with a category extension that declares private properties used within the NetConnector implementation. The implementation defines both the methods declared by the interface and methods declared by the NSURLConnectionDelegate protocol. The initWithRequest: method assigns the input NSURLRequest to the private NetConnector request property, creates and initializes an NSURLCache instance for caching responses, and ends by creating an NSURLConnection object and starting the download of the URL. The following statements from Listing 11-15 create the cache, configure it with in-memory storage, and then set it to be the shared cache used with the connection.

NSURLCache *URLCache = [[NSURLCache alloc] init];
[URLCache setMemoryCapacity:CACHE_MEMORY_SIZE];
[NSURLCache setSharedURLCache:URLCache];

The reloadRequest method reloads the URL. It first resets the finishedLoading flag, and then creates a new NSURLConnection object with the saved request and downloads the URL.

The remaining methods implemented here are NSURLConnectionDelegate protocol methods. The connection:didReceiveResponse: message is sent when a connection is able to send a response to the request. As shown in Listing 11-15, this method sets the receivedData object to a length of zero, thereby discarding data received from previous requests.

The connection:willCacheResponse: message is sent before a connection stores the response in the cache. This enables the method implementation to modify the response or prevent it from being cached. As shown in Listing 11-15, the method performs conditional logic that enables/disables caching of the response based on whether or not the scheme for the URL is http.

The connection:didReceiveData: message is sent as the data is received from the resource. This method may be called multiple times during a single request if the data is received incrementally. As shown in Listing 11-15, the method appends the received data to the receivedData object.

The connectionDidFinishLoading: message is sent when a connection has finished loading the data successfully. The method implementation here just logs a message to the output pane, indicating that the connection has finished loading the resource, and then sets the finishedLoading flag used to exit the run loop.

Finally, the connection:didFailWithError: message is sent when a connection fails to load a request successfully. Here you just log a message to the output pane, indicating the error that occurred, and set the finishedLoading flag.

OK, now that you have implemented the NetConnector class, let’s use this to load a URL. In the Xcode project navigator, select the main.m file and update the main() function, as shown in Listing 11-16.

Listing 11-16.  NetConnector main( ) Function

#import <Foundation/Foundation.h>
#import "NetConnector.h"

#define INDEX_URL       @"http://www.wikipedia.com/index.html"

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    // Retrieve the current run loop
    NSRunLoop *loop = [NSRunLoop currentRunLoop];

    // Create the request with specified cache policy, then begin downloading!
    NSURLRequest *request = [NSURLRequest
                             requestWithURL:[NSURL URLWithString:INDEX_URL]
                                cachePolicy:NSURLRequestReturnCacheDataElseLoad
                            timeoutInterval:5];
    NetConnector *netConnect = [[NetConnector alloc] initWithRequest:request];
    // Loop until finished loading the resource (Note the empty statement!)
    while (!netConnect.finishedLoading &&
           [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

    // Log the amount of memory being used by the cache
    NSLog(@"Cache memory usage = %lu bytes", [[NSURLCache sharedURLCache]
                                              currentMemoryUsage]);

    // Reload data from request, this time it will be retrieved from the cache!
    [netConnect reloadRequest];
    while (!netConnect.finishedLoading &&
           [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    // Zero out cache
    [[NSURLCache sharedURLCache] removeAllCachedResponses];
  }
  return 0;
}

The main() function begins by retrieving the current run loop, required for asynchronous URL loading with NSURLConnection objects. Next, an NSURLRequest instance is created using the convenience constructor requestWithURL:cachePolicy:timeoutInterval:. This constructor enables you to select the cache policy for the request. The policy NSURLRequestReturnCacheDataElseLoad specifies that a cache value should be used (even if out of date) if available; otherwise, the data should be loaded from the resource. Note that if a URL does not support caching, the data will not be loaded from the cache, even if the policy specifies otherwise. A NetConnector instance is then created using the request and its NSURLConnection object begins downloading the resource. The next statement is a loop used to keep the application running until the connection has finished loading the resource.

while (!netConnect.finishedLoading &&
       [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

Looks familiar, doesn’t it? This loop is identical to the one used for the Bonjour service browser program that you implemented earlier in this chapter. It runs the loop, receiving events from its input sources and executing any corresponding delegate methods, until the connection has finished loading the resource. The next statement logs the cache memory usage to the output pane, enabling us to view cache utilization. Then you reload the request. As shown in Listing 11-16, the next set of statements reloads the URL. As the data is already stored in the cache and the cache policy specifies that a cache value should be used, it is immediately retrieved without being loaded from the URL. Finally, the in-memory cache is zeroed out, thereby freeing this memory. Now when you compile and run the NetConnector program, you should observe the messages in the output pane, as shown in Figure 11-2

9781430250500_Fig11-02.jpg

Figure 11-2. NetConnector program output

.

As shown in the output pane, the response is cached after the data is downloaded. The total amount of data is then logged to the console, along with the URL of the request. Next, the cache memory utilization is output. This is identical to the data downloaded. The request is then reloaded. Because the response is already in the cache, it is retrieved from there (notice that the delegate method connection:willCacheResponse: is not invoked). OK, that was pretty involved, wasn’t it? Well, you now have a pretty good handle on the use of the URL loading APIs to asynchronously download a URL. Once you’re ready, let’s move on and look into handling authentication challenges when trying to load a resource.

Authentication and Credentials Management

The authentication and credentials classes ( NSURLProtectionSpace, NSURLCredentialStorage, NSURLCredential, NSURLAuthenticationChallenge, and NSURLAuthenticationChallengeSender ) provide support for authenticating users requesting access to protected URLs. A resource that requires authentication requests credentials from a client attempting to load the resource. The Foundation Framework NSURLAuthenticationChallenge class encapsulates a challenge from a server that requires authentication from the client. NSURLCredential represents an authentication credential returned by a user in response to an authentication challenge. The delegate protocols for NSURLConnection and NSURLDownload instances are sent messages when a connection request issues an authentication challenge. The corresponding methods should be implemented to return the appropriate credential.

For the NSURLConnectionDelegate protocol, the message connection:willSendRequestForAuthenticationChallenge: should be implemented to return the appropriate credential. An example implementation of this method for the NetConnector program is shown in Listing 11-17.

Listing 11-17.  Handling Authentication Challenges

- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
  NSURLCredential *credential =
    [NSURLCredential credentialWithUser:@"TestUser"
                               password:@"TestPassword"
                            persistence:NSURLCredentialPersistenceForSession];
  [[challenge sender] useCredential:credential
         forAuthenticationChallenge:challenge];
}

As shown in Listing 11-17, the method creates a credential with a user name of TestUser and a password of TestPassword;. The useCredential:forAuthenticationChallenge: message uses this credential to respond to an authentication challenge for the connection.

Cookie Management

The Foundation Framework classes NSHTTPCookie and NSHTTPCookieStorage facilitate the creation and management of HTTP cookies, which are used to provide persistent storage of data across URL requests. An NSHTTPCookie instance represents a cookie, and NSHTTPCookieStorage is a singleton object used to manage cookies. For OS X applications, cookies are shared and kept in sync across processes. Session cookies are local to a single process and not shared between programs. The NSHTTPCookie class provides methods to create cookies, convert cookies to request headers, and retrieve cookie properties. The NSHTTPCookieStorage class provides methods for retrieving the shared cookie storage instance, managing the cookie accept policy, and managing (i.e., adding, removing, retrieving) cookies.

Protocol Support

The Foundation Framework classes NSURLProtocol and NSURLProtocolClient enable the creation of custom protocols for loading data from a URL. NSURLProtocol is an abstract class that provides the basic structure to perform protocol-specific URL loading. It includes methods for creating NSURLProtocol objects, registering/unregistering protocol classes, request management, retrieving protocol attributes, and starting/stopping downloads. NSURLProtocolClient is a protocol used by NSURLProtocol subclasses to communicate with the URL loading system.

Interprocess Communication

The Foundation Framework includes a collection of classes that support process-to-process communication. Specifically, they provide facilities for creating and using communication channels.

Communication via Pipes

The NSPipe class encapsulates a pipe, a one-way channel for communication between processes. The API includes methods to create and initialize a pipe, and retrieve the corresponding NSFileHandle instance for a pipe. The NSTask class provides several methods for setting the input and output channels for a process (setStandardOutput:, setStandardInput:). Listing 11-18 demonstrates the use of the NSTask and NSPipe APIs to create and launch a task (the Unix command /bin/ls simply lists the files in the current directory), set the task’s standard output, and then retrieve and log the standard output written to the pipe after task completion.

Listing 11-18.  Using NSPipe and NSTask to Invoke a Process

NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:@"/bin/ls"];
NSPipe *outPipe = [NSPipe pipe];
[task setStandardOutput:outPipe];
[task launch];
NSData *output = [[outPipe fileHandleForReading] readDataToEndOfFile];
NSString *lsout = [[NSString alloc] initWithData:output
                                        encoding:NSUTF8StringEncoding];
NSLog(@"/bin/ls output: %@", lsout);

Communication via Ports

NSPort, NSMachPort, NSMessagePort, and NSSocketPort provide low-level mechanisms for communication between threads or processes, typically via NSPortMessage objects.

NSPort is an abstract class. It includes methods to create and initialize a port, create connections to a port, set port information, and monitor a port. NSMachPort, NSMessagePort, and NSSocketPort are concrete subclasses of NSPort used for specific types of communication ports. NSMachPort and NSMessagePort allow local (on the same machine) communication only. In addition, NSMachPort is used with Mach ports, the fundamental communication port in OS X. NSSocketPort allows both local and remote communication, but may be less efficient than the others when used for local communication. Port instances can be supplied as parameters when creating an NSConnection instance (using the initWithReceivePort:sendPort: method). You can also add a port to a run loop via the NSPort scheduleInRunLoop:forMode: method.

As ports are a very low-level interprocess communication mechanism, you should implement interapplication communication using distributed objects whenever possible and use NSPort objects only when necessary. In addition, when you are finished using a port object, you must explicitly invalidate the object via the NSPort invalidate method.

The NSSocketPort and NSPortMessage classes are only available for use on the OS X platform.

Port Registration

NSPortNameServer, NSMachBootstrapServer, NSMessagePortNameServer, and NSSocketPortNameServer provide an interface to the port registration service, used to retrieve instances of NSMachPort, NSMessagePort, and NSSocketPort .

NSPortNameServer provides an object-oriented interface to the port registration service used by the distributed objects system. NSConnection objects use it to contact each other and to distribute objects over the network. You should rarely need to interact directly with an NSPortNameServer instance. NSMachBootstrapServer, NSMessagePortNameServer, and NSSocketPortNameServer are subclasses of NSPortNameServer that return corresponding port instances (NSMachPort, NSMessagePort, NSSocketPort).

The NSPortNameServer, NSMachBootstrapServer, NSMessagePortNameServer, and NSSocketPortNameServer classes are only available for use on the OS X platform.

Roundup

In this chapter, you began your review of the Foundation Framework, focusing on classes that provide common, general-purpose functionality required for most Objective-C programs. You should now be familiar with Foundation Framework classes that provide the following functionality:

  • Network services
  • Application services
  • File system utilities
  • URL handling
  • Interprocess communication
  • Concurrency and threading
..................Content has been hidden....................

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