Persistence Framework Details - iOS

The iOS implementation of the persistence framework is described in this section.

AbstractServiceWeb

AbstractServiceWeb.h:

  1. #import <Foundation/Foundation.h>
  2. #import "ContentBrokerDelegate.h"
  3.  
  4. #define TRUSTED_HOST @"your_server_name_here"
  5. #define BASE_URL @"https://" TRUSTED_HOST @"/additional/server/path/here/"
  6.  
  7. @interface AbstractServiceWeb : NSObject<NSURLConnectionDelegate>
  8.  
  9. @property int action;
  10.  
  11. @property NSString *userId;
  12. @property NSString *token;
  13.  
  14.  
  15. -(void)loadContentFromServer;
  16. -(void) postContent;
  17. -(NSString *) getEncodedString: (NSString *)rawString;
  18. -(NSMutableData *) getBody: (NSString *) curBoundary;
  19. -(NSString *) getContentURL;
  20. -(void) parseContents: (NSString *)rawJSON;
  21.  
  22. @end

Comments:

  • Lines 4-5: Basic information used to identify the server.
  • Line 7: Note that this class implements the NSURLConnectionDelegate protocol.
  • Lines 11-12: User ID and Token properties used to communicate Facebook credentials

AbstractServiceWeb.m

  1. #import "AbstractServiceWeb.h"
  2.  
  3. @implementation AbstractServiceWeb {
  4. NSMutableData *contentData;
  5. NSURLConnection *conn;
  6. }
  7.  
  8. @synthesize action, userId, token;
  9.  
  10. -(void) loadContentFromServer {
  11. contentData = [NSMutableData data];
  12. NSString *contentURL = [self getContentURL];
  13. NSLog(@"Content URL: %@", contentURL );
  14. NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:contentURL]];
  15. conn = [[NSURLConnection alloc] initWithRequest: request delegate:self];
  16.  
  17. }
  18.  
  19. // If this is a post instead of a get, use this method
  20. -(void) postContent {
  21. contentData = [NSMutableData data];
  22. NSString *contentURL = [self getContentURL];
  23. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
  24. [request setURL:[NSURL URLWithString:contentURL]];
  25. [request setHTTPMethod:@"POST"];
  26.  
  27. NSString *boundary = @"0xToDaYiSaFuNdAyOhSaYcAnUcEe";
  28. NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
  29. [request addValue:contentType forHTTPHeaderField:@"Content-Type"];
  30.  
  31. NSMutableData *body = [self getBody: boundary];
  32. [request setHTTPBody:body];
  33.  
  34. conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
  35. }
  36.  
  37. // A helper method for getting an encoded string
  38. -(NSString *) getEncodedString: (NSString *)rawString {
  39. NSString *encodedString = (NSString *)
  40. CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(
  41. NULL, (CFStringRef)rawString, NULL, (CFStringRef)@"!*'();:@&=+$,/?%#[]",
  42. kCFStringEncodingUTF8 ));
  43. return encodedString;
  44. }
  45.  
  46. - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
  47. [contentData appendData:data];
  48. }
  49.  
  50. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
  51. NSLog(@"Web URL Error");
  52. [self parseContents:nil];
  53.  
  54. conn = nil;
  55. }
  56.  
  57. - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  58. NSString *loadedContent = [[NSString alloc] initWithData:
  59. contentData encoding:NSUTF8StringEncoding];
  60.  
  61. // NOTE: parseContents() is now responsible for notifiying the delegate
  62. [self parseContents:loadedContent];
  63. }
  64.  
  65. // --------- ***** Override these in the subclass ******* ----------------------------
  66.  
  67. -(void) parseContents: (NSString *)rawJSON {
  68. NSLog(@"This should not appear");
  69. }
  70.  
  71. // If a subclass sends multipart form data to the server, it should override this method
  72. -(NSMutableData *) getBody: (NSString *) curBoundary {
  73. return nil;
  74. }
  75.  
  76. -(NSString *) getContentURL {
  77. NSString *contentURL = [NSString stringWithFormat:@"<call get url here %@ %@>", @"userId", @"token"];
  78. return contentURL;
  79. }
  80.  
  81. @end

Comments:

  • Lines 4 - 5: Local properties for managing URL connections.
  • Lines 10 - 17: Standard boilerplate code for loading JSON data from the server.
  • Lines 19 - 35: Standard boilerplate code for posting multipart data to the server.
  • Lines 37 - 44: A helper method for encoding strings for transmission across the web.
  • Lines 46 - 63: This code is very similar to the original ContentBroker code. Specifically, as a URL is loaded, the data is saved in a property named contentData. Once it completes, or if an error is encountered, a callback method is invoked.
  • Lines 62, 67 - 69: When the data has been loaded, the method parseContents is invoked. The base implementation of this method logs an error. This is because each concrete class that subclasses this class must specify how the data will be parsed. (In the Android implementation, parseContents() is abstract.)
  • Lines 71 - 79, 12, 22, 31: Two additional "abstract" methods. They should be implemented in the concrete subclass so that the methods loadContentFromServer and postContent are able to get concrete class dependent URLs and content.

LocationService

  1. #import <Foundation/Foundation.h>
  2. #import "FishingSpot.h"
  3. #import "ContentBrokerDelegate.h"
  4.  
  5. @protocol LocationService <NSObject>
  6.  
  7. -(BOOL) create:(FishingSpot *)x error:(NSError **)error;
  8. -(BOOL) update:(FishingSpot *)x error:(NSError **)error;
  9. -(BOOL) delete:(FishingSpot *)x error:(NSError **)error;
  10. -(BOOL) get:(long)x error:(NSError **)error;
  11. -(BOOL) getMany:(NSString *)criteria error:(NSError **)error;
  12.  
  13. -(void) setDelegate:(id<ContentBrokerDelegate>) delegate;
  14.  
  15. @end

Comments:
This protocol is very similar to the other service protocols, such as CatchService, and FishService. Each protocol focuses on a specific business object, and exposes basic CRUD operations to the application. The only other item of note is the setDelegate method: This provides a callback object that will be notified when the various operations complete.

LocationServiceWeb

LocationServiceWeb.h

  1. #import "AbstractServiceWeb.h"
  2. #import "LocationService.h"
  3.  
  4. @interface LocationServiceWeb : AbstractServiceWeb<LocationService>
  5.  
  6. @end

LocationServiceWeb.m

  1. #import "LocationServiceWeb.h"
  2. #import "EventManager.h"
  3.  
  4. @implementation LocationServiceWeb {
  5. NSString *contentURL;
  6. id<ContentBrokerDelegate> _delegate;
  7. }
  8.  
  9. -(void) setDelegate:(id<ContentBrokerDelegate>) delegate {
  10. _delegate = delegate;
  11. }
  12.  
  13. -(BOOL) create:(FishingSpot *)x error:(NSError **)error {
  14. [self setAction:CB_CREATE_JSON];
  15.  
  16. NSString *encodedName = [self getEncodedString:x.name];
  17. NSString *encodedComments = [self getEncodedString:x.comments];
  18. contentURL = [NSString stringWithFormat:
  19. @"%@locations.php?action=create&userid=%@&token=%@&id=0&name=%@&author=%@&lat=%f&lng=%f&is_public_spot=%i&comments=%@",
  20. BASE_URL, [self userId], [self token], encodedName, [self userId],
  21. [x latitude], [x longitude], (x.isPublicSpot ? 1 : 0), encodedComments];
  22.  
  23. [self loadContentFromServer];
  24. return YES;
  25. }
  26.  
  27. -(BOOL) update:(FishingSpot *)x error:(NSError **)error {
  28. [self setAction:CB_UPDATE];
  29.  
  30. NSString *encodedName = [self getEncodedString:x.name];
  31. NSString *encodedComments = [self getEncodedString:x.comments];
  32. contentURL = [NSString stringWithFormat:
  33. @"%@locations.php?action=update&userid=%@&token=%@&id=%li&name=%@&author=%@&lat=%f&lng=%f&is_public_spot=%i&comments=%@",
  34. BASE_URL, [self userId], [self token], [x ID], encodedName, [self userId],
  35. [x latitude], [x longitude],(x.isPublicSpot ? 1 : 0), encodedComments];
  36.  
  37. [self loadContentFromServer];
  38. return YES;
  39. }
  40.  
  41. -(BOOL) delete:(FishingSpot *)x error:(NSError **)error {
  42. [self setAction:CB_DELETE];
  43.  
  44. contentURL = [NSString stringWithFormat:
  45. @"%@locations.php?action=delete&userid=%@&token=%@&id=%li", BASE_URL, [self userId], [self token], [x ID]];
  46.  
  47. [self loadContentFromServer];
  48. return YES;
  49. }
  50.  
  51. -(BOOL) get:(long)x error:(NSError **)error {
  52. [self setAction:CB_READ];
  53.  
  54. contentURL = [NSString stringWithFormat:
  55. @"%@locations.php?action=get&userid=%@&token=%@&id=%li", BASE_URL, [self userId], [self token], x];
  56. [self loadContentFromServer];
  57. return YES;
  58. }
  59.  
  60. -(BOOL) getMany:(NSString *)criteria error:(NSError **)error {
  61. [self setAction:CB_LIST];
  62.  
  63. contentURL = [NSString stringWithFormat:
  64. @"%@locations.php?action=list&userid=%@&token=%@", BASE_URL, [self userId], [self token]];
  65. [self loadContentFromServer];
  66. return YES;
  67. }
  68.  
  69. -(NSString *) getContentURL {
  70. return contentURL;
  71. }
  72.  
  73. /*
  74.  * This method is invoked when the app gets a response from the server. Attempt to construct
  75.  * Location objects from the JSON string, or report an error.
  76.  */
  77. -(void) parseContents: (NSString *)rawJSON {
  78. ContentResponse *response = [[ContentResponse alloc]initWithRc:-1 andDescr:@"Exception Thrown" andAction:[self action]];
  79. NSMutableArray *locs = [[NSMutableArray alloc] init];
  80. NSError *jsonError = nil;
  81.  
  82. if (rawJSON == nil) {
  83. [_delegate contentLoaded:response];
  84. return;
  85. }
  86.  
  87. NSDictionary *topJson = (NSDictionary *)
  88. [NSJSONSerialization JSONObjectWithData:[rawJSON dataUsingEncoding:NSUTF8StringEncoding]
  89. options:kNilOptions error:&jsonError];
  90. if (jsonError != nil) {
  91. NSLog(@"JSON Error: %@", jsonError.localizedDescription);
  92. [_delegate contentLoaded:response];
  93. return;
  94. }
  95.  
  96. NSDictionary *topLevel = [topJson objectForKey:@"fishingResponse"];
  97. NSNumber *rc = [topLevel objectForKey:@"rc"];
  98. NSString *descr = [topLevel objectForKey:@"descr"];
  99. [response setRc:[rc longValue]];
  100. [response setDescr:descr];
  101.  
  102. if ([rc intValue] != 0) {
  103. NSLog(@"Error (%i): %@", [rc intValue], descr);
  104. [_delegate contentLoaded:response];
  105. return;
  106. }
  107.  
  108. // TODO: Delete is a special case. If successful, set the return code to the id that was deleted
  109. if (self.action == CB_DELETE) {
  110. [_delegate contentLoaded:response];
  111. return;
  112. }
  113.  
  114. // Construct Location objects from the array of items
  115. NSArray *allItems = [topLevel objectForKey:@"items"];
  116.  
  117. for(NSDictionary *curItem in allItems) {
  118.  
  119. NSNumber *ID = [curItem objectForKey:@"id"];
  120. NSString *name = [curItem objectForKey:@"name"];
  121. NSDictionary *owner = [curItem objectForKey:@"owner"];
  122.  
  123. NSNumber *lat = [curItem objectForKey:@"lat"];
  124. NSNumber *lng = [curItem objectForKey:@"lng"];
  125. NSNumber *isPublic = [curItem objectForKey:@"publicSpot"];
  126. BOOL ispub = ([isPublic intValue] == 1 ? YES : NO);
  127. NSString *comments = [curItem objectForKey:@"comments"];
  128.  
  129. FishingSpot *spot = [[FishingSpot alloc] initWithID:
  130. [ID longValue] andName:name atLat:[lat doubleValue] andLng:[lng doubleValue]];
  131. [spot setIsPublicSpot:ispub];
  132. [spot setComments:comments];
  133. User *u = [[User alloc] init];
  134. u.userId = [owner objectForKey:@"userId"];
  135. u.userName = [owner objectForKey:@"userName"];
  136. u.pictureUrl = [owner objectForKey:@"pictureUrl"];
  137. spot.owner = u;
  138.  
  139. [locs addObject:spot];
  140.  
  141. }
  142. [response setItems:locs];
  143. [_delegate contentLoaded:response];
  144. }
  145.  
  146. @end

Comments:

  • Lines 13 - 25: The web-based implementation for creating a new FishingSpot. This is very similar to the LocationBroker code that was previously used.
  • Lines 27 - 39: Update a FishingSpot on the server.
  • Lines 41 - 67: The implementation for deleting and retrieving FishingSpot objects. The key point is to set the proper action and URL, and then invoke loadContentFromServer. (As you will recall, this method is implemented in the parent class - AbstractServiceWeb)
  • Lines 73 - 144: Once the response from the server has been retrieved, this method is responsible for decoding the response, and then notifying the delegate. The method retrieves basic return code information, and then parses any FishingSpot objects that are available.

Now that the two classes and the protocol have been described, it's time to create the ServiceFactory class, and then update the app to use this service instead of the LocationBroker class.