What is a class cluster

Class clustering is a widely used design pattern in the Foundation framework. A class cluster groups private concrete subclasses under a public abstract parent class. Grouping classes in this way simplifies the common visible architecture of an object-oriented framework without reducing its functional richness. Class clusters are based on abstract factory design patterns.

What are the class clusters in OC? NSData, NSArray, NSDictionary, NSString, NSNumber and so on are all class clusters. In the daily development debug process, we may find that _NSCFString, __NSArrayI, in fact, this is the class cluster under the private subclass, you can see the specific class cluster under the subclass, Github for reference.

Why did Apple design it this way? Take NSArray as an example, in order to keep the array access efficient, for different cases (variable, immutable, single element and so on) must have the corresponding subclass to optimize the implementation. If all of them were implemented in visible subclasses, it would be too complicated for the programmer to be familiar with a large number of subclasses and their apis, and to call them separately. Also, if the subclass implementation changes, the interface may change as well, making the framework API change more frequently and unusable.

To solve this problem, NSArray and NSMutableArray serve as public abstract superclasses, abstracting the interface to the array functionality, but the concrete implementation is implemented through private concrete subclasses. Combined with the abstract factory design pattern, the programmer can point to private concrete subclasses by abstracting the parent class reference, and the subclasses can implement the methods abstracted by the parent class according to their own situation. In this way, the interface is very concise, and the changes of the bottom subclasses of the framework will not affect the changes of the interface, which enhances the stability of the interface.

Class cluster implementation

Class clusters are based on abstract factory design patterns, so let’s first understand what an abstract factory design pattern is. Factory pattern belongs to creation pattern, which can be divided into simple factory pattern, factory pattern and abstract factory pattern.

The simple factory pattern defines a factory class that returns different instances depending on the parameters passed in. The created instances have a common parent class or interface.

The factory pattern, which defines an interface for creating objects, lets subclasses decide which classes to instantiate. The factory method pattern delays instantiation of a class to its subclasses. The factory method pattern is a one-step refinement of a simple factory, where instead of providing a single factory class to create all objects, we provide different factories for different objects. That is, each object has a corresponding factory.

The abstract factory pattern provides an interface for creating a set of related or interdependent objects without specifying their concrete classes. In the abstract factory pattern, each concrete factory provides multiple factory methods to produce many different types of objects. The abstract Factory pattern is a further refinement of the factory pattern, where a factory class can create not just one object, but a set of objects. This is the biggest difference from the factory approach.

The above examples are simple examples that only show the factory pattern, but they do not show the actual scenario and value of use. What is the use of using factory patterns (simple factory patterns, abstract factory patterns)? I think the main functions are as follows:

  1. Delays and hides the subclass instantiation process.
  2. Decoupling, separating the creation and use of objects.
  3. Code reuse to simplify instantiated code.
  4. Easy to extend or modify.

Some scenarios where factory mode applies (not limited to the following) :

  1. The creation/instantiation preparation of the object is complex, requiring many parameters to be initialized, database queries, and so on.
  2. The class itself has many subclasses, and the creation of these classes can easily change in the business, or the invocation of the class can easily change.

As you can see, the abstract factory method hides the concrete factory class from creating the concrete product subclass implementation, exposing only the abstract factory creation interface. When combined with the reasons for using class clusters, we can see that this perfectly solves our needs — exposing only abstract classes and creating interfaces, hiding the instantiation and implementation of a series of subclasses, and simplifying the API. So how are class clusters implemented? Let’s take the class cluster implementation of NSArray as an example.

First, we separate the alloc and init methods of NSArray, which returns the following result:

 id obj1 = [NSArray alloc];        //__NSPlaceholderArray
id obj2 = [NSMutableArray alloc]; //__NSPlaceholderArray
id obj3 = [obj1 init];            //__NSArray0
id obj4 = [obj2 init];            //__NSArrayM
Copy the code

Alloc NSArray and NSMutableArray are generated after __NSPlaceholderArray object, then the object in the init method respectively generates __NSArray0 and _NSArrayM concrete subclass. __NSPlaceholderArray in the process which can be seen as abstract factory, while init is a subclass of the abstract factory instantiation.

When an empty overlay is an empty overlay, the __nssummarize array must somehow store who alloc it from, and then init it to an instantiate specific subclass based on this information. However, by looking at the memory layout of __nssummarize array, it is found that there is no information stored except the ISA address. Therefore, it is inferred that Foundation uses static instance addresses to record alloc sources. The pseudocode is as follows:

static __NSPlaceholderArray *GetPlaceholderForNSArray() { static __NSPlaceholderArray *instanceForNSArray; if (! instanceForNSArray) { instanceForNSArray = [__NPlaceholderArray alloc]; } return instanceForNSArray; } static __NSPlaceholderArray *GetPlaceholderForNSMutableArray() { static __NSPlaceholderArray *instanceForNSMutableArray; if (! instanceForNSMutableArray) { instanceForNSMutableArray = [__NPlaceholderArray init]; } return instanceForNSMutableArray; } // alloc {if(self == [NSArray class]) {return getsarray;} // alloc {if(self == [NSArray class]) {return getsarray; } else { return [super alloc]; }} / / NSMutableArray + alloc {(id) if (self = = [NSMutableArray class]) {return GetPlaceholderForNSMutableArray; } else { return [super alloc]; }} / / __NSPlaceholderArray - (id) init {the if (self = = GetPlaceholderForNSArray) {self = [[__NSArray0 alloc] init]; } else if (self == GetPlaceholderForNSMutableArray) { self = [[__NSArrayM alloc] init]; } else { self = [super init]; } return self; }Copy the code

The above is a hypothetical Foundation implementation. Let’s verify the static address with the following code:

id obj1 = [NSArray alloc]; id obj2 = [NSArray alloc]; id obj3 = [NSMutableArray alloc]; id obj4 = [NSMutableArray alloc]; // 1 and 2 have the same address, 3 and 4 have the same address, no matter how many timesCopy the code

There are many subclasses under the NSArray and NSMutableArray class clusters, including: __NSArray0, __NSArrayI, __NSArrayI_Transfer, __NSSingleObjectArrayI, __NSArrayI reversed, __NSFrozenArrayM, NSKeyValueArray, _NSC AllStackArray, __NSOrderedSetArrayProxy, NSXMLChildren, __NSArrayM, __NSCFArray. We can infer that there are various subclass instantiations through the init of __nssummarize array according to different instantiation methods. Through the implementation process of class cluster, is it possible to discover the actual usage scenarios and the significance of the abstract factory? This implementation method is very friendly to the use of API.

How do I subclass a cluster

Through the above analysis of the class cluster implementation process, we found a problem: The class cluster is implemented through the abstract factory pattern, so if we want to write a subclass inherited from NSArray, in order to realize the instantiation of the subclass, we must add the instantiation process of the subclass in the factory according to the class cluster implementation idea. But when we think about it, that’s not possible. Therefore, you should try to avoid using class clusters to create new subclasses, and if you must do so, you must be careful. Now let’s talk about how we can subclass a class cluster.

According to Concepts in Objective-C Programming, to subclass a class cluster, do the following:

  • Take a public abstract class as its parent class, such as NSNumber, NSArray, etc., not its subclass
  • Declare the necessary variables and provide custom storage
  • Overrides all initialization methods of the parent class
  • Override primitive methods in the parent class

First of all, all the superclasses of class cluster are Abstract Classes. Only the Abstract superclasses of class cluster are publicly visible. Therefore, we can only use public Abstract Classes as the superclasses. A child class inherits the interface from its parent class, but does not include instance variables (an abstract parent class does not declare instance variables). So subclasses must declare the instance variables they need and define their storage. Second, because the abstract parent class does not directly implement the instantiation process, the child class must override all the initialization methods of the parent class itself. Finally, primitive methods are the basic interfaces that make up classes, and other methods can be derived from primitive methods (also called derived methods). In the case of NSArray, its original methods include count and objectAtIndex, so its subclasses must override those original methods as well. Generally in the foundation, primitive methods are declared in the notes including primitive or NSArray, but derived methods are declared in categories such as NSArray (NSExtendedArray).

In subclassing a class cluster, if the alloc of the parent class doesn’t have a corresponding subclass calling [super alloc], which is alloc of NSObject, the init of the parent class doesn’t have a concrete implementation. So for subclasses, you don’t override alloc, but for subclasses that have their own declared variables you have to override init… . In addition to overwriting init… In addition, you can provide +className class methods and implementations as needed.

Here’s an example:

@interface MyPairArray : NSArray
{
    id _objs[2];
}

- (id)initWithFirst: (id)first second: (id)second;

@end

@implementation MyPairArray

- (id)initWithFirst: (id)first second: (id)second {
    if(self = [super init])  {
        _objs[0] = first;
        _objs[1] = second;
    }
    return self;
}
    
- (NSUInteger)count {
    return 2;
}

- (id)objectAtIndex: (NSUInteger)index {
    if(index >= 2)
        [NSException raise: NSRangeException format: @"Index (%ld) out of bounds", (long)index];
    return _objs[index];
}

@end
Copy the code

In addition to the above method of subclassing a class cluster, there are two other ways to achieve the same goal. One is to declare an NSArray variable _realArray and copy the array parameter _realArray = [array copy] in the initWithArray: (NSArray *)array method. So the original method can also be implemented by calling the original method of _realArray. Another method is to add a method to the parent class of an existing class cluster by classification. In general, the latter two methods are preferred, because Foundation has done a good job of optimizing for us, and sometimes our own instantiated cluster subclasses don’t perform very well.

Learn from the implementation of class clusters

The implementation of the class cluster separates alloc from init and implements the instantiation process through the factory class. This process is also worth our reference. In daily development, there are some common adaptation problems, such as language or interface adaptation; Some business logic problems: such as car dealer query report, divided into maintenance, insurance, violation of regulations and other queries, different query business logic is different.

For complex cases where there are many subclasses, we can instantiate concrete subclasses by abstracting the factory, marking the factory class accordingly when alloc, and selecting concrete subclasses for instantiation when init the factory class based on the parameters passed in or the factory flag. For less complicated cases, we can also select the appropriate subclass alloc when we alloc, which ensures that we’re calling the method that the subclass instantiates.

Here is an example of screen adaptation:

+ (id)alloc { if ([self class] == [SFSSearchTVC class]) { if ([UIDevice currentDevice] systemMajorVersion] < 7) { return  [SFSSearchTVC6 alloc]; } else if ([UIDevice currentDevice] systemMajorVersion] == 7) { return [SFSSearchTVC7 alloc]; } } return [super alloc]; }Copy the code

conclusion

In this article, we know the class cluster, the factory pattern and the application of abstract factory design pattern in the class cluster, which extends to the problems that we need to pay attention to when subclassing the class cluster in the actual coding process, and how to draw lessons from the implementation of class cluster and the idea of abstract factory pattern. These implementations are not rigid, but should be flexible according to our actual situation. In future coding, more attempts can be made to use abstract factory pattern and factory pattern to realize code reuse and decoupling, and write programs with good maintainability.