diff --git a/samples/Scrumptious/scrumptious.xcodeproj/project.pbxproj b/samples/Scrumptious/Scrumptious.xcodeproj/project.pbxproj similarity index 98% rename from samples/Scrumptious/scrumptious.xcodeproj/project.pbxproj rename to samples/Scrumptious/Scrumptious.xcodeproj/project.pbxproj index ec8b7d2ac2..59cadd1473 100644 --- a/samples/Scrumptious/scrumptious.xcodeproj/project.pbxproj +++ b/samples/Scrumptious/Scrumptious.xcodeproj/project.pbxproj @@ -46,7 +46,6 @@ 856DC2191577F105001FA6A6 /* Icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon@2x.png"; sourceTree = ""; }; 8592BE3915816CBF00056680 /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; 8592BE3C15816CCC00056680 /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; - 8592BE3E158173A000056680 /* Scrumptious.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Scrumptious.entitlements; sourceTree = ""; }; 85954AE1155889A200FABA9A /* SCProtocols.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCProtocols.h; sourceTree = ""; }; B9C1C35815129F0E008FA5D1 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; B9C1C35A1512A637008FA5D1 /* SCMealViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SCMealViewController.h; sourceTree = ""; }; @@ -135,7 +134,6 @@ B9CD0A79150EA4DD00560A93 /* Scrumptious */ = { isa = PBXGroup; children = ( - 8592BE3E158173A000056680 /* Scrumptious.entitlements */, B9CD0A98150EBFBD00560A93 /* images */, B9CD0A7A150EA4DD00560A93 /* Supporting Files */, B9CD0A82150EA4DD00560A93 /* SCAppDelegate.h */, diff --git a/samples/Scrumptious/scrumptious/SCAppDelegate.m b/samples/Scrumptious/scrumptious/SCAppDelegate.m index 5986aef889..803ce7bddc 100644 --- a/samples/Scrumptious/scrumptious/SCAppDelegate.m +++ b/samples/Scrumptious/scrumptious/SCAppDelegate.m @@ -14,6 +14,7 @@ * limitations under the License. */ +#import #import "SCAppDelegate.h" #import "SCViewController.h" #import "SCLoginViewController.h" @@ -71,6 +72,10 @@ - (void)sessionStateChanged:(FBSession *)session if ([[topViewController modalViewController] isKindOfClass:[SCLoginViewController class]]) { [topViewController dismissModalViewControllerAnimated:YES]; } + + // fetch and cache the friends for the friend picker as soon as possible + FBCacheDescriptor *cacheDescriptor = [FBFriendPickerViewController cacheDescriptor]; + [cacheDescriptor prefetchAndCacheForSession:session]; } break; case FBSessionStateClosed: diff --git a/samples/Scrumptious/scrumptious/scrumptious-Prefix.pch b/samples/Scrumptious/scrumptious/scrumptious-Prefix.pch index d3608b881e..65193bdf5a 100644 --- a/samples/Scrumptious/scrumptious/scrumptious-Prefix.pch +++ b/samples/Scrumptious/scrumptious/scrumptious-Prefix.pch @@ -15,7 +15,7 @@ */ // -// Prefix header for all source files of the 'scrumptious' target in the 'scrumptious' project +// Prefix header for all source files of the 'Scrumptious' target in the 'Scrumptious' project // #ifdef __OBJC__ diff --git a/src/FBCacheDescriptor.h b/src/FBCacheDescriptor.h new file mode 100644 index 0000000000..bcf83cfd7d --- /dev/null +++ b/src/FBCacheDescriptor.h @@ -0,0 +1,40 @@ +/* + * Copyright 2012 Facebook + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import "FBSession.h" + +/*! + @class + + @abstract + Base class from which CacheDescriptors derive, provides a method to fetch data for later use + + @discussion + Cache descriptors allow your application to specify the arguments that will be + later used with another object, such as the FBFriendPickerViewController. By using a cache descriptor + instance, an application can choose to fetch data ahead of the point in time where the data is needed. + */ +@interface FBCacheDescriptor : NSObject + +/*! + @method + @abstract + Fetches and caches the data described by the cache descriptor instance, for the given session. + */ +- (void)prefetchAndCacheForSession:(FBSession*)session; + +@end diff --git a/src/FBCacheDescriptor.m b/src/FBCacheDescriptor.m new file mode 100644 index 0000000000..7d9596d3c1 --- /dev/null +++ b/src/FBCacheDescriptor.m @@ -0,0 +1,26 @@ +/* + * Copyright 2012 Facebook + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FBCacheDescriptor.h" + +@implementation FBCacheDescriptor + +- (void)prefetchAndCacheForSession:(FBSession *)session { + // we are treating this method as abstract virtual here + [self doesNotRecognizeSelector:_cmd]; +} + +@end \ No newline at end of file diff --git a/src/FBFriendPickerCacheDescriptor.h b/src/FBFriendPickerCacheDescriptor.h new file mode 100644 index 0000000000..35d011745c --- /dev/null +++ b/src/FBFriendPickerCacheDescriptor.h @@ -0,0 +1,91 @@ +/* + * Copyright 2012 Facebook + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import "FBCacheDescriptor.h" + +/* + @class + + @abstract + Represents the data needed by an FBFriendPickerViewController, in order to construct + the necessary request to populate the view; instances of FBFriendPickerCacheDescriptor + are used to fetch data ahead of the point when the data is used to populate a display. + + @discussion + A common use of an FBFriendPickerCacheDescriptor instance, is to allocate an instance + at the point when a session is opened, and then call prefetchAndCacheForSession. This + causes the API to fetch and cache the data needed by the FBFriendPickerViewController. + If at some point the user goes to select friends, the FBFriendPickerViewController + will first check the cache for a copy of the friends list, and then after displaying + whatever cached data is available, then it will fetch a fresh copy of the friends list. + + @unsorted + */ +@interface FBFriendPickerCacheDescriptor : FBCacheDescriptor + +/* + @method + + @abstract + Initializes an instance with default values for populating + a FBFriendPickerViewController, at some later point. +*/ +- (id)init; + +/* + @method + + @abstract + Initializes an instance specifying the userID to use for populating + a FBFriendPickerViewController, at some later point. +*/ +- (id)initWithUserID:(NSString*)userID; + +/* + @method + + @abstract + Initializes an instance specifying the fields to use for populating + a FBFriendPickerViewController, at some later point. +*/ +- (id)initWithFieldsForRequest:(NSSet*)fieldsForRequest; + +/* + @method + + @abstract + Initializes an instance specifying the userID and fields to use for populating + a FBFriendPickerViewController, at some later point. + + @param userID fbid of the user whose friends we wish to display; nil='me' + @param fieldsForRequest set of additional fields to include in request for friends + */ +- (id)initWithUserID:(NSString*)userID fieldsForRequest:(NSSet*)fieldsForRequest; + +/* + @abstract + Fields to use when fetching data for the view + */ +@property (nonatomic, readonly, copy) NSSet *fieldsForRequest; + +/* + @abstract + Indicates the fbid of the user whose friends are being viewed + */ +@property (nonatomic, readonly, copy) NSString *userID; + +@end diff --git a/src/FBFriendPickerCacheDescriptor.m b/src/FBFriendPickerCacheDescriptor.m new file mode 100644 index 0000000000..c08bdd148d --- /dev/null +++ b/src/FBFriendPickerCacheDescriptor.m @@ -0,0 +1,135 @@ +/* + * Copyright 2012 Facebook + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FBFriendPickerCacheDescriptor.h" +#import "FBFriendPickerViewController+Internal.h" +#import "FBSession.h" +#import "FBRequest.h" +#import "FBRequestConnection.h" +#import "FBGraphObjectTableDataSource.h" +#import "FBGraphObjectPagingLoader.h" + +@interface FBFriendPickerCacheDescriptor () + +@property (nonatomic, readwrite, copy) NSSet *fieldsForRequest; +@property (nonatomic, readwrite, copy) NSString *userID; +@property (nonatomic, readwrite, retain) FBGraphObjectPagingLoader *loader; + +// these properties are only used by unit tests, and should not be removed or made public +@property (nonatomic, readwrite, assign) BOOL hasCompletedFetch; +@property (nonatomic, readwrite, assign) BOOL usePageLimitOfOne; +- (void)setUsePageLimitOfOne; + +@end + +@implementation FBFriendPickerCacheDescriptor + +@synthesize fieldsForRequest = _fieldsForRequest, + userID = _userID, + loader = _loader, + hasCompletedFetch = _hasCompletedFetch, + usePageLimitOfOne = _usePageLimitOfOne; + +- (id)init { + return [self initWithUserID:nil + fieldsForRequest:nil]; +} + +- (id)initWithUserID:(NSString*)userID { + return [self initWithUserID:userID + fieldsForRequest:nil]; +} + +- (id)initWithFieldsForRequest:(NSSet*)fieldsForRequest { + return [self initWithUserID:nil + fieldsForRequest:fieldsForRequest]; +} + +- (id)initWithUserID:(NSString*)userID fieldsForRequest:(NSSet*)fieldsForRequest { + self = [super init]; + if (self) { + self.fieldsForRequest = fieldsForRequest ? fieldsForRequest : [NSSet set]; + self.userID = userID; + self.hasCompletedFetch = NO; + self.usePageLimitOfOne = NO; + } + return self; +} + +- (void)dealloc { + self.fieldsForRequest = nil; + self.userID = nil; + self.loader = nil; + [super dealloc]; +} + +- (void)prefetchAndCacheForSession:(FBSession*)session { + // datasource has some field ownership, so we need one here + FBGraphObjectTableDataSource *datasource = [[FBGraphObjectTableDataSource alloc] + init]; + datasource.groupByField = @"name"; + + // me or one of my friends that also uses the app + NSString *user = self.userID; + if (!user) { + user = @"me"; + } + + // create the request object that we will start with + FBRequest *request = [FBFriendPickerViewController requestWithUserID:user + fields:self.fieldsForRequest + dataSource:datasource + session:session]; + + // this property supports unit testing + if(self.usePageLimitOfOne) { + [request.parameters setObject:@"1" + forKey:@"limit"]; + } + + self.loader.delegate = nil; + self.loader = [[FBGraphObjectPagingLoader alloc] initWithDataSource:datasource]; + self.loader.session = session; + [self.loader release]; + + self.loader.delegate = self; + self.loader.pagingMode = FBGraphObjectPagingModeImmediateViewless; + + // make sure we are around to handle the delegate call + [self retain]; + + // seed the cache + [self.loader startLoadingWithRequest:request + cacheIdentity:FBFriendPickerCacheIdentity + skipRoundtripIfCached:NO]; + + [datasource release]; +} + +- (void)setUsePageLimitOfOne { + self.usePageLimitOfOne = YES; +} + +- (void)pagingLoaderDidFinishLoading:(FBGraphObjectPagingLoader *)pagingLoader { + self.loader.delegate = nil; + self.loader = nil; + self.hasCompletedFetch = YES; + + // this feels like suicide! + [self release]; +} + +@end diff --git a/src/FBFriendPickerViewController+Internal.h b/src/FBFriendPickerViewController+Internal.h new file mode 100644 index 0000000000..2bedf6efe9 --- /dev/null +++ b/src/FBFriendPickerViewController+Internal.h @@ -0,0 +1,33 @@ +/* + * Copyright 2012 Facebook + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import "FBFriendPickerViewController.h" +#import "FBRequest.h" +#import "FBGraphObjectTableDataSource.h" +#import "FBSession.h" + +// This is the cache identity used by both the view controller and cache descriptor objects +extern NSString *const FBFriendPickerCacheIdentity; + +@interface FBFriendPickerViewController (Internal) + ++ (FBRequest*)requestWithUserID:(NSString*)userID + fields:(NSSet*)fields + dataSource:(FBGraphObjectTableDataSource*)datasource + session:(FBSession*)session; + +@end \ No newline at end of file diff --git a/src/FBFriendPickerViewController.h b/src/FBFriendPickerViewController.h index 1da973faad..889b4696dc 100644 --- a/src/FBFriendPickerViewController.h +++ b/src/FBFriendPickerViewController.h @@ -17,8 +17,10 @@ #import #import "FBGraphUser.h" #import "FBSession.h" +#import "FBCacheDescriptor.h" @protocol FBFriendPickerDelegate; +@class FBFriendPickerCacheDescriptor; /*! @typedef FBFriendSortOrdering enum @@ -143,6 +145,17 @@ typedef enum { */ - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil; +/*! + @abstract + Configures the properties that impact any queries made by the view controller, using a cacheDescriptor + + @discussion + Cache descriptors are used to fetch and cache the data used by the ViewController, at some point + prior in the execution of the application. If the ViewController finds a cached copy of the data, it will + first display the cached content, and then fetch a fresh copy from the server. + */ +- (void)configureUsingCachedDescriptor:(FBCacheDescriptor*)cacheDescriptor; + /*! @abstract Starts a query against the server for a set of friends. It is legal @@ -160,6 +173,34 @@ typedef enum { */ - (void)updateView; +/*! + @method + + @abstract + Creates a cache descriptor based on default settings of FBFriendPickerViewController + + @discussion + A cacheDescriptor object may be used to fetch data ahead of use by the FBFriendPickerViewController, and + may also be used to configure a FBFriendPickerViewController object at time of use + */ ++ (FBCacheDescriptor*)cacheDescriptor; + +/*! + @method + + @param userID fbid of the user whose friends we wish to display; nil='me' + @param fieldsForRequest set of additional fields to include in request for friends + + @abstract + Creates a cache descriptor with additional fields and a userID for use with FBFriendPickerViewController + + @discussion + A cacheDescriptor object may be used to fetch data ahead of use by the FBFriendPickerViewController, and + may also be used to configure a FBFriendPickerViewController object at time of use + + */ ++ (FBCacheDescriptor*)cacheDescriptorWithUserID:(NSString*)userID fieldsForRequest:(NSSet*)fieldsForRequest; + @end /*! diff --git a/src/FBFriendPickerViewController.m b/src/FBFriendPickerViewController.m index beee79b8d2..91d7bfc9a8 100644 --- a/src/FBFriendPickerViewController.m +++ b/src/FBFriendPickerViewController.m @@ -16,6 +16,8 @@ #import "FBError.h" #import "FBFriendPickerViewController.h" +#import "FBFriendPickerViewController+Internal.h" +#import "FBFriendPickerCacheDescriptor.h" #import "FBGraphObjectPagingLoader.h" #import "FBGraphObjectTableDataSource.h" #import "FBGraphObjectTableSelection.h" @@ -25,8 +27,10 @@ #import "FBRequestConnection.h" #import "FBUtility.h" -static NSString *defaultImageName = -@"FBiOSSDKResources.bundle/FBFriendPickerView/images/default.png"; +NSString *const FBFriendPickerCacheIdentity = @"FBFriendPicker"; +static NSString *defaultImageName = @"FBiOSSDKResources.bundle/FBFriendPickerView/images/default.png"; + +int const FBRefreshCacheDelaySeconds = 2; @interface FBFriendPickerViewController () delegate; @property (nonatomic) FBGraphObjectPagingMode pagingMode; +@property (nonatomic, readonly) BOOL isResultFromCache; - (id)initWithDataSource:(FBGraphObjectTableDataSource*)aDataSource; -- (void)startLoadingWithRequest:(FBRequest*)request; +- (void)startLoadingWithRequest:(FBRequest*)request + cacheIdentity:(NSString*)cacheIdentity + skipRoundtripIfCached:(BOOL)skipRoundtripIfCached; - (void)addResultsAndUpdateView:(NSDictionary*)results; - (void)cancel; @@ -49,6 +54,7 @@ typedef enum { - (void)pagingLoader:(FBGraphObjectPagingLoader*)pagingLoader willLoadURL:(NSString*)url; - (void)pagingLoader:(FBGraphObjectPagingLoader*)pagingLoader didLoadData:(NSDictionary*)results; +- (void)pagingLoaderDidFinishLoading:(FBGraphObjectPagingLoader*)pagingLoader; - (void)pagingLoader:(FBGraphObjectPagingLoader*)pagingLoader handleError:(NSError*)error; - (void)pagingLoaderWasCancelled:(FBGraphObjectPagingLoader*)pagingLoader; diff --git a/src/FBGraphObjectPagingLoader.m b/src/FBGraphObjectPagingLoader.m index b42663a0c2..fe09bffac7 100644 --- a/src/FBGraphObjectPagingLoader.m +++ b/src/FBGraphObjectPagingLoader.m @@ -17,11 +17,14 @@ #import "FBGraphObjectPagingLoader.h" #import "FBRequest.h" #import "FBError.h" +#import "FBRequestConnection+Internal.h" @interface FBGraphObjectPagingLoader () @property (nonatomic, retain) NSString *nextLink; @property (nonatomic, retain) FBRequestConnection *connection; +@property (nonatomic, copy) NSString *cacheIdentity; +@property (nonatomic, assign) BOOL skipRoundtripIfCached; - (void)followNextLink; - (void)requestCompleted:(FBRequestConnection *)connection @@ -40,6 +43,9 @@ @implementation FBGraphObjectPagingLoader @synthesize session = _session; @synthesize connection = _connection; @synthesize delegate = _delegate; +@synthesize isResultFromCache = _isResultFromCache; +@synthesize cacheIdentity = _cacheIdentity; +@synthesize skipRoundtripIfCached = _skipRoundtripIfCached; #pragma mark Lifecycle methods @@ -47,6 +53,7 @@ - (id)initWithDataSource:(FBGraphObjectTableDataSource*)aDataSource { if (self = [super init]) { self.dataSource = aDataSource; self.pagingMode = FBGraphObjectPagingModeAsNeeded; + _isResultFromCache = NO; } return self; } @@ -57,6 +64,7 @@ - (void)dealloc { [_nextLink release]; [_session release]; [_connection release]; + [_cacheIdentity release]; [super dealloc]; } @@ -100,8 +108,9 @@ - (void)addResultsAndUpdateView:(NSDictionary*)results { [self.dataSource appendGraphObjects:nil]; [self updateView]; - if ([self.delegate respondsToSelector:@selector(pagingLoader:didLoadData:)]) { - [self.delegate pagingLoader:self didLoadData:results]; + // notify of completion + if ([self.delegate respondsToSelector:@selector(pagingLoaderDidFinishLoading:)]) { + [self.delegate pagingLoaderDidFinishLoading:self]; } return; } else { @@ -155,11 +164,12 @@ - (void)addResultsAndUpdateView:(NSDictionary*)results { [self.delegate pagingLoader:self didLoadData:results]; } - // If we are supposed to keep paging, do so. But if we have lost our tableView, - // take that as a sign to stop (probably because the view was unloaded). If - // tableView is re-set, we will start again. - if (self.pagingMode == FBGraphObjectPagingModeImmediate && - self.tableView) { + // If we are supposed to keep paging, do so. But unless we are viewless, if we have lost + // our tableView, take that as a sign to stop (probably because the view was unloaded). + // If tableView is re-set, we will start again. + if ((self.pagingMode == FBGraphObjectPagingModeImmediate && + self.tableView) || + self.pagingMode == FBGraphObjectPagingModeImmediateViewless) { [self followNextLink]; } } @@ -180,9 +190,10 @@ - (void)followNextLink { FBRequestConnection *connection = [[FBRequestConnection alloc] init]; [connection addRequest:request completionHandler: ^(FBRequestConnection *connection, id result, NSError *error) { - self.connection = nil; - [self requestCompleted:connection result:result error:error]; - }]; + _isResultFromCache = _isResultFromCache || connection.isResultFromCache; + self.connection = nil; + [self requestCompleted:connection result:result error:error]; + }]; // Override the URL using the one passed back in 'next'. NSURL *url = [NSURL URLWithString:self.nextLink]; @@ -192,22 +203,37 @@ - (void)followNextLink { self.nextLink = nil; self.connection = connection; - [self.connection start]; + [self.connection startWithCacheIdentity:self.cacheIdentity + skipRoundtripIfCached:self.skipRoundtripIfCached]; [request release]; [connection release]; } } -- (void)startLoadingWithRequest:(FBRequest*)request { +- (void)startLoadingWithRequest:(FBRequest*)request + cacheIdentity:(NSString*)cacheIdentity + skipRoundtripIfCached:(BOOL)skipRoundtripIfCached { [self.dataSource clearGraphObjects]; [self.connection cancel]; + _isResultFromCache = NO; + + self.cacheIdentity = cacheIdentity; + self.skipRoundtripIfCached = skipRoundtripIfCached; + + FBRequestConnection *connection = [[FBRequestConnection alloc] init]; + [connection addRequest:request + completionHandler:^(FBRequestConnection *connection, id result, NSError *error) { + _isResultFromCache = _isResultFromCache || connection.isResultFromCache; + [self requestCompleted:connection result:result error:error]; + }]; + + self.connection = connection; + [self.connection startWithCacheIdentity:self.cacheIdentity + skipRoundtripIfCached:self.skipRoundtripIfCached]; - self.connection = [request startWithCompletionHandler: - ^(FBRequestConnection *connection, id result, NSError *error) { - [self requestCompleted:connection result:result error:error]; - }]; + [connection release]; NSString *urlString = [[[self.connection urlRequest] URL] absoluteString]; if ([self.delegate respondsToSelector:@selector(pagingLoader:willLoadURL:)]) { diff --git a/src/FBGraphObjectTableCell.m b/src/FBGraphObjectTableCell.m index c81d98de2f..e6e15cfb3f 100644 --- a/src/FBGraphObjectTableCell.m +++ b/src/FBGraphObjectTableCell.m @@ -33,6 +33,8 @@ @interface FBGraphObjectTableCell() @property (nonatomic, retain) UILabel* titleSuffixLabel; @property (nonatomic, retain) UIActivityIndicatorView *activityIndicator; +- (void)updateFonts; + @end @implementation FBGraphObjectTableCell diff --git a/src/FBGraphObjectTableDataSource.m b/src/FBGraphObjectTableDataSource.m index 32a0d0e6cf..6991d81c14 100644 --- a/src/FBGraphObjectTableDataSource.m +++ b/src/FBGraphObjectTableDataSource.m @@ -104,19 +104,23 @@ - (NSString *)fieldsForRequestIncluding:(NSSet *)customFields, ... if (self.groupByField) { [nameSet addObject:self.groupByField]; } + + // get a stable order for our fields, because we use the resulting URL as a cache ID + NSMutableArray *sortedFields = [[nameSet allObjects] mutableCopy]; + [sortedFields sortUsingSelector:@selector(caseInsensitiveCompare:)]; + + [nameSet release]; // Build the comma-separated string NSMutableString *fields = [[[NSMutableString alloc] init] autorelease]; - for (NSString *field in nameSet) { + for (NSString *field in sortedFields) { if ([fields length]) { [fields appendString:@","]; } [fields appendString:field]; } - [nameSet release]; - return fields; } diff --git a/src/FBPlacePickerCacheDescriptor.h b/src/FBPlacePickerCacheDescriptor.h new file mode 100644 index 0000000000..cf0b1ea38c --- /dev/null +++ b/src/FBPlacePickerCacheDescriptor.h @@ -0,0 +1,22 @@ +/* + * Copyright 2012 Facebook + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import "FBCacheDescriptor.h" + +@interface FBPlacePickerCacheDescriptor : FBCacheDescriptor + +@end diff --git a/src/FBPlacePickerCacheDescriptor.m b/src/FBPlacePickerCacheDescriptor.m new file mode 100644 index 0000000000..4f3aac894d --- /dev/null +++ b/src/FBPlacePickerCacheDescriptor.m @@ -0,0 +1,21 @@ +/* + * Copyright 2012 Facebook + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FBPlacePickerCacheDescriptor.h" + +@implementation FBPlacePickerCacheDescriptor + +@end diff --git a/src/FBPlacePickerViewController.m b/src/FBPlacePickerViewController.m index 9b5ee01193..7c5a0c6129 100644 --- a/src/FBPlacePickerViewController.m +++ b/src/FBPlacePickerViewController.m @@ -253,7 +253,9 @@ - (void)loadDataPostThrottle [request.parameters setObject:fields forKey:@"fields"]; _hasSearchTextChangedSinceLastQuery = NO; - [self.loader startLoadingWithRequest:request]; + [self.loader startLoadingWithRequest:request + cacheIdentity:nil + skipRoundtripIfCached:NO]; [self updateView]; } @@ -352,8 +354,6 @@ - (void)pagingLoader:(FBGraphObjectPagingLoader*)pagingLoader willLoadURL:(NSStr } - (void)pagingLoader:(FBGraphObjectPagingLoader*)pagingLoader didLoadData:(NSDictionary*)results { - [self.spinner stopAnimating]; - // This logging currently goes here because we're effectively complete with our initial view when // the first page of results come back. In the future, when we do caching, we will need to move // this to a more appropriate place (e.g., after the cache has been brought in). @@ -366,6 +366,11 @@ - (void)pagingLoader:(FBGraphObjectPagingLoader*)pagingLoader didLoadData:(NSDic } } +- (void)pagingLoaderDidFinishLoading:(FBGraphObjectPagingLoader *)pagingLoader { + // finished loading, stop spinner + [self.spinner stopAnimating]; +} + - (void)pagingLoader:(FBGraphObjectPagingLoader*)pagingLoader handleError:(NSError*)error { if ([self.delegate respondsToSelector:@selector(placePickerViewController:handleError:)]) { [self.delegate placePickerViewController:self handleError:error]; diff --git a/src/FBRequestConnection+Internal.h b/src/FBRequestConnection+Internal.h index baea290eb0..c44442d410 100644 --- a/src/FBRequestConnection+Internal.h +++ b/src/FBRequestConnection+Internal.h @@ -18,6 +18,8 @@ @interface FBRequestConnection (Internal) +@property (nonatomic, readonly) BOOL isResultFromCache; + - (void)startWithCacheIdentity:(NSString*)cacheIdentity skipRoundtripIfCached:(BOOL)consultCache; diff --git a/src/FBRequestConnection.m b/src/FBRequestConnection.m index 753d9af736..8c2a9bc489 100644 --- a/src/FBRequestConnection.m +++ b/src/FBRequestConnection.m @@ -126,6 +126,7 @@ @interface FBRequestConnection () @property (nonatomic, retain) FBRequest *deprecatedRequest; @property (nonatomic, retain) FBLogger *logger; @property (nonatomic) unsigned long requestStartTime; +@property (nonatomic, readonly) BOOL isResultFromCache; - (NSMutableURLRequest *)requestWithBatch:(NSArray *)requests timeout:(NSTimeInterval)timeout; @@ -220,6 +221,7 @@ @implementation FBRequestConnection @synthesize deprecatedRequest = _deprecatedRequest; @synthesize logger = _logger; @synthesize requestStartTime = _requestStartTime; +@synthesize isResultFromCache = _isResultFromCache; - (NSMutableURLRequest *)urlRequest { @@ -262,6 +264,7 @@ - (id)initWithTimeout:(NSTimeInterval)timeout _timeout = timeout; _state = kStateCreated; _logger = [[FBLogger alloc] initWithLoggingBehavior:FBLogBehaviorFBRequests]; + _isResultFromCache = NO; } return self; } @@ -421,6 +424,8 @@ - (void)startWithCacheIdentity:(NSString*)cacheIdentity self.connection = connection; [connection release]; } else { + _isResultFromCache = YES; + // complete on result from cache [self completeWithResponse:nil data:cachedData diff --git a/src/FacebookSDK.h b/src/FacebookSDK.h index 542c80dbe3..2c6a4982df 100644 --- a/src/FacebookSDK.h +++ b/src/FacebookSDK.h @@ -19,10 +19,11 @@ #import "FBRequest.h" #import "FBError.h" -// controls +// ux #import "FBProfilePictureView.h" #import "FBPlacePickerViewController.h" #import "FBFriendPickerViewController.h" +#import "FBCacheDescriptor.h" // graph #import "FBGraphUser.h" diff --git a/src/facebook-ios-sdk.xcodeproj/project.pbxproj b/src/facebook-ios-sdk.xcodeproj/project.pbxproj index bf8b603838..5407934376 100644 --- a/src/facebook-ios-sdk.xcodeproj/project.pbxproj +++ b/src/facebook-ios-sdk.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@ 8409694C1541F41100479AD9 /* FBOpenGraphAction.h in Headers */ = {isa = PBXBuildFile; fileRef = 8409694B1541F41100479AD9 /* FBOpenGraphAction.h */; settings = {ATTRIBUTES = (Public, ); }; }; 840FDCBD152B5D9200F9C927 /* FBFrictionlessRequestSettings.h in Headers */ = {isa = PBXBuildFile; fileRef = 840FDCBB152B5D9200F9C927 /* FBFrictionlessRequestSettings.h */; }; 840FDCBE152B5D9200F9C927 /* FBFrictionlessRequestSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 840FDCBC152B5D9200F9C927 /* FBFrictionlessRequestSettings.m */; }; + 841062451582501900FC561C /* FBFriendPickerCacheDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 84D0A6531581A15E00A2FA5E /* FBFriendPickerCacheDescriptor.m */; }; + 841062471582502B00FC561C /* FBPlacePickerCacheDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 84D0A6551581A1A600A2FA5E /* FBPlacePickerCacheDescriptor.m */; }; 84137158152B94B000B2C0E1 /* FBSessionManualTokenCachingStrategy.h in Headers */ = {isa = PBXBuildFile; fileRef = 84137156152B94B000B2C0E1 /* FBSessionManualTokenCachingStrategy.h */; }; 84137159152B94B000B2C0E1 /* FBSessionManualTokenCachingStrategy.m in Sources */ = {isa = PBXBuildFile; fileRef = 84137157152B94B000B2C0E1 /* FBSessionManualTokenCachingStrategy.m */; }; 843D2DD81547C33000873F7C /* FBUtility.h in Headers */ = {isa = PBXBuildFile; fileRef = 843D2DD61547C33000873F7C /* FBUtility.h */; }; @@ -35,6 +37,14 @@ 84B5F11A1552F82200A55DDC /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8446FDB3151D2674000BE007 /* UIKit.framework */; }; 84B5F11C1552FD3C00A55DDC /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B5F11B1552FD3C00A55DDC /* CoreGraphics.framework */; }; 84C7F72D15806ADC00E4B78A /* FBRequestConnection+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 84C7F72C15806ADC00E4B78A /* FBRequestConnection+Internal.h */; }; + 84C9FF3015871363000C0C97 /* FBCacheDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 84D0A64C1581A0CF00A2FA5E /* FBCacheDescriptor.m */; }; + 84D0A64D1581A0CF00A2FA5E /* FBCacheDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 84D0A64B1581A0CF00A2FA5E /* FBCacheDescriptor.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 84D0A64F1581A0CF00A2FA5E /* FBCacheDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 84D0A64C1581A0CF00A2FA5E /* FBCacheDescriptor.m */; }; + 84D0A6521581A12800A2FA5E /* FBFriendPickerCacheDescriptor.h in Resources */ = {isa = PBXBuildFile; fileRef = 84D0A6511581A12800A2FA5E /* FBFriendPickerCacheDescriptor.h */; }; + 84D0A6561581A1A600A2FA5E /* FBPlacePickerCacheDescriptor.m in Resources */ = {isa = PBXBuildFile; fileRef = 84D0A6551581A1A600A2FA5E /* FBPlacePickerCacheDescriptor.m */; }; + 84D0A6581581A1C000A2FA5E /* FBPlacePickerCacheDescriptor.h in Resources */ = {isa = PBXBuildFile; fileRef = 84D0A6571581A1C000A2FA5E /* FBPlacePickerCacheDescriptor.h */; }; + 84D0A6591581A20400A2FA5E /* FBFriendPickerCacheDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 84D0A6511581A12800A2FA5E /* FBFriendPickerCacheDescriptor.h */; settings = {ATTRIBUTES = (); }; }; + 84D0A65A1581A20A00A2FA5E /* FBPlacePickerCacheDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 84D0A6571581A1C000A2FA5E /* FBPlacePickerCacheDescriptor.h */; settings = {ATTRIBUTES = (); }; }; 84D2FC13153CC34300D7F814 /* FBGraphObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 84AE5DA2152EA02500C4DE54 /* FBGraphObject.m */; }; 84E0C9F91535ED5A00778DA4 /* FBGraphUser.h in Headers */ = {isa = PBXBuildFile; fileRef = 84E0C9F81535ED5A00778DA4 /* FBGraphUser.h */; settings = {ATTRIBUTES = (Public, ); }; }; 84E0CA04153618E500778DA4 /* FacebookSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = 84E0CA03153618E500778DA4 /* FacebookSDK.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -46,6 +56,9 @@ 84E2DA2C15353430007F886C /* FBGraphLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 84E2DA2B1535342F007F886C /* FBGraphLocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 84E374BF153CC1140043B59C /* FBGraphObjectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 84E374BE153CC1140043B59C /* FBGraphObjectTests.m */; }; 84F43FFE15194E4800CEECD5 /* FBRequestConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 84F43FFD15194E4800CEECD5 /* FBRequestConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 84F9E5A315825C73001B9CF6 /* FBFriendPickerCacheDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 84D0A6531581A15E00A2FA5E /* FBFriendPickerCacheDescriptor.m */; }; + 84F9E5A415825CA4001B9CF6 /* FBGraphObjectPagingLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 85954B85155B452E00FABA9A /* FBGraphObjectPagingLoader.m */; }; + 84F9E5A515825CAE001B9CF6 /* FBFriendPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E28B75531547D85A002E30C0 /* FBFriendPickerViewController.m */; }; 84FA4261153E155E009CEEF8 /* FBRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = AEA93B0311D5293B000A4545 /* FBRequest.m */; }; 84FA4266153E15A3009CEEF8 /* FBFrictionlessRequestSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 840FDCBC152B5D9200F9C927 /* FBFrictionlessRequestSettings.m */; }; 84FA426A153E15A3009CEEF8 /* FBRequestConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = E29B4E64152631FB00D1BE21 /* FBRequestConnection.m */; }; @@ -131,7 +144,7 @@ E2B99CF51549E02A002AEA86 /* FBGraphObjectTableSelection.h in Headers */ = {isa = PBXBuildFile; fileRef = E2B99CF31549E02A002AEA86 /* FBGraphObjectTableSelection.h */; }; E2B99CF61549E02A002AEA86 /* FBGraphObjectTableSelection.m in Sources */ = {isa = PBXBuildFile; fileRef = E2B99CF41549E02A002AEA86 /* FBGraphObjectTableSelection.m */; }; E7BBCE2E15753C7400B6B8F6 /* FBContentLinkTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7BBCE2D15753C7400B6B8F6 /* FBContentLinkTests.m */; }; - E7BBCE3115753C8E00B6B8F6 /* FBContentLink.h in Headers */ = {isa = PBXBuildFile; fileRef = E7BBCE2F15753C8E00B6B8F6 /* FBContentLink.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E7BBCE3115753C8E00B6B8F6 /* FBContentLink.h in Headers */ = {isa = PBXBuildFile; fileRef = E7BBCE2F15753C8E00B6B8F6 /* FBContentLink.h */; settings = {ATTRIBUTES = (); }; }; E7BBCE3315753C8E00B6B8F6 /* FBContentLink.m in Sources */ = {isa = PBXBuildFile; fileRef = E7BBCE3015753C8E00B6B8F6 /* FBContentLink.m */; }; /* End PBXBuildFile section */ @@ -159,6 +172,7 @@ 8446FDAB151CDB0B000BE007 /* FBSessionTokenCachingStrategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSessionTokenCachingStrategy.h; sourceTree = ""; }; 8446FDAC151CDB0B000BE007 /* FBSessionTokenCachingStrategy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBSessionTokenCachingStrategy.m; sourceTree = ""; }; 8446FDB3151D2674000BE007 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 847BA63115823BF600C2D56E /* FBFriendPickerViewController+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FBFriendPickerViewController+Internal.h"; sourceTree = ""; }; 84AE5DA1152EA02500C4DE54 /* FBGraphObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphObject.h; sourceTree = ""; }; 84AE5DA2152EA02500C4DE54 /* FBGraphObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBGraphObject.m; sourceTree = ""; }; 84B5F1131552E4AF00A55DDC /* FBSessionTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FBSessionTests.h; path = tests/FBSessionTests.h; sourceTree = ""; }; @@ -166,6 +180,12 @@ 84B5F11B1552FD3C00A55DDC /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 84BEDF4C151BC24F00F89C3B /* FBSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = FBSession.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 84C7F72C15806ADC00E4B78A /* FBRequestConnection+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FBRequestConnection+Internal.h"; sourceTree = ""; }; + 84D0A64B1581A0CF00A2FA5E /* FBCacheDescriptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBCacheDescriptor.h; sourceTree = ""; }; + 84D0A64C1581A0CF00A2FA5E /* FBCacheDescriptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBCacheDescriptor.m; sourceTree = ""; }; + 84D0A6511581A12800A2FA5E /* FBFriendPickerCacheDescriptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBFriendPickerCacheDescriptor.h; sourceTree = ""; }; + 84D0A6531581A15E00A2FA5E /* FBFriendPickerCacheDescriptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBFriendPickerCacheDescriptor.m; sourceTree = ""; }; + 84D0A6551581A1A600A2FA5E /* FBPlacePickerCacheDescriptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBPlacePickerCacheDescriptor.m; sourceTree = ""; }; + 84D0A6571581A1C000A2FA5E /* FBPlacePickerCacheDescriptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBPlacePickerCacheDescriptor.h; sourceTree = ""; }; 84E0C9F81535ED5A00778DA4 /* FBGraphUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphUser.h; sourceTree = ""; }; 84E0CA03153618E500778DA4 /* FacebookSDK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FacebookSDK.h; sourceTree = ""; }; 84E0CA051536198400778DA4 /* FBConnect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBConnect.h; sourceTree = ""; }; @@ -332,6 +352,8 @@ 84E0CA07153619D400778DA4 /* Facebook.h */, 84E0CA08153619D400778DA4 /* Facebook.m */, 84E0CA03153618E500778DA4 /* FacebookSDK.h */, + 84D0A64B1581A0CF00A2FA5E /* FBCacheDescriptor.h */, + 84D0A64C1581A0CF00A2FA5E /* FBCacheDescriptor.m */, B9C6E1301525219600E46808 /* FBCacheIndex.h */, B9C6E1311525219600E46808 /* FBCacheIndex.m */, 84E0CA051536198400778DA4 /* FBConnect.h */, @@ -345,7 +367,10 @@ E23E5A0A1521161900A011A8 /* FBError.m */, 840FDCBB152B5D9200F9C927 /* FBFrictionlessRequestSettings.h */, 840FDCBC152B5D9200F9C927 /* FBFrictionlessRequestSettings.m */, + 84D0A6511581A12800A2FA5E /* FBFriendPickerCacheDescriptor.h */, + 84D0A6531581A15E00A2FA5E /* FBFriendPickerCacheDescriptor.m */, E28B75521547D85A002E30C0 /* FBFriendPickerViewController.h */, + 847BA63115823BF600C2D56E /* FBFriendPickerViewController+Internal.h */, E28B75531547D85A002E30C0 /* FBFriendPickerViewController.m */, 84E2DA2B1535342F007F886C /* FBGraphLocation.h */, 84AE5DA1152EA02500C4DE54 /* FBGraphObject.h */, @@ -365,6 +390,8 @@ AEA93B0611D5293B000A4545 /* FBLoginDialog.h */, AEA93B0711D5293B000A4545 /* FBLoginDialog.m */, 8409694B1541F41100479AD9 /* FBOpenGraphAction.h */, + 84D0A6571581A1C000A2FA5E /* FBPlacePickerCacheDescriptor.h */, + 84D0A6551581A1A600A2FA5E /* FBPlacePickerCacheDescriptor.m */, E2223AE91554573900126FD2 /* FBPlacePickerViewController.h */, E2223AEA1554573900126FD2 /* FBPlacePickerViewController.m */, B9DC7F3E151AB56100DF1158 /* FBProfilePictureView.h */, @@ -514,6 +541,9 @@ E7BBCE3115753C8E00B6B8F6 /* FBContentLink.h in Headers */, 85F29E9415785D72001F0531 /* FBTestSession+Internal.h in Headers */, 84C7F72D15806ADC00E4B78A /* FBRequestConnection+Internal.h in Headers */, + 84D0A64D1581A0CF00A2FA5E /* FBCacheDescriptor.h in Headers */, + 84D0A6591581A20400A2FA5E /* FBFriendPickerCacheDescriptor.h in Headers */, + 84D0A65A1581A20A00A2FA5E /* FBPlacePickerCacheDescriptor.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -588,6 +618,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 84D0A6521581A12800A2FA5E /* FBFriendPickerCacheDescriptor.h in Resources */, + 84D0A6561581A1A600A2FA5E /* FBPlacePickerCacheDescriptor.m in Resources */, + 84D0A6581581A1C000A2FA5E /* FBPlacePickerCacheDescriptor.h in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -666,6 +699,10 @@ 85052AF9156F5E1200F8F9A5 /* FBTestSessionTests.m in Sources */, E7BBCE2E15753C7400B6B8F6 /* FBContentLinkTests.m in Sources */, E7BBCE3315753C8E00B6B8F6 /* FBContentLink.m in Sources */, + 84D0A64F1581A0CF00A2FA5E /* FBCacheDescriptor.m in Sources */, + 84F9E5A315825C73001B9CF6 /* FBFriendPickerCacheDescriptor.m in Sources */, + 84F9E5A415825CA4001B9CF6 /* FBGraphObjectPagingLoader.m in Sources */, + 84F9E5A515825CAE001B9CF6 /* FBFriendPickerViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -705,6 +742,9 @@ 85954B87155B452E00FABA9A /* FBGraphObjectPagingLoader.m in Sources */, 8525A5BB156F2049009F6F3F /* FBTestSession.m in Sources */, 85F29E9C1578782B001F0531 /* FBContentLink.m in Sources */, + 841062451582501900FC561C /* FBFriendPickerCacheDescriptor.m in Sources */, + 841062471582502B00FC561C /* FBPlacePickerCacheDescriptor.m in Sources */, + 84C9FF3015871363000C0C97 /* FBCacheDescriptor.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/tests/FBCacheTests.h b/src/tests/FBCacheTests.h index 54563c6ffe..c26246caeb 100644 --- a/src/tests/FBCacheTests.h +++ b/src/tests/FBCacheTests.h @@ -17,8 +17,9 @@ #import #import "FBCacheIndex.h" #import "FBDataDiskCache.h" +#import "FBTests.h" -@interface FBCacheTests : SenTestCase +@interface FBCacheTests : FBTests { NSString* _dataCachePath; } diff --git a/src/tests/FBCacheTests.m b/src/tests/FBCacheTests.m index 9869508807..6860d614a4 100644 --- a/src/tests/FBCacheTests.m +++ b/src/tests/FBCacheTests.m @@ -18,6 +18,13 @@ #import "FBDataDiskCache.h" #import "FBCacheIndex.h" #import "FBTests.h" +#import "FBTestBlocker.h" +#import "FBCacheDescriptor.h" +#import "FBFriendPickerViewController+Internal.h" +#import "FBFriendPickerViewController.h" +#import "FBRequest.h" +#import "FBRequestConnection.h" +#import "FBRequestConnection+Internal.h" #if defined(FBIOSSDK_SKIP_CACHE_TESTS) @@ -369,6 +376,58 @@ - (void)testDeletingUsedData [[NSFileManager defaultManager] removeItemAtPath:tempFolder error:NULL]; } +- (void)testBasicFriendPickerCache { + + // let's get a user going with some friends + FBTestSession *session1 = self.defaultTestSession; + FBTestSession *session2 = [self getSessionWithSharedUserWithPermissions:nil + uniqueUserTag:kSecondTestUserTag]; + [self makeTestUserInSession:session1 friendsWithTestUserInSession:session2]; + + FBTestSession *session3 = [self getSessionWithSharedUserWithPermissions:nil + uniqueUserTag:kThirdTestUserTag]; + [self makeTestUserInSession:session1 friendsWithTestUserInSession:session3]; + + FBCacheDescriptor *cacheDescriptor = [FBFriendPickerViewController cacheDescriptor]; + + // set the page limit to 1 + //[cacheDescriptor performSelector:@selector(setUsePageLimitOfOne)]; + + // here we actually perform the prefetch + [cacheDescriptor prefetchAndCacheForSession:session1]; + + FBTestBlocker *blocker = [[FBTestBlocker alloc] init]; + + [blocker waitWithPeriodicHandler:^(FBTestBlocker *blocker) { + // white-box, using an internal API to determine if fetch completed + if ([cacheDescriptor performSelector:@selector(hasCompletedFetch)]) { + [blocker signal]; + } + }]; + [blocker release]; + blocker = [[FBTestBlocker alloc] init]; + + FBFriendPickerViewController *vc = [[FBFriendPickerViewController alloc] init]; + vc.session = session1; + [vc configureUsingCachedDescriptor:cacheDescriptor]; + + FBRequest *request = [vc performSelector:@selector(requestForLoadData)]; + FBRequestConnection *connection = [[FBRequestConnection alloc] init]; + [connection addRequest:request + completionHandler:^(FBRequestConnection *connection, id result, NSError *error) { + [blocker signal]; + STAssertTrue(connection.isResultFromCache, @"This result should have been cached"); + }]; + + [connection startWithCacheIdentity:FBFriendPickerCacheIdentity + skipRoundtripIfCached:YES]; + + [blocker wait]; + + [blocker release]; + + [connection release]; +} @end diff --git a/src/tests/FBRequestConnectionTests.m b/src/tests/FBRequestConnectionTests.m index a0f5c3fafc..4619ed2f2f 100644 --- a/src/tests/FBRequestConnectionTests.m +++ b/src/tests/FBRequestConnectionTests.m @@ -118,6 +118,8 @@ - (void)testCachedRequests [blocker wait]; + STAssertFalse(connection.isResultFromCache, @"Should not have cached, and should have fetched from server"); + [connection release]; [blocker release]; @@ -136,8 +138,8 @@ - (void)testCachedRequests // should have completed successfully by here STAssertTrue(completedWithoutBlocking, @"Should have called the handler, due to cache hit"); + STAssertTrue(connection.isResultFromCache, @"Should not have fetched from server"); [connection release]; - } - (void)testDelete diff --git a/src/tests/FBTestBlocker.h b/src/tests/FBTestBlocker.h index 45ba27d70d..239a207e1f 100644 --- a/src/tests/FBTestBlocker.h +++ b/src/tests/FBTestBlocker.h @@ -16,6 +16,10 @@ #import +@class FBTestBlocker; + +typedef void (^FBTestBlockerPeriodicHandler)(FBTestBlocker *blocker); + // FBTestBlocker class // // Summary: @@ -29,8 +33,11 @@ - (id)init; - (id)initWithExpectedSignalCount:(NSInteger)expectedSignalCount; +- (id)initWithExpectedSignalCount:(NSInteger)expectedSignalCount; - (void)wait; - (BOOL)waitWithTimeout:(NSUInteger)timeout; +- (void)waitWithPeriodicHandler:(FBTestBlockerPeriodicHandler)handler; +- (BOOL)waitWithTimeout:(NSUInteger)timeout periodicHandler:(FBTestBlockerPeriodicHandler)handler; - (void)signal; @end diff --git a/src/tests/FBTestBlocker.m b/src/tests/FBTestBlocker.m index 75f371b50a..3e3e979f73 100644 --- a/src/tests/FBTestBlocker.m +++ b/src/tests/FBTestBlocker.m @@ -44,8 +44,18 @@ - (void)wait { [self waitWithTimeout:0]; } -// Note that after call to wait/waitWithTimeout the blocker resets to its original signal count. +- (void)waitWithPeriodicHandler:(FBTestBlockerPeriodicHandler)handler { + [self waitWithTimeout:0 + periodicHandler:handler]; +} + - (BOOL)waitWithTimeout:(NSUInteger)timeout { + return [self waitWithTimeout:timeout + periodicHandler:nil]; +} + +- (BOOL)waitWithTimeout:(NSUInteger)timeout + periodicHandler:(FBTestBlockerPeriodicHandler)handler { NSDate *start = [NSDate date]; // loop until the previous call completes @@ -56,6 +66,9 @@ - (BOOL)waitWithTimeout:(NSUInteger)timeout { [self reset]; return NO; } + if (handler) { + handler(self); + } }; [self reset]; return YES;