From 2c43ee6dfb7097c43c835ca83b9477ceb4c12552 Mon Sep 17 00:00:00 2001 From: Keith Lazuka Date: Mon, 30 Nov 2009 11:48:34 -0500 Subject: [PATCH] Fixed issue#3: TTPhotoViewController not loading past maxPhotoIndex. This was a forwarding / object-identity bug. --- Classes/ForwardingAdapters.h | 41 +++++++++++++ Classes/ForwardingAdapters.m | 67 ++++++++++++++++++++++ Classes/SearchPhotosViewController.m | 12 ++-- Classes/SearchResultsPhotoSource.h | 34 +++++++++-- Classes/SearchResultsPhotoSource.m | 5 +- Classes/SearchTableViewController.h | 2 +- TTRemoteExamples.xcodeproj/project.pbxproj | 6 ++ 7 files changed, 151 insertions(+), 16 deletions(-) create mode 100644 Classes/ForwardingAdapters.h create mode 100644 Classes/ForwardingAdapters.m diff --git a/Classes/ForwardingAdapters.h b/Classes/ForwardingAdapters.h new file mode 100644 index 0000000..718dca8 --- /dev/null +++ b/Classes/ForwardingAdapters.h @@ -0,0 +1,41 @@ +// +// ForwardingAdapters.h +// TTRemoteExamples +// +// Created by Keith Lazuka on 11/30/09. +// Copyright 2009 __MyCompanyName__. All rights reserved. +// + +#import "Three20/Three20.h" + +/* + * HACK: + * + * The TTModel system does not expect that your TTPhotoSource implementation + * is actually forwarding to another object that implements the TTModel aspect + * of the TTPhotoSource protocol. So we must ensure that the TTModelViewController's + * notion of what its model is matches the object that it will receive + * via the TTModelDelegate messages. + */ + +@class SearchResultsPhotoSource; + +@interface MyPhotoViewController : TTPhotoViewController +{ + id realModel; +} +@property (nonatomic,retain) id realModel; +@end + + +@interface MyThumbsViewController : TTThumbsViewController +{ + id realModel; +} +- (id)initForPhotoSource:(SearchResultsPhotoSource *)source; +@end + + +@interface MyThumbsDataSource : TTThumbsDataSource +{} +@end diff --git a/Classes/ForwardingAdapters.m b/Classes/ForwardingAdapters.m new file mode 100644 index 0000000..278c46d --- /dev/null +++ b/Classes/ForwardingAdapters.m @@ -0,0 +1,67 @@ +// +// ForwardingAdapters.m +// TTRemoteExamples +// +// Created by Keith Lazuka on 11/30/09. +// Copyright 2009 __MyCompanyName__. All rights reserved. +// + +#import "ForwardingAdapters.h" +#import "SearchResultsPhotoSource.h" +#import "SearchResultsModel.h" + +@implementation MyPhotoViewController + +@synthesize realModel; + +- (void)setModel:(id)m { [super setModel:realModel]; } + +- (void)dealloc +{ + [realModel release]; + [super dealloc]; +} + +@end + +@implementation MyThumbsViewController + +- (id)initForPhotoSource:(SearchResultsPhotoSource *)source +{ + if ((self = [super init])) { + realModel = [[source underlyingModel] retain]; + self.photoSource = source; + } + return self; +} + +- (void)setModel:(id)m { [super setModel:realModel]; } + +- (TTPhotoViewController*)createPhotoViewController +{ + MyPhotoViewController *vc = [[[MyPhotoViewController alloc] init] autorelease]; + vc.realModel = realModel; + return vc; +} + +- (id)createDataSource { + return [[[MyThumbsDataSource alloc] initWithPhotoSource:_photoSource delegate:self] autorelease]; +} + +- (void)dealloc +{ + [realModel release]; + [super dealloc]; +} + +@end + + +@implementation MyThumbsDataSource + +- (id)model +{ + return [(SearchResultsPhotoSource*)_photoSource underlyingModel]; +} + +@end diff --git a/Classes/SearchPhotosViewController.m b/Classes/SearchPhotosViewController.m index 9691428..966eed4 100644 --- a/Classes/SearchPhotosViewController.m +++ b/Classes/SearchPhotosViewController.m @@ -4,8 +4,11 @@ #import "SearchPhotosViewController.h" #import "SearchResultsPhotoSource.h" +#import "ForwardingAdapters.h" #import "SearchResultsModel.h" +// -------------------------------------------------------------------------------- + @implementation SearchPhotosViewController - (id)init @@ -34,14 +37,7 @@ - (void)doSearch [photoSource load:TTURLRequestCachePolicyDefault more:NO]; // Display the updated photoSource. - TTThumbsViewController *thumbs = [[TTThumbsViewController alloc] init]; - [thumbs setPhotoSource:photoSource]; - // Ugly hack: the TTModel system does not expect that your TTPhotoSource implementation - // is actually forwarding to another object in order to conform to the TTModel aspect - // of the TTPhotoSource protocol. So I have to ensure that the TTModelViewController's - // notion of what its model is matches the object that it will receive - // via the TTModelDelegate messages. - thumbs.model = [photoSource underlyingModel]; + TTThumbsViewController *thumbs = [[MyThumbsViewController alloc] initForPhotoSource:photoSource]; [self.navigationController pushViewController:thumbs animated:YES]; [thumbs release]; } diff --git a/Classes/SearchResultsPhotoSource.h b/Classes/SearchResultsPhotoSource.h index c3a8027..478fd10 100644 --- a/Classes/SearchResultsPhotoSource.h +++ b/Classes/SearchResultsPhotoSource.h @@ -15,15 +15,39 @@ * * Responsibilities: * - Load photos from the Internet (this responsibility is delegated to - * the YahooSearchResultsModel via Objective-C forwarding). + * the SearchResultsModel implementation via Objective-C forwarding). * - Vend TTPhoto instances to the photo browsing system. * - Tell the photo browsing system how many photos in total * are available on the server. * - * The TTPhotoSource protocol entails that you must also conform to the TTModel protocol. - * Since we already have a useful TTModel in this demo app (YahooSearchResultsModel) - * we do not want to reinvent the wheel here. Hence, I will just forward the TTModel - * interface to the underlying model object. + * The TTPhotoSource protocol entails that your implementation must also conform to the + * TTModel protocol. Since we already have a useful TTModel in this demo app + * (YahooSearchResultsModel and FlickrSearchResultsModel) we want to reuse those objects. Hence, + * I will just forward the TTModel interface to the underlying model object. Unfortunately, + * this is easier said than done, as there are several places in the Three20 codebase + * where it uses object identity to determine whether to respond to an event. In the case + * where an object such as this photo source is forwarding the TTModel interface to another + * object, these object identity tests will fail unless precautions are taken. Please + * see ForwardingAdapters.h for the workaround. + * + * This forwarding hack would not be necessary if Three20 had finished the decoupling + * of remote datasource and the "datasource" that feeds the UI. TTModel is a nice abstraction, + * but I believe it is incomplete, particularly with respect to TTPhotoSource. The whole reason + * that TTModel exists is to allow one remote datasource to be used in multiple UI contexts, + * but as you can see from this demo app, it is difficult to make a clean separation. + * The problem is that TTPhotoSource practically begs to be a subclass of TTURLRequestModel, + * but if you were to rely on inheritance here, you would be once again conflating the + * remote datasource and the datasource that feeds the UI, thereby defeating the whole purpose + * of the TTModel abstraction. + * + * In summary, until TTPhotoSource's relationship with TTModel is cleaned up, the best way + * to separate the remote datasource from the way that it is presented is to do something + * like the runtime forwarding that I do here. If, however, you only need to display the + * data from the remote datasource using a single UI component (e.g. just in the photo browser, + * without any need to display it in a table view), then you can get rid of the forwarding + * and just make your SearchResultsModels also implement the TTPhotoSource protocol. Of course, + * if you go that route, you are throwing away whatever advantages the TTModel abstraction + * can provide to your application's architecture. * */ @interface SearchResultsPhotoSource : NSObject diff --git a/Classes/SearchResultsPhotoSource.m b/Classes/SearchResultsPhotoSource.m index 5c7e55f..16a3b28 100644 --- a/Classes/SearchResultsPhotoSource.m +++ b/Classes/SearchResultsPhotoSource.m @@ -6,7 +6,7 @@ // #import "SearchResultsPhotoSource.h" -#import "YahooSearchResultsModel.h" +#import "SearchResultsModel.h" #import "SearchResult.h" // NOTE: I have disabled compiler warnings on this source file (SearchResultsPhotoSource.m) @@ -35,7 +35,7 @@ @implementation SearchResultsPhotoSource @synthesize title = albumTitle; -- (id)initWithModel:(YahooSearchResultsModel *)theModel +- (id)initWithModel:(id )theModel { if ((self = [super init])) { albumTitle = @"Photos"; @@ -169,3 +169,4 @@ - (void)dealloc } @end + diff --git a/Classes/SearchTableViewController.h b/Classes/SearchTableViewController.h index 7ac5096..1c5ef1a 100644 --- a/Classes/SearchTableViewController.h +++ b/Classes/SearchTableViewController.h @@ -9,7 +9,7 @@ * ------------------------ * * This view controller manages a UITableView that will - * display Yahoo image search results. + * display Yahoo or Flickr image search results. * */ @interface SearchTableViewController : TTTableViewController diff --git a/TTRemoteExamples.xcodeproj/project.pbxproj b/TTRemoteExamples.xcodeproj/project.pbxproj index 1490b7e..b82ff39 100755 --- a/TTRemoteExamples.xcodeproj/project.pbxproj +++ b/TTRemoteExamples.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 539A1DCB101BCAE30085BC76 /* SearchResultsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 539A1DCA101BCAE30085BC76 /* SearchResultsModel.m */; }; 539C36E30FD6A99D00117201 /* GTMNSString+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 539C36E00FD6A99D00117201 /* GTMNSString+URLArguments.m */; }; 539C36E40FD6A99D00117201 /* GTMNSDictionary+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 539C36E20FD6A99D00117201 /* GTMNSDictionary+URLArguments.m */; }; + 53B8BD2610C430940025C559 /* ForwardingAdapters.m in Sources */ = {isa = PBXBuildFile; fileRef = 53B8BD2510C430940025C559 /* ForwardingAdapters.m */; }; 53CDB8D01018B56800AE0B64 /* SearchResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 53CDB8CF1018B56800AE0B64 /* SearchResult.m */; }; 53CDB8D41018B83F00AE0B64 /* YahooSearchResultsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 53CDB8D31018B83F00AE0B64 /* YahooSearchResultsModel.m */; }; 53CDB8D71018C08700AE0B64 /* SearchResultsTableDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 53CDB8D61018C08700AE0B64 /* SearchResultsTableDataSource.m */; }; @@ -120,6 +121,8 @@ 539C36E00FD6A99D00117201 /* GTMNSString+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+URLArguments.m"; sourceTree = ""; }; 539C36E10FD6A99D00117201 /* GTMNSDictionary+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSDictionary+URLArguments.h"; sourceTree = ""; }; 539C36E20FD6A99D00117201 /* GTMNSDictionary+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+URLArguments.m"; sourceTree = ""; }; + 53B8BD2410C430940025C559 /* ForwardingAdapters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ForwardingAdapters.h; sourceTree = ""; }; + 53B8BD2510C430940025C559 /* ForwardingAdapters.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ForwardingAdapters.m; sourceTree = ""; }; 53CDB8CE1018B56800AE0B64 /* SearchResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SearchResult.h; sourceTree = ""; }; 53CDB8CF1018B56800AE0B64 /* SearchResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SearchResult.m; sourceTree = ""; }; 53CDB8D21018B83F00AE0B64 /* YahooSearchResultsModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YahooSearchResultsModel.h; sourceTree = ""; }; @@ -272,6 +275,8 @@ 539A18B4101A0C950085BC76 /* SearchPhotosViewController.m */, 539A1BDE101B1BF40085BC76 /* SearchResultsPhotoSource.h */, 539A1BDF101B1BF40085BC76 /* SearchResultsPhotoSource.m */, + 53B8BD2410C430940025C559 /* ForwardingAdapters.h */, + 53B8BD2510C430940025C559 /* ForwardingAdapters.m */, ); name = "Photo Stuff"; sourceTree = ""; @@ -448,6 +453,7 @@ 539A1C4E101B50480085BC76 /* FlickrSearchResultsModel.m in Sources */, 539A1D2B101B69550085BC76 /* FlickrXMLResponse.m in Sources */, 539A1DCB101BCAE30085BC76 /* SearchResultsModel.m in Sources */, + 53B8BD2610C430940025C559 /* ForwardingAdapters.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };