image

Let’s do a very simple small program: display 100,000 flowers randomly on the interface, and these flowers only have 6 styles. As shown in the figure:

image

Create a 10W imageView and display it as follows:

- (void)viewDidLoad
{
    [super viewDidLoad];

// Use normal mode
    for (int i = 0; i < 100000; i++) {
        @autoreleasepool {
            CGRect screenBounds = [[UIScreen mainScreen] bounds];
            CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
            CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
            NSInteger minSize = 10;
            NSInteger maxSize = 50;
            CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
            CGRect area = CGRectMake(x, y, size, size);

            FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
            // Create an object
            UIImageView *imageview = [self flowerViewWithType:flowerType];
            imageview.frame = area;
            [self.view addSubview:imageview];
        }
    }
}

- (UIImageView *)flowerViewWithType:(FlowerType)type
{
    UIImageView *flowerView = nil;
    UIImage *flowerImage;

    switch (type)
    {
        case kAnemone:
            flowerImage = [UIImage imageNamed:@"anemone.png"];
            break;
        case kCosmos:
            flowerImage = [UIImage imageNamed:@"cosmos.png"];
            break;
        case kGerberas:
            flowerImage = [UIImage imageNamed:@"gerberas.png"];
            break;
        case kHollyhock:
            flowerImage = [UIImage imageNamed:@"hollyhock.png"];
            break;
        case kJasmine:
            flowerImage = [UIImage imageNamed:@"jasmine.png"];
            break;
        case kZinnia:
            flowerImage = [UIImage imageNamed:@"zinnia.png"];
            break;
        default:
            break;
    }

    flowerView = [[UIImageView alloc]initWithImage:flowerImage];

    return flowerView;
}Copy the code

Feels good, right? Take a look at the memory occupied by the app, as shown in the figure below:

image

It takes up 153M of memory, which is not too scary. This is only one page. If there are two more pages, the app will not directly exhaust the memory.

We use instrument to analyze where too much memory is occupied. Screenshot below:

image

Can see that the memory consumption is mainly a method is called [self flowerViewWithType: flowerType] create UIImageView, into this approach and look at the specific memory allocation, as shown in figure:

image

We know that UIImageview creation is very memory intensive, so we’re creating 10W uiImageViews, which is pretty memory intensive.

So what’s the solution?

The analysis showed that there were only six patterns of the 10W flowers on the screen, just different positions on the screen. Can we just create 6 UIImageViews to display florets, and then reuse those UIImageViews?

The answer is yes, and this requires the design pattern we are going to talk about: the Meta pattern. So let’s look at the specifics


define

Use sharing techniques to effectively support a large number of fine-grained objects.

We need to create 10W UIImageViews to display florets. Most of these florets are duplicated, but in different locations, resulting in a waste of memory. The solution is to cache these fine grained objects and let them create once, then use directly from the cache.

However, it is important to note that not all objects can be cached, because the cache is an instance of an object, which stores properties. If those properties change constantly, the data in the cache must change as well, and the cache is meaningless.

So we need to divide an object into two parts: the invariant part and the changed part. We cache the parts that don’t change, we call them internal states, and expose the parts that change as external states for the outside world to change. In contrast to the above program, the small flower displayed on the screen, the image itself is fixed (there are only 6 styles, all the others are repeated), and we can separate it out and share it as the internal state, which we call the share element. What changes is the position of the display. We can use it as an external state for the outside world to change and pass it to the member when needed.


The UML structure is described

image

In order to facilitate the external access to enjoy yuan, generally use to enjoy yuan factory to manage the object, today we only discuss sharing yuan, not sharing practical significance, not to discuss.

To implement the above program using the share mode, the key is to separate the share from the external state. The share is the location of the six UIImagViews and the 10W flowers of the external state. Let’s look at the implementation.


Code implementation

1, create the yuan

We need to separate out the invariant parts as the share elements, which are the six UIImageViews, so we define a flowerView that inherits from the UIImageview of the system, Then rewrite UIImageview’s — (void) drawRect:(CGRect)rect method, expose the rect parameter as the external state, and pass in uiimageviwe’s frame to draw the image.

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>


@interface FlowerView : UIImageView
{

}

- (void) drawRect:(CGRect)rect;

@end

==================

#import "FlowerView.h"
#import <UIKit/UIKit.h>

@implementation FlowerView

- (void) drawRect:(CGRect)rect
{
  [self.image drawInRect:rect];
}

@endCopy the code

2. Create A Xiangyuan factory

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

typedef enum
{
  kAnemone,
  kCosmos,
  kGerberas,
  kHollyhock,
  kJasmine,
  kZinnia,
  kTotalNumberOfFlowerTypes
} FlowerType;

@interface FlowerFactory : NSObject 
{
  @private
  NSMutableDictionary *flowerPool_;
}

- (UIImageView *) flowerViewWithType:(FlowerType)type;

@end

======================

#import "FlowerFactory.h"
#import "FlowerView.h"

@implementation FlowerFactory


- (UIImageView *)flowerViewWithType:(FlowerType)type
{
  if (flowerPool_ == nil)
  {
    flowerPool_ = [[NSMutableDictionary alloc] 
                   initWithCapacity:kTotalNumberOfFlowerTypes];
  }

  UIImageView *flowerView = [flowerPool_ objectForKey:[NSNumber
                                                  numberWithInt:type]];

  if (flowerView == nil)
  {
    UIImage *flowerImage;

    switch (type) 
    {
      case kAnemone:
        flowerImage = [UIImage imageNamed:@"anemone.png"];
        break;
      case kCosmos:
        flowerImage = [UIImage imageNamed:@"cosmos.png"];
        break;
      case kGerberas:
        flowerImage = [UIImage imageNamed:@"gerberas.png"];
        break;
      case kHollyhock:
        flowerImage = [UIImage imageNamed:@"hollyhock.png"];
        break;
      case kJasmine:
        flowerImage = [UIImage imageNamed:@"jasmine.png"];
        break;
      case kZinnia:
        flowerImage = [UIImage imageNamed:@"zinnia.png"];
        break;
      default:
        break;
    } 

    flowerView = [[FlowerView alloc] 
                   initWithImage:flowerImage];
    [flowerPool_ setObject:flowerView 
                    forKey:[NSNumber numberWithInt:type]];
  }

  return flowerView;
}


@endCopy the code

3. Separate the share element from the external state

We randomly take out a enjoy element through the enjoy element factory, and then give it a random location, into the dictionary. Loop through 10W objects and store them in an array

#import "ViewController.h" #import "FlowerFactory.h" #import "FlyweightView.h" #import <objc/runtime.h> #import <malloc/malloc.h> @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; FlowerFactory *factory = [[FlowerFactory alloc] init]; NSMutableArray *flowerList = [[NSMutableArray alloc] initWithCapacity:500]; for (int i = 0; i < 10000; ++i) { @autoreleasepool { FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes; / / object reuse UIImageView * flowerView = [factory flowerViewWithType: flowerType]; CGRect screenBounds = [[UIScreen mainScreen] bounds]; CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width); CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height); NSInteger minSize = 10; NSInteger maxSize = 50; CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize; CGRect area = CGRectMake(x, y, size, size); NSValue *key = [NSValue valueWithCGRect:area]; / / the new object NSDictionary * dic = [NSDictionary dictionaryWithObject: flowerView forKey: key]; [flowerList addObject:dic]; } } FlyweightView *view = [[FlyweightView alloc]initWithFrame:self.view.bounds]; view.flowerList = flowerList; self.view = view; } @endCopy the code

4. Customize UIView to display meta objects

Take the prime object, pass in the external state: position, and start drawing the UIImageview

#import <UIKit/UIKit.h> @interface FlyweightView : UIView @property (nonatomic, retain) NSArray *flowerList; @end ================== #import "FlyweightView.h" #import "FlowerView.h" @implementation FlyweightView extern NSString *FlowerObjectKey, *FlowerLocationKey; - (void)drawRect:(CGRect)rect { for (NSDictionary *dic in self.flowerList) { NSValue *key = (NSValue *)[dic allKeys][0];  FlowerView *flowerView = (FlowerView *)[dic allValues][0]; CGRect area = [key CGRectValue]; [flowerView drawRect:area]; } } @endCopy the code

5, test,

Run and check app memory usage again

image

Look, only 44M, the original one-third are less than, we can try their own, if the number of florets doubled again, using the yuan model to increase the memory is only 20 megabytes, but if we use the method at the beginning of the article, memory is almost 2 times. Now you realize the power of the Share mode.

Let’s look at the memory allocation at this point

image

Note that the UIImageview flowerView in the figure above has a memory footprint of 457KB. Let’s go to the factory method of creating UIImageview to see how much memory is allocated

image

And no matter how many florets we have, it’s going to take that much memory to create UIImageViews, it’s not going to increase that much, because we’re only creating six UIImageViews instead of hundreds of thousands.

Comparing the two screenshots here with the two screenshots at the beginning of the text, you can see the difference.


The problem

As soon as you see this, the share mode is too memory saving, as long as you need to create multiple similar objects, can use the share mode. Let’s see, let’s create 100, 1000, 5000, 10000 florets in two different ways, and then look at the memory consumption. You’ll notice that only when the number of florets created is around 10,000 does the free mode consume less memory than the normal mode, and in the other three cases, the normal mode actually consumes less memory than the free mode.

Why is that?

Let’s take this code out again

            FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
            //1. Reuse objects
            UIImageView *flowerView = [factory flowerViewWithType:flowerType];

            CGRect screenBounds = [[UIScreen mainScreen] bounds];
            CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
            CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
            NSInteger minSize = 10;
            NSInteger maxSize = 50;
            CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
            CGRect area = CGRectMake(x, y, size, size);
            // create a new object
            NSValue *key = [NSValue valueWithCGRect:area];
            //3. Create an object
            NSDictionary *dic =   [NSDictionary dictionaryWithObject:flowerView forKey:key];
            [flowerList addObject:dic];Copy the code

In order to store the external state, we created two new objects in step 2 and step 3, both of which consumed memory.

Let’s say you create 1000 flowers. Using the free mode, you need to create 1000 NSValues and 1000 NSDictonary objects and 6 UIImageViews, as opposed to 1000 UIImageViews using normal mode. Although NSValue and NSDictonary objects take up a lot less memory than UIImageview, once there are more of them, they also take up a lot of memory.

The share mode is advantageous only when the number of flowers reaches a certain level, when creating NSValues and NSDictonary objects takes up less memory than normal UIImageview creation.

At this point, you should know that the share mode splits the original object into two parts: the share and the external state. Each element needs a corresponding external state, and the external state also needs to create objects to store. So the meta pattern is advantageous only when the native object occupies much more memory than the object storing the external state.

And enjoy the original simple creation of the use of objects, split into several classes to complete the cooperation, the operation is more complex, which is also the need to consume memory and time.

To sum up, it is only necessary to consider using the share mode if the following three conditions are met:

  • The original object takes up a lot of memory, like UIImageView
  • A very large number (in tens of thousands)
  • Each object is similar enough to isolate the element

Most of the books and articles I read on the Internet only give pseudo code, without a specific analysis of the advantages and disadvantages of the free mode and the ordinary mode in terms of memory consumption. In fact, according to those codes on the Internet, the free mode consumes more memory.

Finding a way to meet the above requirements is actually very difficult, especially mobile devices rarely need to process such a large amount of data, after all, the device capacity is limited. This pattern is more widely used in back-end scenarios.


Use time

image


Download the Demo

Enjoy the meta mode demo