- Section 1: JavaScript Device Activation
- Section 2: Objective-C Device Activation
- Section 3: Objective-C Implementation of the QuickConnectiPhone Architecture
- Summary
Section 3: Objective-C Implementation of the QuickConnectiPhone Architecture
The code shown in Sections 1 and 2 depends heavily on an implementation in Objective-C of the same architecture, which is explained in Chapter 2. This section shows how to implement the architecture in Objective-C. To see a full explanation of each component, see Chapter 2, which contains the JavaScript implementation.
As in the JavaScript implementation, all requests for application behavior are handled via a front controller. The front controller is implemented as the class QuickConnect, the source for which is found in the QuickConnect.m and QuickConnect.h files. Because messages sent to QuickConnect might need to be made from many different locations throughout an application, this class is a singleton.
Singleton classes are written so that only one instantiated object of that class can be allocated within an application. If done correctly, there is always a way to obtain a pointer to this single object from anywhere in the application. With the QuickConnect singleton object, this is accomplished by implementing a class method getInstance that returns the single QuickConnect instance that is allocated the first time this method is called.
Because it is a class method, a getInstance message can be sent to the class without instantiating a QuickConnect object. When called, it returns a pointer to the underlying QuickConnect instance. As seen in the following code, this is accomplished by assigning an instance of the class to a statically defined QuickConnect pointer.
+ (QuickConnect*)getInstance{ //since this line is declared static //it will only be executed once. static QuickConnect *mySelfQC = nil; @synchronized([QuickConnect class]) { if (mySelfQC == nil) { mySelfQC = [QuickConnect singleton]; [mySelfQC init]; } } return mySelfQC; }
The singleton message sent prior to init uses the behavior defined in the QuickConnect objects' superclass FTSWAbstractSingleton. This superclass allocates the embedded singleton behavior such as overriding new, clone, and other methods that someone might incorrectly attempt to use to allocate another QuickConnect instance. Because of this, only the getInstance method can be used to create and use a QuickConnect object. As with all well-formed objects in Objective-C, after a QuickConnect object has been allocated, it must be initialized.
Both the allocation and initialization of the object happen only if no QuickConnect object has been assigned to the mySelfQC attribute. Additionally, because of the synchronization call surrounding the check for the instantiated QuickConnect object, the checking and initialization are thread safe.
- (void) handleRequest: (NSString*) aCmd withParameters:(NSArray*) parameters is another method of the QuickConnect class. Just as with the JavaScript handleRequest(aCmd, parameters) function from Chapter 2, this method is the way to request functionality be executed in your application.
A command string and an array of parameters are passed to the method. In the following example, lines 3–9 show that a series of messages are sent to the application controller. Lines 3 and 4 first execute any VCOs associated with the command. If the command and parameters pass validation, any BCOs associated with the command are executed by a dispatchToBCO message. This message returns an NSMutableArray that contains the original parameters array data to which has been added any data accumulated by any BCO object that might have been called.
1 - (void) handleRequest: (NSString*) aCmd 2 withParameters:(NSArray*) parameters{ 3 if([self->theAppController dispatchToValCO:aCmd 4 withParameters:parameters] != nil){ 5 NSMutableArray *newParameters = 6 [self->theAppController dispatchToBCO:aCmd 7 withParameters:parameters]; 8 [self->theAppController dispatchToVCO:aCmd 9 withParameters:newParameters]; 10 } 11 }
After the completion of the call to dispatchToBCO:withParameters, a dispatchToVCO:withParameters message is sent. This causes any VCOs also associated with the given command to be executed.
By using the handleRequest:withParameters method for all requests for functionality, each request goes through a three-step process.
- Validation.
- Execution of business rules (BCO).
- Execution of view changes (VCO).
As in the JavaScript implementation, each dispatchTo method is a façade. In this case, the underlying Objective-C method is dispatchToCO:withParameters.
This method first retrieves all the command objects associated with the default command in aMap the passed parameter. aMap contains either BCOs, VCOs, or ValCOs depending on which façade method was called. These default command objects, if any, are retrieved and used for all commands. If you want to have certain command objects used for all commands, you do not need to map them to each individual command. Map them to the default command once instead.
For the retrieved command objects to be used, they must be sent a message. The message to be sent is doCommand. Lines 19–23 in the following example show this message being retrieved as a selector and the performSelector message being passed. This causes the doCommand message you have implemented in your QCCommandObject to be executed.
1 - (id) dispatchToCO: (NSString*)command withParameters: 2 (NSArray*)parameters andMap:(NSDictionary*)aMap{ 3 //create a mutable array that contains all of 4 // the existing parameters. 5 NSMutableArray *resultArray; 6 if(parameters == nil){ 7 resultArray = [[NSMutableArray alloc] 8 initWithCapacity:0]; 9 } 10 else{ 11 resultArray = [NSMutableArray 12 arrayWithArray:parameters]; 13 } 14 //set the result to be something so 15 //that if no mappings are made the 16 //execution will continue. 17 id result = @"Continue"; 18 if([aMap objectForKey:@"default"] != nil){ 19 SEL aSelector = @selector(doCommand); 20 while((result = [((QCCommandObject*) 21 [aMap objectForKey:@"default"]) 22 performSelector:aSelector 23 withObject:parameters]) != nil){ 24 if(aMap == self->businessMap){ 25 [resultArray addObject:result]; 26 } 27 } 28 } 29 //if all of the default command objects' method calls 30 //return something, execute all of the custom ones. 31 if(result != nil && [aMap objectForKey:command] != 32 nil){ 33 NSArray *theCommandObjects = 34 [aMap objectForKey:command]; 35 int numCommandObjects = [theCommandObjects count]; 36 for(int i = 0; i < numCommandObjects; i++){ 37 QCCommandObject *theCommand = 38 [theCommandObjects objectAtIndex:i]; 39 result = [theCommand doCommand:parameters]; 40 if(result == nil){ 41 resultArray = nil; 42 break; 43 } 44 if(aMap == self->businessMap){ 45 [resultArray addObject:result]; 46 } 47 } 48 } 49 if(aMap == self->businessMap){ 50 return resultArray; 51 } 52 return result; 53 }
After all the doCommand messages are sent to any QCCommandObjects you mapped to the default command, the same is done for QCCommandObjects you mapped to the command passed into the method as a parameter. These QCCommandObjects have the same reasons for existence as the control functions in the JavaScript implementation. Because QCCommandObjects contain all the behavior code for your application, an example is of one is helpful in understanding how they are created.
QCCommandObject is the parent class of LoggingVCO. As such, LoggingVCO must implement the doCommand method. The entire contents of the LoggingVCO.m file found in the DeviceCatalog example follows. Its doCommand method writes to the log file of the running application. This VCO logs debug messages generated from within the JavaScript code of your application. Figure 4.2 shows the calls required to accomplish this.
Figure 4.2 A sequence diagram shows the methods called in Objective-C to handle a request to log a JavaScript debug message.
The doCommand method of the LoggingVCO class is small. All doCommand methods for the different types of command objects should always be small. They should do one thing only and do it well. If you find that a doCommand method you are working on is getting large, you might want to consider splitting it into logical components and creating more than one command object class. The reason for this is that if these methods become long, they are probably doing more than one thing.
In the following example, the "one thing" the LoggingVCO does is log messages to the debug console in Xcode. Obviously, this small component can be reused with many commands in combination with other command objects.
The behavior of this VCO consists of a single line that executes the NSLog function. In doing so, the first object in the parameters array is appended to a static string and written out.
#import "LoggingVCO.h" @implementation LoggingVCO + (id) doCommand:(NSArray*) parameters{ NSLog(@"JavaScriptMessage: %@", [parameters objectAtIndex:1]); return nil; } @end
For this logging to occur, a mapping must be generated between the logMessage command and the LoggingVCO class. As in the JavaScript implementation, this is done by adding logMessage as a key and the name of the LoggingVCO class as a value to a map.
Mapping is done in the QCCommandMappings.m file. The code that follows comes from this file in the DeviceCatalog example and maps logMessage to the LoggingVCO class.
[aController mapCommandToVCO:@"logMessage" withFunction:@"LoggingVCO"];
The application controller is passed the mapCommandToVCO:withFunction message where the command is the first parameter and the VCO name is the second. This method and others like it used to map the other command object types are façades. Each of these façade methods calls the underlying mapCommandToCO method.
This mapCommandToCO method enables multiple command objects to be mapped to a single command by mapping the command to an NSMutableArray. This array is then used to contain the Class objects that match the class name passed in as the second parameter. The following code shows the implementation of the mapCommandToCO method.
- (void) mapCommandToCO:(NSString*)aCommand withFunction:(NSString*)aClassName toMap:(NSMutableDictionary*)aMap{ NSMutableArray *controlObjects = [[aMap objectForKey:aCommand] retain]; if(controlObjects == nil){ NSMutableArray *tmpCntrlObjs = [[NSMutableArray alloc] initWithCapacity:1]; [aMap setObject: tmpCntrlObjs forKey:aCommand]; controlObjects = tmpCntrlObjs; [tmpCntrlObjs release]; } //Get the control object's class //for the given name and add an object //of that type to the array for the command. Class aClass = NSClassFromString(aClassName); if(aClass != nil){ [controlObjects addObject:aClass]; } else{ MESSAGE( unable to find the %@ class. Make sure that it exists under this name and try again."); } }
The addition of the Class objects to an NSMutableArray enables any number of command objects of similar type, VCOs, BCOs, or others to be mapped to the same command and then executed individually in the order that the mapCommandTo messages were sent. Thus, you can have several VCOs execute sequentially.
For example, you can use a VCO that displays a UIView followed by another that changes another UIView's opacity, and then follow that up by logging a message. Sending three mapCommandToVCO messages with the same command but three different command object names would do this.
Several other examples of BCOs and VCOs exist in the DeviceCatalog example. Each one is activated as requests are made from the JavaScript portion of the application.