Following the Effective Objective-C Dry Trilogy (Part 1) : Concepts, this is the second part of the trilogy: Specifications. For those of you who haven’t seen the first chapter of Effective Objective-C, I’ve organized the 52 knowledge points of Effective Objective-C into three broad categories:

  • Concept class: explains some conceptual knowledge.
  • Specification class: explains some of the specifications that need to be followed in order to avoid problems or facilitate future development.
  • Skills: explains the skills needed to solve a particular problem.

Then I sorted it out with a mind map:

As the second part of the trilogy, this summary draws on the prescriptive knowledge in Effective Objective-C: Prescriptive knowledge to avoid problems or facilitate development. Mastering these knowledge helps form the habit of writing OC code scientifically, making it easier to maintain and expand the code. Learning this kind of knowledge is the only way for iOS beginners to advance.

Rule 2: Minimize references to other headers in class header files

Sometimes class A needs instance variables of class B as attributes of its public API. At this point, we should not introduce headers of class B, but rather use the class keyword forward declaration and reference B’s header in A’s implementation.

// EOCPerson.h
#import <Foundation/Foundation.h>@class EOCEmployer; @interface EOCPerson : NSObject @property (nonatomic, copy) NSString *firstName; @property (nonatomic, copy) NSString *lastName; @property (nonatomic, strong) EOCEmployer *employer; // EOCEmployer as attribute @end // eocperson.m#import "EOCEmployer.h"

Copy the code

What are the advantages of this:

  • If you don’t include B’s header in A’s header file, you don’t include all of B’s content at the same time, which reduces compilation time.
  • Circular references can be avoided: if two classes import each other’s header files in their own header files, one of them will not compile properly.

Occasionally, however, we must introduce headers from other classes:

There are two main cases:

  1. If the class inherits from a class, the parent class’s header file should be imported.
  2. If the class complies with a protocol, the header file for that protocol should be imported. And it’s best to keep the protocol in a separate header file.

Rule 3: Use literal syntax more than its equivalent

1. Declaration literal syntax:

When declaring NSNumber, NSArray, and NSDictionary, you should try to use concise literal syntax.

NSNumber *intNumber = @1;
NSNumber *floatNumber = @ 2.5 f;Copy the code
NSArray *animals =[NSArray arrayWithObjects:@"cat"The @"dog"The @"mouse"The @"badger", nil];
Dictionary *dict = @{@"animal": @"tiger"The @"phone": @"iPhone 6"};
Copy the code

2. Set class subscript literal syntax:

Subscript operations for NSArray, NSDictionary, NSMutableArray, and NSMutableDictionary should also use literal syntax as much as possible.

NSString *cat = animals[0];
NSString *iphone = dict[@"phone"];

Copy the code

Advantages of using literal syntax:

  1. The code looks cleaner.
  2. If there is a nil value, an exception is thrown immediately. If an array is defined without literal syntax, if there is nil inside the array, the system sets it to the last element of the array and terminates. So when this nil is not the last element, it’s going to be an error that’s hard to troubleshoot.

Note: literal syntax creates immutable strings, arrays, and dictionary objects.

Use type constants more often than #define preprocessing commands

In OC, preprocessing commands are usually used to define constants, but it is not recommended to use them, instead using the method of type constants. First, compare the differences between the two methods:

  • Preprocessing commands: simple text replacements that do not include type information and can be modified at will.
  • Type constants: Contain type information and can be scoped and cannot be modified.

It can be seen that although the use of preprocessing can achieve the purpose of replacing text, it still has its limitations: there is no type + and it can be modified arbitrarily. In short, it gives a feeling of insecurity.

Knowing their strengths and weaknesses, let’s take a brief look at how they can be used:

Preprocessing commands:

#define W_LABEL (W_SCREEN - 2*GAP)

Here, (w_screen-2 *GAP) replaces W_LABEL, which has no type information for W_LABEL. Also note: if there is an operation symbol in the substitution, in my experience, it is best to use parentheses, otherwise it is prone to errors (experience).

Type constants:

Static const NSTimeIntervalDuration = 0.3;

Here: const sets it to constant and cannot be changed. Static means that the variable is visible only in the compilation unit where it was defined. If static is not declared, the compiler creates an external symbol for it. Let’s take a look at the public constant declaration method:

To make a constant public:

If we need to send notifications, we need to get the notification “channel” string in a different place, which obviously cannot be easily changed and can be retrieved in a different place. At this point, you need to define a visible string constant.

//header file
extern NSString *const NotificationString;

//implementation file
NSString *const  NotificationString = @"Finish Download";
Copy the code

Here NSString *const NotificationString is a pointer constant. The extern keyword tells the compiler that there will be a symbol named NotificationString in the global symbol table.

We usually declare constants in header files and define them in their implementation files. When the object file is generated from the implementation file, the compiler allocates storage space for the string in the “data segment.”

Finally, notice the naming conventions for public and private constants:

Public constants: The name of a constant is best prefixed with the name of its associated class. Non-public constants: confined to a tanslation unit (implementation file) with the letter K in the signature.

Enumerations represent states, options, and status codes

We often need to define several states for a class, and these state codes can be managed using enumerations. Here is an enumeration of status codes for network connection states:

typedef NS_ENUM(NSUInteger, EOCConnectionState) {
  EOCConnectionStateDisconnected,
  EOCConnectionStateConnecting,
  EOCConnectionStateConnected,
};
Copy the code

One thing to note: Do not implement the default branch in switch statements of enumerated types. The nice thing about this is that when we add members to an enumeration, the compiler tells the developer that the switch statement does not process all enumerations. One lesson I learned was that once again I set the “default branch” to the first item in the enumeration in the switch statement, thinking it would make the program more robust, and it crashed badly.

Rule 7: Try to access instance variables directly from within an object

Instance variables can be accessed directly or via attributes (point syntax). The authors recommend reading instance variables directly and setting instance variables via properties.

Features of direct access properties:

  • Bypass set, get semantics, fast speed;

Attributes accessed by attributes:

  • The memory management semantics of property definitions are not bypassed
  • Helps troubleshoot errors at the point of interruption
  • KVO can be triggered

So, here’s a compromise:

Set properties: Read properties by properties: Direct access

But there are two exceptions:

  1. In the initializer and dealloc methods, you need to access the instance variable directly to set the properties. If the set method is not bypassed here, other unnecessary operations may be triggered.
  2. Property of lazy initialization, through which data must be read. Because lazy initialization initializes an instance variable by overriding the GET method, if the instance variable is not read through a property, it will never be initialized.

Use prefixes to avoid namespace collisions

Apple claims that it reserves the right to use all two-letter prefixes, so our choice should be three. Also, if you develop a program that uses a third-party library, you should prefix it.

Rule 18: Use immutable objects whenever possible

In the book, the authors suggest making the properties published to the public as read-only as possible and inside the implementation file as read/write. The specific approach is:

In the header file, set the object property to readonly and in the implementation file to readwrite. This way, the data can only be read externally, not modified, making the data held by instances of the class more secure.

Also, for objects of the collection class, you should carefully consider whether you can make them mutable.

If you can only set it as a read-only property in the public part, store a variable in the private part. This way, when we get this property externally, we only get an immutable version of the internal variant, for example:

In the public API:

@interface EOCPerson : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSSet *friends // public immutable set - (id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName; - (void)addFriend:(EOCPerson*)person; - (void)removeFriend:(EOCPerson*)person; @endCopy the code

Here, we set the Friends property to an immutable set. Public interfaces are then provided to add and remove elements from the set.

In the implementation file:

@interface EOCPerson () @property (nonatomic, copy, readwrite) NSString *firstName; @property (nonatomic, copy, readwrite) NSString *lastName; @end @implementation EOCPerson { NSMutableSet *_internalFriends; } - (NSSet*)friends {return[_internalFriends copy]; // The get method always returns a variableset} - (void)addFriend (EOCPerson*)person {[_internalFriends addObject:person]; // Add a collection element externallydosomething when add element } - (void)removeFriend:(EOCPerson*)person { [_internalFriends removeObject:person]; // Remove an element externally //do something when remove element
}

- (id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName {

     if ((self = [super init])) {
        _firstName = firstName;
        _lastName = lastName;
        _internalFriends = [NSMutableSet new];
    }
 return self;
}

Copy the code

We can see that in the implementation file, a mutable set is kept to record external additions and deletions.

The most important code here is:

- (NSSet*)friends {
 return [_internalFriends copy];
}
Copy the code

This is how to get the Friends property: it copies the currently saved mutable set to an immutable set and returns it. Therefore, externally read sets will be immutable versions.

Wait, here’s a question:

Is there a contradiction between setting immutable sets on a public interface and putting code in and out of the public interface?

Answer: No contradiction!

If the Friends property is set to be mutable, then the external set can change the data in the set at will. The changes in the set are only changes to the underlying data, without any other operations. Sometimes, however, we need to change the set data while performing other work hidden in the implementation file, so arbitrarily changing this property externally is not going to meet this requirement.

Therefore, we need to provide the outside world with our own custom methods of adding and removing, and not let the outside world “add and remove” themselves.

Rule 19: Use clear and coordinated naming

In the OC method name to take full advantage of the OC method naming advantages, take a semantic clear method name! What is semantic clarity? That means it reads like a sentence.

Let’s look at an example:

First look at the bad name:

// method definition - (id)initWithSize:(float)width :(float)height; // Call EOCRectangle *aRectangle =[[EOCRectangle alloc] initWithSize:5.0f :10.0f];Copy the code

Here is the Rectangle initialization method defined. Although it is intuitively clear that this method forms the size of the rectangle by passing in two parameters, we do not know which is the width and which is the height of the rectangle. Check out the correct 🌰 :

// method definition - (id)initWithWidth:(float)width height:(float)height; // Call EOCRectangle *aRectangle =[[EOCRectangle alloc] initWithWidth:5.0f height:10.0f];Copy the code

The name of the method explains the intent of the method: the class is initialized with width and height. Moreover, which parameter is height, which parameter is width, you can see clearly. Always remember: code is for people to see.

The author summarizes the method naming rules:

The method part to the left of each colon should be the same as the parameter name to the right.

For methods that return Boolean values, we should also pay attention to the naming conventions:

  • To get a Boolean value for “whether”, add the “is” prefix:


- isEqualToString:

Copy the code

To get a Boolean value for “has”, add the “has” prefix:


- hasPrefix:

Copy the code

Rule 20: Prefix private method names

It is recommended that private methods be prefixed in the implementation file to facilitate debugging and to make it easy to distinguish between public and private methods. Because public methods are often not easy to modify arbitrarily.

Here, the author gives an example:


#import <Foundation/Foundation.h>

@interface EOCObject : NSObject

- (void)publicMethod;

@end


@implementation EOCObject

- (void)publicMethod {
 /* ... */
}

- (void)p_privateMethod {
 /* ... */
}

@end

Copy the code

Note: Do not use underscores to distinguish between private and public methods, as this duplicates Apple’s API.

Article 23: Communication between objects via delegate and data source protocols

If you send a message to a delegate, you must determine in advance whether the delegate implements the message:


NSData *data = /* data obtained from network */;

if ([_delegate respondsToSelector: @selector(networkFetcher:didReceiveData:)])
{
        [_delegate networkFetcher:self didReceiveData:data];
}

Copy the code

Moreover, it is best to add another judgment: whether the delegate object exists


NSData *data = /* data obtained from network */;

if ( (_delegate) && ([_delegate respondsToSelector: @selector(networkFetcher:didReceiveData:)]))
{
        [_delegate networkFetcher:self didReceiveData:data];
}

Copy the code

There are two types of proxy mode in iOS:

  • The common delegate pattern: information flows from the class to the principal
  • Information source pattern: Information flows from data sources to classes

It’s like a TableView telling its delegate that “I’ve been clicked”; And its data Source tells it, “You have this data.” On closer reflection, the two messages go in opposite directions.

Rule 24: Divide the implementation code of a class into manageable categories

Typically, a class has many methods, and these methods can often be grouped with some specific logic. We can use OC’s classification mechanism to logically divide these methods into several partitions.

Example:

Classes without classification:

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;

- (id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName;

/* Friendship methods */
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;


/* Work methods */
- (void)performDaysWork;
- (void)takeVacationFromWork;


/* Play methods */
- (void)goToTheCinema;
- (void)goToSportsGame;


@end

Copy the code

After classification:

#import <Foundation/Foundation.h>


@interface EOCPerson : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;



- (id)initWithFirstName:(NSString*)firstName

lastName:(NSString*)lastName;

@end



@interface EOCPerson (Friendship)

- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;

@end



@interface EOCPerson (Work)

- (void)performDaysWork;
- (void)takeVacationFromWork;

@end



@interface EOCPerson (Play)

- (void)goToTheCinema;
- (void)goToSportsGame;

@end

Copy the code

The realization code of FriendShip classification can be written as follows:


// EOCPerson+Friendship.h
#import "EOCPerson.h"


@interface EOCPerson (Friendship)

- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;

@end


// EOCPerson+Friendship.m
#import "EOCPerson+Friendship.h"


@implementation EOCPerson (Friendship)

- (void)addFriend:(EOCPerson*)person {
 /* ... */
}

- (void)removeFriend:(EOCPerson*)person {
 /* ... */
}

- (BOOL)isFriendsWith:(EOCPerson*)person {
 /* ... */
}

@end

Copy the code

Note: When creating a new classification file, be sure to introduce the classified class file.

The classification mechanism allows class code to be divided into manageable functional areas and easy to debug. Because the method name of the classification contains the name of the classification, you can immediately see which category the method belongs to.

To take advantage of this, we can create a class called Private and put all the Private methods in it. That way, we can justify calling private by where it occurs, and it’s a way to write self-documenting code.

Rule 25: Always prefix the class names of third-party classes

The classification mechanism is powerful, but if the method in the classification has the same name as the original method, the classification method overrides the original method, always based on the last overridden method.

Therefore, we should use namespaces to distinguish the names of each classification from the methods defined in them. What we do in OC is give these methods some common prefix. Such as:

@interface NSString (ABC_HTTP)

// Encode a string with URL encoding
- (NSString*)abc_urlEncodedString;

// Decode a URL encoded string
- (NSString*)abc_urlDecodedString;

@end

Copy the code

Therefore, if we want to add classes to third-party libraries or iOS frameworks, it is best to prefix the class name and method name.

Do not declare attributes in a classification

You can’t add instance variables to a class except for the class-Continuation class in the implementation file where you can declare properties.

Therefore, all data encapsulated by a class should be defined in the main interface, the only place where instance variables can be defined.

One thing to emphasize about categorization:

Classification mechanism, the goal is to extend the functionality of the class, not encapsulate the data.

Rule 27: Hide implementation details using a class-continuation classification

In general, we need to reduce what is exposed in the public interface (including properties and methods), and the limitations that this gives us can be compensated for with the features of the class-Continuation class:

  • You can add instance variables to the class-Continuation category.
  • You can set the read-only property of a public interface to read/write in the class-CONTINUATION class.
  • You can follow the protocol in the class-CONTINUATION category and keep it unknown.

In the dealloc method, only the reference is released and the listener is unlistened

Never call the dealloc method yourself; the runtime system will call it when appropriate. We sometimes need to do something in the dealloc method based on performance requirements. So what can we do in the dealloc method?

  • Release all references that the object has, but ARC automatically adds the release code so you don’t have to worry about it.
  • Other non-OC objects owned by the object must also be released (CoreFoundation objects must be released manually).
  • Release original observation behavior: unregister notification. If it is not logged out in time, a notification is sent, causing the program to crash.

Take a simple 🌰 :


- (void)dealloc {

     CFRelease(coreFoundationObject);
    [[NSNotificationCenter defaultCenter] removeObserver:self];

}

Copy the code

One particular note: no other methods should be called in the dealloc method, because if these methods are asynchronous and the current object is used in the callback, it is likely that the current object has been freed and will crash.

And you can’t call the property access methods in the dealloc methods, because there are probably other operations in those methods. The property may also be in the key-value watch state, and the observer of the property may retain or use the soon-to-be-reclaimed object if the property changes.

36. Do not use retainCount

Using retainCount in a non-ARC environment will return the reference count for the current object, but calling it in an ARC environment will return an error because the method has been deprecated.

It is deprecated because the reference count it returns only reflects the object’s reference count at one point in time, and does not “predict” how the object’s reference count will change in the future (for example, if the object is currently in the auto-release pool, it will automatically decrement the reference count in the future).

Do not use dispatch_get_CURRENT_queue

We cannot describe the “current queue” attribute in terms of a queue, because dispatch queues are hierarchically organized.

So what is the hierarchy of queues?

A fast scheduled on a queue will be executed on its upper queue, and the highest ranked queue is always the global concurrent queue.

In this case, the blocks in B and C will execute in A. But the block in D might be parallel to the block in A, because the target queues of A and D are concurrent queues.

Because of this hierarchy, it is not always accurate to check whether the current queue is concurrent or non-concurrent.

Rule 48: Use block enumerations more and use for loops less

Block enumerations are recommended when iterating through collection elements, because they are more efficient and concise than traditional for loops, and they get values that traditional for loops cannot provide:

Let’s first look at traditional traversal:

Traditional for traversal

NSArray *anArray = /* ... * /.for (int i = 0; i < anArray.count; i++) {
   id object = anArray[i];
   // Do something with 'object'} // Dictionary NSDictionary *aDictionary = /* ... * /. NSArray *keys = [aDictionary allKeys];for (int i = 0; i < keys.count; i++) {
   id key = keys[i];
   id value = aDictionary[key];
   // Do something with 'key' and 'value'} // Set NSSet *aSet = /* ... * /. NSArray *objects = [aSet allObjects];for (int i = 0; i < objects.count; i++) {
   id object = objects[i];
   // Do something with 'object'

}

Copy the code

And you can see, as we’re going through the NSDictionary and the NSet, we’re creating a new array. Although the purpose of traversal is achieved, it increases the overhead of the system.

Using quick traversal:

NSArray *anArray = /* ... * /.for (id object in anArray) {
 // Do something with 'object'} // Dictionary NSDictionary *aDictionary = /* ... * /.for (id key in aDictionary) {
 id value = aDictionary[key];
 // Do something with 'key' and 'value'} NSSet *aSet = /* ... * /.for (id object in aSet) {
 // Do something with 'object'
}

Copy the code

This quick traversal method is more concise and understandable than the traditional traversal method, but the disadvantage is that it is not easy to obtain the index of the element.

Using block-based traversal:

NSArray *anArray = /* ... * /. [anArray enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop){ // Do something with'object'
   if(shouldStop) { *stop = YES; // stop the iteration}}]; // Dictionary NSDictionary *aDictionary = /*... * /. [aDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id object, BOOL *stop){ // Do something with'key' and 'object'
     if (shouldStop) {
        *stop = YES;
    }
}];


// Set
NSSet *aSet = /* ... */;
[aSet enumerateObjectsUsingBlock:^(id object, BOOL *stop){
     // Do something with 'object'
     if (shouldStop) {
        *stop = YES;
    }
Copy the code

We can see that when using blocks for quick enumeration, we can do without creating temporary arrays. Although the syntax is not as neat as quick enumeration, we can get the ordinals of the array elements, the keys of the dictionary elements, and we can terminate the traversal at any time.

Using fast enumerations and block enumerations has another advantage: the ability to modify the block’s method signature

for (NSString *key in aDictionary) {
         NSString *object = (NSString*)aDictionary[key];
        // Do something with 'key' and 'object'
}
Copy the code
NSDictionary *aDictionary = /* ... * /. [aDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop){ // Do something with'key' and 'obj'

}];

Copy the code

If we know the element type in the collection, we can change the signature. This has the advantage of letting compile time check to see if the element implements the method we want to call, and if it doesn’t, do something else. In this way, the program can be made more secure.

Rule 50: Use NSCache instead of NSDictionary when building a cache

If we use caching properly, the response time of the application will improve. Only data that is “cumbersome to recalculate is worth caching,” such as data that needs to be fetched from the network or read from disk.

Many people are used to using NSDictionary or NSMutableDictionary when building caches, but the authors recommend using NSCache, which as a class for managing caches has many advantages over dictionaries because it is designed for managing caches.

NSCache has several advantages over NSDictionary:

  • When system resources are about to be used up, NSCache automatically deletes buffers. It also removes the “most unused” objects first.
  • NSCache does not copy keys, but preserves them. Not all keys comply with the copy protocol (dictionary keys must support the copy protocol, so there are limitations).
  • NSCache is thread-safe: Multiple threads can access NSCache at the same time without writing locking code.

About manipulating when NSCache deletes content

Developers can adjust this timing by using two metrics:

  • Total number of objects in the cache.
  • When an object is added to the cache, it is assigned a cost value.

For overhead values, this scale should be considered only if the overhead can be calculated quickly, otherwise it will increase the overhead of the system.

Let’s look at the use of caching: Caching network downloaded data

// Network fetcher class
typedef void(^EOCNetworkFetcherCompletionHandler)(NSData *data);

@interface EOCNetworkFetcher : NSObject

- (id)initWithURL:(NSURL*)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)handler;

@end

// Class that uses the network fetcher and caches results
@interface EOCClass : NSObject
@end

@implementation EOCClass {
     NSCache *_cache;
}

- (id)init {

     if ((self = [super init])) {
    _cache = [NSCache new];

     // Cache a maximum of 100 URLs
    _cache.countLimit = 100;


     /**
     * The size in bytes of data is used as the cost,
     * so this sets a cost limit of 5MB.
     */
    _cache.totalCostLimit = 5 * 1024 * 1024;
    }
 return self;
}



- (void)downloadDataForURL:(NSURL*)url { 

     NSData *cachedData = [_cache objectForKey:url];

     if(cachedData) {// Cache hit: there is Cache, read [self useData:cachedData]; }elseEOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url]; [fetcher startWithCompletionHandler:^(NSData *data){ [_cachesetObject:data forKey:url cost:data.length];    
        [self useData:data];
        }];
    }
}
@end

Copy the code

Here, we use the URL as the cache key, set the total number of objects to 100, and set the overhead value to 5MB.

NSPurgeableData

NSPurgeableData is a subclass of NSMutableData, which works well with NSCache.

Because when system resources are tight, you can free up the memory that holds NSPurgeableData.

If you need to access an NSPurgeableData object, you can call the beginContentAccess method to tell it that it shouldn’t discard the memory it occupies just yet.

When you’re done, call the endContentAccess method and tell the system to discard the memory it occupies if necessary.

These two methods are similar to the “reference count” decrement operation, that is, only if the “reference count” is zero can it be deleted in the future.


- (void)downloadDataForURL:(NSURL*)url { 

      NSPurgeableData *cachedData = [_cache objectForKey:url];

      if(cachedData) {// If there is a cache, call the beginContentAccess method [cacheData beginContentAccess]; // Use the cached data [self useData:cachedData]; // Call endContentAccess [cacheData endContentAccess]; }else{// No cache EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url]; [fetcher startWithCompletionHandler:^(NSData *data){ NSPurgeableData *purgeableData = [NSPurgeableData dataWithData:data]; [_cachesetObject:purgeableData forKey:url cost:purgeableData.length];

                          // Don't need to beginContentAccess as it begins // with access already marked // Use the retrieved data [self useData:data]; // Mark that the data may be purged now [purgeableData endContentAccess]; }]; }}Copy the code

Note:

The beginContentAccess method needs to be implemented in cases where we can get purgeableData directly. However, in the case of creating purgeableData, there is no need to execute beginContentAccess because the reference count automatically +1 after the purgeableData is created;

Simplify the initialize and Load implementation code

The load method

+(void)load;
Copy the code

When each class or class is added to the runtime system, the load method is called only once. It is recommended not to call other methods in this method, especially when using other classes. The reason is that each class loads the library at a different time, and it can be dangerous if that class calls a class that has not yet loaded the library.

The initialize method

+(void)initialize;
Copy the code

This method is similar to the Load method, except that it is called the first time a program calls the class (lazy call) and only once (never actively called with code).

It is important to note that if a subclass does not implement it and its superclass does, then the superclass’s code will be run: a situation that is often overlooked.

Take a look at 🌰 :

#import <Foundation/Foundation.h>

@interface EOCBaseClass : NSObject
@end

@implementation EOCBaseClass
+ (void)initialize {
 NSLog(@"%@ initialize", self);
}
@end

@interface EOCSubClass : EOCBaseClass
@end

@implementation EOCSubClass
@end
Copy the code

When using the EOCSubClass class, the console prints two print methods:

EOCBaseClass initialize
EOCSubClass initialize
Copy the code

Since subclass EOCSubClass does not override the Initialize method, the method of its parent class EOCBaseClass is naturally called. The solution is to detect the type of the class:

+ (void)initialize {
   if (self == [EOCBaseClass class]) {
       NSLog(@"%@ initialized", self); }}Copy the code

EOCSubClass, a subclass of EOCBaseClass, can no longer call the Initialize method. We can see that doing too much in this method can make the program difficult to maintain and may cause other bugs. Therefore, in the initialize method, it is best to just set the internal data, and do not call other methods, as other functions may be added to these methods in the future, which may cause bugs that are hard to detect.

Rule 52: Don’t forget that NSTimer keeps its target object

When NSTimer is used, NSTimer generates a reference to its consumer, and its consumer generates a reserved ring if it also references NSTimer.

#import <Foundation/Foundation.h>

@interface EOCClass : NSObject
- (void)startPolling;
- (void)stopPolling;
@end


@implementation EOCClass {
     NSTimer *_pollTimer;
}


- (id)init {
     return[super init]; } - (void)dealloc { [_pollTimer invalidate]; } - (void)stopPolling { [_pollTimer invalidate]; _pollTimer = nil; } - (void) startPolling {_pollTimer = [NSTimer scheduledTimerWithTimeInterval: 5.0 target: the self selector:@selector(p_doPoll) userInfo:nil repeats:YES]; } - (void)p_doPoll { // Poll the resource } @endCopy the code

Here, a reserved ring is formed between EOCClass and _pollTimer, which cannot be broken without actively calling the stopPolling method. A design like this that breaks the retention loop by actively calling methods is obviously bad.

Furthermore, breaking the reserved loop by recycling the class would not work, since it would isolate the class and the NSTimer, creating an “island” :

This can be an extremely dangerous situation because NSTimer doesn’t go away, and it can continue to perform tasks that consume system resources. Also, if the task involves downloading, it could be worse.

So how to solve it? Solve by “block”!

This can be solved by adding a category to NSTimer:

#import <Foundation/Foundation.h>

@interface NSTimer (EOCBlocksSupport)

+ (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                         block:(void(^)())block
                                         repeats:(BOOL)repeats;
@end



@implementation NSTimer (EOCBlocksSupport)

+ (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                         block:(void(^)())block
                                        repeats:(BOOL)repeats
{
             return [self scheduledTimerWithTimeInterval:interval
                                                  target:self
                                                selector:@selector(eoc_blockInvoke:)
                                                userInfo:[block copy]
                                                 repeats:repeats];

}


+ (void)eoc_blockInvoke:(NSTimer*)timer {
     void (^block)() = timer.userInfo;
         if (block) {
             block();
        }
}
@end

Copy the code

We added a method to the NSTimer class. Let’s see how to use it:

- (void)startPolling { __weak EOCClass *weakSelf = self; _pollTimer = [NSTimer eoc_scheduledTimerWithTimeInterval: 5.0 block: ^ {EOCClass * strongSelf = weakSelf;  [strongSelf p_doPoll]; } repeats:YES]; }Copy the code

In this case, a weak reference to self is created, and then the block captures the self variable, keeping it alive during execution.

Once the last external reference to an EOC class disappears, the class is released, and an invalidate message is also sent to NSTimer (because an invalidate message was sent to NSTimer in the class’s dealloc method).

Moreover, even if the Dealloc method does not send an invalidate message, NSTimer will also fail because weakSelf in the block will become nil.

The last word

In general, this section is relatively easy to understand. It teaches us more about the specifications for writing OC programs, without going into the technical details.

The final part of the trilogy, Tips, focuses on some tips you can use to write OC code. Broadly speaking, these tips can also be referred to as “specifications”, such as the section on “providing universal initialization methods”, but they are more like “design patterns” aimed at solving practical problems and therefore classified as “tips”.

Because the content of the third article is a little bit more difficult, so I plan to digest it for a few more days, and then I will present the first draft of the third article to you

This post has been synced to my blog: Portal

Portals in the other two games:

Effective Objective-C — Effective Objective-C — Effective Objective-C

Effective Objective-C Effective Objective-C Effective Objective-C

— — — — — — — — — — — — — — — — — — — — — — — — — — — — on July 17, 2018 update — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Pay attention!!

The author recently opened a personal public account, mainly to share programming, reading notes, thinking articles.

  • Programming articles: including selected technical articles published by the author before, and subsequent technical articles (mainly original), and gradually away from iOS content, will shift the focus to improve the direction of programming ability.
  • Reading notes: Share reading notes on programming, thinking, psychology, and career books.
  • Thinking article: to share the author’s thinking on technology and life.

Because the number of messages released by the official account has a limit, so far not all the selected articles in the past have been published on the official account, and will be released gradually.

And because of the various restrictions of the major blog platform, the back will also be released on the public number of some short and concise, to see the big dry goods article oh ~

Scan the qr code of the official account below and click follow, looking forward to growing with you