It's a pretty common marketing strategy that lures people to buy something by offering a trial period without any obligation to pay for it. Sometimes even a 30-day money-back guarantee is not as powerful as a use-it-for-"free" offer. It works particularly well on some expensive items or online subscription services.
We know there are a lot of online dating sites that provide subscriptions to singles who want to find their partners. A lot of them are quite expensive—let's say $100 for three months of unlimited searching and communication with other members. It's expensive, and there is no guarantee that you will definitely find your perfect match after you pay $100 within three months. So a lot of dating sites provide free searches, flirting, or other limited features but not actual communication with other members. This approach is more acceptable than shelling out $100 upfront and hoping for the best. If you really like the service and you get a lot of flirts and messages from other members (both paying and non-paying), then you may feel more comfortable about shelling out that $100 to dive into the game.
So you may ask, "What does this have to do with the Proxy pattern?" One common usage of a proxy is to act as a stand-in, lightweight object that allows clients to access some information or features that are cheap to prepare in the first place. Until the moment when it's worth it or necessary to get the real "deal," the proxy will go ahead and prepare the real, expensive resources for the clients. So a proxy offers you a trial membership at the beginning; when you are ready to pay for the real, expensive membership, the proxy will open that gate for you to access more features available only for paid members.
A design pattern that is elaborated from the idea is called the Proxy pattern. In this chapter, we will discuss its concepts and key features. We will also design and implement a virtual image proxy that displays a placeholder image on the screen while loading a real one in the background.
There are several types of proxies:
A remote proxy provides a local representative for an object in a different address space or in the network.
A protection proxy controls access to the original object based on different access rights.
A smart-reference proxy counts the number of references to the real object for memory management. It can also be used for locking the real object so no other objects can change it.
THE PROXY PATTERN: Provides a surrogate or placeholder for another object to control access to it.[20]
In general, a proxy acts as a surrogate or placeholder that controls access to another object, objects that are remote, expensive to create, or in need of securing. We can't cover every type of proxy here, so we will focus only on the virtual proxy.
The idea is to have a proxy that basically behaves the same as the real subject. Clients can use the proxy transparently without knowing that what they are dealing with is just a proxy and not the real thing. So when clients request some features that are expensive to create, the proxy will forward the request to the real object and get the features prepared and returned to the clients. The clients have no idea what's going on behind the scenes. Both a proxy and a real subject share the same behavior that clients expect. A class diagram that illustrates this idea is shown in Figure 22-1.
When a client sends a "request" message to a Proxy
object, the Proxy
object will in turn forward the same message to a RealSubject
object that is sitting in the Proxy
object. The RealSubject
will carry out any of the real operations to fulfill the request for the client indirectly.
At runtime, you can imagine a scenario of a client who owns a reference to a subject as an abstract type. A reference to it is in fact a Proxy
object. The Proxy
object itself has a reference to a RealSubject
instance that will perform any heavy-duty jobs later if requested. That scenario at runtime is illustrated in Figure 22-2.
You'd naturally think about using the pattern when
You need a remote proxy that provides a local representative for an object in a different address space or in the network.
You need a virtual proxy to create heavy-weighted objects on demand. We will implement that kind of proxy in a code example later in this chapter.
You need a protection proxy to control access to the original object based on different access rights.
You need a smart-reference proxy for counting the number of references to the real object for memory management. It can also be used for locking the real object so no other objects can change it.
In Chapter 2, we have defined some requirements for the TouchPainter app. One of them was to have a ThumbnailViewController
allow the user to browse through all previously saved scribbles in the file system. But loading all of them is not practical, especially when the memory on the device is very limited. Even if we can load all the thumbnail images in the memory at the time when the user enters the view, the performance may suffer if there is very little memory left for the rest of the application.
In the original design discussed in Chapter 2, scribbles are presented as little thumbnails lined up on the screen row by row. Each row is an instance of a customized UITableViewCell
that contains multiple thumbnail placeholders that will present a corresponding thumbnail image of a scribble. Before any actual thumbnail image of a scribble is loaded, a thumbnail placeholder should present a placeholder image that is shared among other thumbnail placeholders on the same viewable area of the view. The framework at runtime will manage UITableViewCell
objects. Cells that are viewable (outside of the viewable area of the screen) will be destroyed, while cells that are entering the viewable area will be created or initialized and reused with other off-screen cell resources.
When a real thumbnail image is completely loaded, the original placeholder image will be replaced by the real image. The process continues until all onscreen thumbnail images are completely loaded. The image on the left in Figure 22-3 shows a screenshot of the thumbnail view when it is first being loaded with only placeholder images. The image on the right in Figure 22-3 shows the same view with partially loaded scribble thumbnail images.
Figure 22-3. A scribble thumbnail view with only placeholder images vs. the same view with partially loaded scribble thumbnail images
This type of user interface is used in some iOS applications created by Apple to load thumbnail images while the user is scrolling the view for a whole collection of them. This provides a smooth operation and response to the user while individual small thumbnails are being loaded in the background.
The key elements to make that kind of "magical" thumbnail view are a placeholder image proxy and a mechanism for loading a real image in the background, so that the whole process looks smooth. We can lay out our basic proxy design in a class diagram illustrated in Figure 22-4.
The basic structure of the class diagram for ScribbleThumbnailViewImageProxy
and a real image object represented by UIImage
captures the essence of the original Proxy pattern. They are customized to fit what we needed for the TouchPainter app. Both the ScribbleThumbnailViewImageProxy
and UIImage
implement pretty much the same draw*
interface to draw things on a UIView
object. ScribbleThumbnailView
itself is a subclass of UIView
, so subclasses can be used with our UITableViewCell
that can display them. The complete implementation of the whole user interface involves many other parts. For this example, we will go over only some essential elements that can highlight the concepts of the Proxy pattern. You can check out a copy of the example code for this chapter for the detailed implementation of each piece.
The idea of the image forward-loading mechanism in our ScribbleThumbnailViewImageProxy
is that it will load a real image if the real one is not yet loaded and then present it on the screen. But that is just a bottom-line idea. It takes some more tricky moves to make it a working solution for our TouchPainter app. We will get to that later when we discuss the implementation part of the code example.
So far, we've got the big picture of what's going to happen with our proxy design for thumbnails displayed on a view. To visualize what we should expect at runtime with a proxy structure, let's take a look at a diagram that can illustrate that in Figure 22-5.
At runtime, aClient
is accessing an instance of ScribbleThumbnailView
's subclass, in this case, ScribbleThumbnailViewImageProxy
. An instance of UIImage
is not created until it's requested by aClient
. So the real image data is always on disk until the ScribbleThumbnailViewImageProxy
object is asked to load it. If the actual loading is never asked to trigger, then only the ScribbleThumbnailViewImageProxy
object stays in memory. We can use the similar lazy-loading approach for loading any other expensive resources that need to present on a UIView
.
Listing 22-1 shows a class declaration of ScribbleThumbnailView
.
Example 22-1. A Class Declaration of ScribbleThumbnailView
in ScribbleThumbnailView.h
@interface ScribbleThumbnailView : UIView { @protected NSString *imagePath_; } @property (nonatomic, readonly) UIImage *image; @property (nonatomic, copy) NSString *imagePath; @end
The only thing that concerns us about the behavior of ScribbleThumbnailView
in general is the actual path with which we can load and return a real image. As an abstract base class, ScribbleThumbnailView
maintains the abstract image
and imagePath
properties. These properties are essential for the whole virtual proxy operations we are going to see later. There is not much going on in the definition of the ScribbleThumbnailView
implementation except that there are some property-related directives, as shown in Listing 22-2.
Everything seems straightforward so far. Now we are going to get to the trickiest part of our proxy design—implementing ScribbleThumbnailViewImageProxy
. Its class declaration is shown in Listing 22-3.
Example 22-3. A Class Declaration of ScribbleThumbnailViewImageProxy
in ScribbleThumbnailViewImageProxy.h
#import "ScribbleThumbnailView.h" @interface ScribbleThumbnailViewImageProxy : ScribbleThumbnailView { @private UIImage *realImage_; BOOL loadingThreadHasLaunched_; } @property (nonatomic, readonly) UIImage *image; @end
realImage_
is used for holding a real image reference after it is loaded. A private BOOL
member variable, loadingThreadHasLaunched_
, will be used later for forwarding a loading process for a real image. The implementation of ScribbleThumbnailViewImageProxy
is relatively long. So go get a cup of coffee before we start rolling it.
If you are ready, let's get started and take a look at its implementation in Listing 22-4.
Example 22-4. An Implementation of ScribbleThumbnailViewImageProxy
in ScribbleThumbnailViewImageProxy.m
#import "ScribbleThumbnailViewImageProxy.h" // A private category for a forward loading thread @interface ScribbleThumbnailViewImageProxy () - (void) forwardImageLoadingThread;
@end @implementation ScribbleThumbnailViewImageProxy @dynamic imagePath; // Clients can use this method directly // to forward-load a real image // if there is no need to show this object // on a view. - (UIImage *) image { if (realImage_ == nil) { realImage_ = [[UIImage alloc] initWithContentsOfFile:imagePath_]; } return realImage_; } // A forward call will be established // in a separate thread to get a real payload from // a real image. // Before a real payload is returned, // drawRect: will handle the background // loading process and draw a placeholder frame. // Once the real payload is loaded, // it will redraw itself with the real one. - (void)drawRect:(CGRect)rect { // if is no real image available // from realImageView_, // then just draw a blank frame // as a placeholder image if (realImage_ == nil) { // Drawing code CGContextRef context = UIGraphicsGetCurrentContext(); // draw a placeholder frame // with a 10-user-space-unit-long painted // segment and a 3-user-space-unit-long // unpainted segment of a dash line CGContextSetLineWidth(context, 10.0); const CGFloat dashLengths[2] = {10,3}; CGContextSetLineDash (context, 3, dashLengths, 2); CGContextSetStrokeColorWithColor(context, [[UIColor darkGrayColor] CGColor]); CGContextSetFillColorWithColor(context, [[UIColor lightGrayColor] CGColor]); CGContextAddRect(context, rect); CGContextDrawPath(context, kCGPathFillStroke); // launch a thread to load the real // payload if it hasn't done yet if (!loadingThreadHasLaunched_) {
[self performSelectorInBackground:@selector(forwardImageLoadingThread) withObject:nil]; loadingThreadHasLaunched_ = YES; } } // otherwise pass the draw*: message // along to realImage_ and let it // draw the real image else { [realImage_ drawInRect:rect]; } } - (void) dealloc { [realImage_ release]; [super dealloc]; } #pragma mark - #pragma mark A private method for an image forward loading thread - (void) forwardImageLoadingThread { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // forward loading the real // payload [self image]; // redraw itself with the newly loaded image [self performSelectorInBackground:@selector(setNeedsDisplay) withObject:nil]; [pool release]; } @end
Wow! Congratulations, you made it! Since the implementation is huge, we can't cover every bit and piece of it. But we can summarize the whole proxy operation as follows:
The image
property method will load a real image with [[UIImagealloc] initWithContentsOfFile:imagePath_]
if realImage_
is not loaded. It eventually returns realImage_
.
ScribbleThumbnailViewImageProxy
is a subclass of UIView
; its drawRect:
method provides a custom drawing algorithm on the screen. In this case, it will draw either a placeholder image or an actual image if it is loaded. If the real image is not loaded, then spawn a new thread to forward the loading process defined in the image
property method.
A thread method, forwardImageLoadingThread
, is privately defined in an anonymous category. The method will execute the image
property method so that a real image will be loaded as a UIImage
object. Then a message of setNeedsDisplay
to self
will trigger a view content refresh. Since the real image is completely loaded at this point, the drawRect:
method will draw the real UIImage
object by forwarding a drawInRect:
message to it instead of drawing the placeholder image.
There are a couple of things that are worth mentioning. The private member variable loadingThreadHasLaunched_
is used to determine if a thread for forwardImageLoadingThread
is already launched in the drawRect:
method. If so, it won't do anything except draw the same old placeholder image because the proxy requires loading the real image only once. If the proxy thumbnail view is located outside of the displayable area of the view, UITableView
will destroy it. When the thumbnail view comes back into the view, then UITableView
will reuse/construct it and the proxy forward-loading process will start all over again.
Objective-C doesn't support multiple inheritances. So if your proxy objects are not subclasses of anything specific in the Cocoa Touch framework, you may consider using NSProxy
for your placeholders or surrogate objects.
The NSProxy
is a root class in the Cocoa framework just like NSObject
. The NSProxy
implements NSObject
protocol, so an NSProxy
object is also indeed an NSObject
type. The NSProxy
class is an abstract base class so it doesn't have its own initialization method. A call to any method that an NSProxy
object doesn't know how to respond to will throw an exception.
The main purpose of NSProxy
is to define an API for objects that act as stand-ins for other objects or for objects that don't exist yet. A message that is sent to a proxy object will be forwarded to the real object or cause the proxy to load or transform itself into the real object. Subclasses of the NSProxy
can be used to implement lazy instantiation of objects that are expensive to create—for example, a large image from the file system, as in the example in the previous sections.
Even though NSProxy
is regarded as a type of NSObject
, its existence has only one purpose – being a proxy. There are two instance methods that are essential to make the whole proxy deal happen, forwardInvocation:
and methodSignatureForSelector:
. An NSProxy
subclass doesn't even need to have other extra methods except, probably, an initialization method and some other useful properties. The key is that when an object of a NSProxy
subclass doesn't respond to a method that would be available to a real object, then the Objective-C runtime will send a message of methodSignatureForSelector:
to the proxy object for a correct method signature of the message being forwarded. The runtime will in turn use the returned method signature to construct an instance of NSInvocation
and send it with a forwardInvocation:
message to the proxy object so it will forward the invocation to other object. If the proxy object of NSProxy
's subclass can respond to the message, then the forwardInvocation:
method will not be invoked at all. Objective-C doesn't support multiple inheritances, but we can achieve that by using NSProxy
's message forwarding mechanism to forward tasks that can be handled by other classes' objects.
A common example of the Proxy pattern in an iOS application is the Mail app. Any attachment received in a message will show only some basic information, such as the file name and its size, as shown in a screenshot in Figure 22-6.
When the user taps the placeholder image to start loading the content of the attachment, the icon will be replaced by a small progress view reporting the actual loading progress, as shown in Figure 22-7.
When the attachment is completely loaded, the actual image will be displayed in the message area, as shown in Figure 22-8.
The original attachment placeholder icon is a proxy for loading an actual image across the network when it's asked to do so. If the user never taps the placeholder icon, the attachment will never be loaded and only the basic information of the attachment will be shown. It saves not only network resources, but also memory and the waiting time when the Mail app is still being run.
Memory usage is always a concern for iOS application development. No matter what kind of iOS device your application is running on, lazy-loading techniques are always recommended for the sake of performance. You can apply the Proxy pattern to an iOS application to lazy-load any expensive data, such as large image files in the file system or large payload from a server through a slow network. A virtual proxy provides some lightweight information to clients that loading an expensive object is not required until it's requested to do so.
In the next part, we will see a different design pattern for abstracting a process of saving the state of an object.
[20] The original definition appeared in Design Patterns, by the "Gang of Four" (Addison-Wesley, 1994).
3.147.72.74