Navigation Menu

Skip to content

Commit

Permalink
Fixed issue#3: TTPhotoViewController not loading past maxPhotoIndex. …
Browse files Browse the repository at this point in the history
…This was a forwarding / object-identity bug.
  • Loading branch information
klazuka committed Nov 30, 2009
1 parent 1a60dc2 commit 2c43ee6
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 16 deletions.
41 changes: 41 additions & 0 deletions 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 <TTModel> realModel;
}
@property (nonatomic,retain) id <TTModel> realModel;
@end


@interface MyThumbsViewController : TTThumbsViewController
{
id <TTModel> realModel;
}
- (id)initForPhotoSource:(SearchResultsPhotoSource *)source;
@end


@interface MyThumbsDataSource : TTThumbsDataSource
{}
@end
67 changes: 67 additions & 0 deletions 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<TTModel>)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<TTModel>)m { [super setModel:realModel]; }

- (TTPhotoViewController*)createPhotoViewController
{
MyPhotoViewController *vc = [[[MyPhotoViewController alloc] init] autorelease];
vc.realModel = realModel;
return vc;
}

- (id<TTTableViewDataSource>)createDataSource {
return [[[MyThumbsDataSource alloc] initWithPhotoSource:_photoSource delegate:self] autorelease];
}

- (void)dealloc
{
[realModel release];
[super dealloc];
}

@end


@implementation MyThumbsDataSource

- (id<TTModel>)model
{
return [(SearchResultsPhotoSource*)_photoSource underlyingModel];
}

@end
12 changes: 4 additions & 8 deletions Classes/SearchPhotosViewController.m
Expand Up @@ -4,8 +4,11 @@

#import "SearchPhotosViewController.h"
#import "SearchResultsPhotoSource.h"
#import "ForwardingAdapters.h"
#import "SearchResultsModel.h"

// --------------------------------------------------------------------------------

@implementation SearchPhotosViewController

- (id)init
Expand Down Expand Up @@ -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];
}
Expand Down
34 changes: 29 additions & 5 deletions Classes/SearchResultsPhotoSource.h
Expand Up @@ -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 <TTPhotoSource>
Expand Down
5 changes: 3 additions & 2 deletions Classes/SearchResultsPhotoSource.m
Expand Up @@ -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)
Expand Down Expand Up @@ -35,7 +35,7 @@ @implementation SearchResultsPhotoSource

@synthesize title = albumTitle;

- (id)initWithModel:(YahooSearchResultsModel *)theModel
- (id)initWithModel:(id <SearchResultsModel>)theModel
{
if ((self = [super init])) {
albumTitle = @"Photos";
Expand Down Expand Up @@ -169,3 +169,4 @@ - (void)dealloc
}

@end

2 changes: 1 addition & 1 deletion Classes/SearchTableViewController.h
Expand Up @@ -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 <UISearchBarDelegate>
Expand Down
6 changes: 6 additions & 0 deletions TTRemoteExamples.xcodeproj/project.pbxproj
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -120,6 +121,8 @@
539C36E00FD6A99D00117201 /* GTMNSString+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+URLArguments.m"; sourceTree = "<group>"; };
539C36E10FD6A99D00117201 /* GTMNSDictionary+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSDictionary+URLArguments.h"; sourceTree = "<group>"; };
539C36E20FD6A99D00117201 /* GTMNSDictionary+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+URLArguments.m"; sourceTree = "<group>"; };
53B8BD2410C430940025C559 /* ForwardingAdapters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ForwardingAdapters.h; sourceTree = "<group>"; };
53B8BD2510C430940025C559 /* ForwardingAdapters.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ForwardingAdapters.m; sourceTree = "<group>"; };
53CDB8CE1018B56800AE0B64 /* SearchResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SearchResult.h; sourceTree = "<group>"; };
53CDB8CF1018B56800AE0B64 /* SearchResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SearchResult.m; sourceTree = "<group>"; };
53CDB8D21018B83F00AE0B64 /* YahooSearchResultsModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YahooSearchResultsModel.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -272,6 +275,8 @@
539A18B4101A0C950085BC76 /* SearchPhotosViewController.m */,
539A1BDE101B1BF40085BC76 /* SearchResultsPhotoSource.h */,
539A1BDF101B1BF40085BC76 /* SearchResultsPhotoSource.m */,
53B8BD2410C430940025C559 /* ForwardingAdapters.h */,
53B8BD2510C430940025C559 /* ForwardingAdapters.m */,
);
name = "Photo Stuff";
sourceTree = "<group>";
Expand Down Expand Up @@ -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;
};
Expand Down

0 comments on commit 2c43ee6

Please sign in to comment.