This is the final installment in Effective Objective-C’s dry trilogy: Tips. This article summarizes some of the book’s development tips and biases toward “design patterns.”

For those of you who don’t know what TRILOGY I’m talking about, take a look at this:

The first two portals:

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

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

Article 9 hides implementation details in the “family pattern.

In iOS development, we also use the “class Cluster” design pattern to instantiate different entity subclasses through “abstract base classes.”

An 🌰 :

+ (UIButton *)buttonWithType:(UIButtonType)type;
Copy the code

In this case, we just type in different button types (UIButtonType) to get different subclasses of UIButton. This design pattern is commonly used in the OC framework.

Why do you do that?

I think the reason for this is to “weaken” the concrete type of the subclass so that developers don’t have to worry about which class the subclass is created for. (Here I think there is something, but I haven’t thought of it yet, welcome to add!)

Let’s look at a concrete example: For the class “employee,” there are various “subtypes” : development employee, design employee, and finance employee. These “entity classes” can be obtained from the abstract base class “employee” :

1. Abstract base classes

//EOCEmployee.h

typedef NS_ENUM(NSUInteger, EOCEmployeeType) {
    EOCEmployeeTypeDeveloper,
    EOCEmployeeTypeDesigner,
    EOCEmployeeTypeFinance,
};

@interface EOCEmployee : NSObject

@property (copy) NSString *name;
@property NSUInteger salary;


// Helper for creating Employee objects
+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type;

// Make Employees do their respective day's work - (void)doADaysWork; @endCopy the code
//EOCEmployee.m

@implementation EOCEmployee

+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type {
     switch (type) {
         case EOCEmployeeTypeDeveloper:
            return [EOCEmployeeDeveloper new];
         break; 

        case EOCEmployeeTypeDesigner:
             return [EOCEmployeeDesigner new];
         break;

        case EOCEmployeeTypeFinance:
             return [EOCEmployeeFinance new];
         break;
    }
}

- (void)doADaysWork {// Requires subclasses to implement} @endCopy the code

As we can see, using EOCEmployee as an abstract base class, the abstract base class has an initialization method that gives us multiple entity subclasses based on the abstract base class:

2. Concrete Subclass:


@interface EOCEmployeeDeveloper : EOCEmployee
@end

@implementation EOCEmployeeDeveloper

- (void)doADaysWork {
    [self writeCode];
}

@end

Copy the code

Note: Be careful when querying type information if the object belongs to a class that belongs to a family of classes. Because entity subclasses in a family of classes do not belong to the same class as their base class.

Use associated objects to store custom data in existing classes

We can concatenate two objects through the “associative object” mechanism. So we can get the value of the corresponding associated object from an object.

Let’s look at the syntax of the associated object:

1. Set the associated object value for an object:

void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)

Here, the first parameter is the master object, the second parameter is the key, the third parameter is the associated object, and the fourth parameter is the storage policy: the enumeration defines the memory management semantics.

2. Obtain the corresponding associated object value from an object based on the given key:

id objc_getAssociatedObject(id object, void *key)

3. Remove the associated object of the specified object:

void objc_removeAssociatedObjects(id object)

Here’s an example:

#import <objc/runtime.h>

static void *EOCMyAlertViewKey = "EOCMyAlertViewKey";


- (void)askUserAQuestion {

         UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question"
                                                         message:@"What do you want to do?"
                                                        delegate:self
                                               cancelButtonTitle:@"Cancel"
                                               otherButtonTitles:@"Continue", nil];

         void (^block)(NSInteger) = ^(NSInteger buttonIndex){

                     if (buttonIndex == 0) {
                            [self doCancel];
                     } else {
                            [self doContinue]; }}; / / will be alert and block associated with objc_setAssociatedObject (alert, EOCMyAlertViewKey, block, OBJC_ASSOCIATION_COPY); [alert show]; } // UIAlertViewDelegate protocol method - (void)alertView:(UIAlertView*)alertView ClickedButtonAtIndex :(NSInteger)buttonIndex {//alert: void (^block)(NSInteger) = Objc_getAssociatedObject (alertView, EOCMyAlertViewKey) // Pass the index value to the block block(buttonIndex); }Copy the code

Article 13: Use “Method Blending Technique” to debug “black box method”

The method corresponding to the selector name can be changed at run time, so we can change the functionality of the class itself without inheriting the class and overwriting the method.

So how do you change the selectors’ corresponding methods at run time? A: By manipulating the IMP pointer to the class’s method list

What is a class method table? What is an IMP pointer?

The method list of the class maps the selectors’ names to the associated method implementation, allowing the DYNAMIC messaging system to find the method to call. These methods are represented as function Pointers, called IMP. For example, the NSString class selector sublist:

With this table, OC’s run-time system provides several ways to manipulate it. Developers can add selectors to it, change the method implementation corresponding to one of the selectors, or swap the Pointers mapped to the two selectors for the purpose of swapping method implementations.

Here’s an example: Swap the lowercaseString and uppercaseString methods:


Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class],@selector(uppercaseString));

method_exchangeImplementations(originalMethod, swappedMethod);

Copy the code

As a result, the mapping for the class method table looks like this:

At this point, if we call the lowercaseString method we actually call the uppercaseString method, and vice versa.

However! In practice, it doesn’t make much sense to swap only two methods that already exist. We should use this feature to add new functionality to an existing method (which sounds silly) :

It works by adding a new method by classification, and then replacing the new method with the old method to add functionality (the old method name corresponds to the implementation of the new method) so that if we call the old method, the new method will be implemented.

I don’t know if that’s abstract. Here’s an example:

** We need to add an output statement to the original lowercaseString method.

Step 1: We first write the new method in the NSString classification:

@interface NSString (EOCMyAdditions) - (NSString*)eoc_myLowercaseString; @end @implementation NSString (EOCMyAdditions) - (NSString*)eoc_myLowercaseString { NSString *lowercase = [self eoc_myLowercaseString]; // the eoc_myLowercaseString method will execute the lowercaseString method NSLog(@) after a future method swap"% @ = > % @", self, lowercase); // Output statements for easy debuggingreturn lowercase;
}
@end

Copy the code

Step 2: Swap the implementation of the two methods (manipulate the swap IMP pointer)

Method originalMethod =
 class_getInstanceMethod([NSString class],
 @selector(lowercaseString));
Method swappedMethod =
 class_getInstanceMethod([NSString class],
 @selector(eoc_myLowercaseString));

method_exchangeImplementations(originalMethod, swappedMethod);
Copy the code

This way, if we swap the lowercaseString and eoc_myLowercaseString method implementations, we can print the new statement after calling the original lowercaseString method.

"Nsstrings * string = @"ThIs iS tHe StRiNg"; NSString *lowercaseString = [string lowercaseString]; // Output: ThIs iS tHe StRiNg => ThIs iS tHe StRiNgCopy the code

Article 16: Provide “universal initialization methods”

Sometimes, a class can have multiple initializers that create instances due to various design requirements. We should select one of these as a universal initializer and have the other initializers call it.

Note:

  • Internal data can only be stored in this universal initialization method. This way, when the underlying datastore mechanism changes, you only need to modify the code of this method, without changing the other initialization methods.
  • The universal initializer is the most parameterized of all initializers, because it uses as many parameters as it needs to be initialized so that other methods can call it.
  • After we have a universal initialization method, it is best to override the init method to set the default values.
// universal initialization method - (id)initWithWidth:(float)width andHeight:(float)height
{
     if ((self = [super init])) {
        _width = width;
        _height = height;
    }
    returnself; } //init also calls the universal initialization method - (id)init {return[the self initWithWidth: 5.0 f andHeight: 10.0 f]; }Copy the code

Now we’re going to create a squre class that inherits the ractangle class, which has its own universal initialization method:

- (id)initWithDimension: (float)dimension{
    return [super initWithWidth:dimension andHeight:dimension];
}
Copy the code

Something’s wrong here!

However, because the Square class is a subclass of the Rectangle class, it can also use the initWithWidth: andHeight: method, or even init. So in both cases, there’s obviously no guarantee that the initial shape is square.

Therefore, we need to override the omnipotent initialization method for Square’s Rectangle parent:

- (id)initWithWidth:(float)width andHeight:(float)height
{
    float dimension = MAX(width, height);
    return [self initWithDimension:dimension];
}

Copy the code

This way, when square is initialized with initWithWidth: andHeight:, you get a square.

Also, if we initialize square with init, we can get a default square. Because the rectangle class overwrites init, which in turn calls initWithWidth: andHeight: and the square class overwrites initWithWidth: AndHeight: method, so we still get a square.

Also, to make square’s init method get a default square, we can override its own initialization method:

- (id)init{
    return[the self initWithDimension: 5.0 f]; }Copy the code

Let’s conclude:

We need to override initWithWidth: andHeight: in the subclass because the universal initialization method (initWithDimension) is not the same as its parent class.

A bit less: initWithCoder: initialization

Sometimes, you need to define two universal initialization methods, because objects can be created in two completely different ways, such as the initWithCoder: method.

We still need to call the initialization method of the superclass:

In a rectangle class:

// Initializer from NSCoding
- (id)initWithCoder:(NSCoder*)decoder {

     // Call through to super's designated initializer if ((self = [super init])) { _width = [decoder decodeFloatForKey:@"width"]; _height = [decoder decodeFloatForKey:@"height"]; } return self; }Copy the code

In square type:

// Initializer from NSCoding
- (id)initWithCoder:(NSCoder*)decoder {

 // Call through to super's designated initializer if ((self = [super initWithCoder:decoder])) { // EOCSquare's specific initializer
    }
     return self;
}
Copy the code

The omnipotent initializer method of each subclass should call the corresponding method of its superclass, layer by layer. After the initialization method of the superclass is called, the methods associated with the superclass are executed.

Article 17: Implement the Description method

When we print an instance object of our own class, the console output usually looks like this:

object = <EOCPerson: 0x7fd9a1600600>
Copy the code

This contains only the class name and memory address, and its information is obviously not specific, far from the requirements of debugging.

* * but! ** If we override the description method in our own class, we can print the information we want when we print an instance of the class.

Such as:


- (NSString*)description {
     return [NSString stringWithFormat:@"<%@: %p, %@ %@>", [self class], self, firstName, lastName];
}

Copy the code

Here, the memory address is displayed, along with all the attributes of the class.

Furthermore, it is more readable if we print these attribute values in a dictionary:

- (NSString*)description {

     return [NSString stringWithFormat:@"<%@: %p, %@>",[self class],self,
   
    @{    @"title":_title,
       @"latitude":@(_latitude),
      @"longitude":@(_longitude)}
    ];
}
Copy the code

Output result:

location = <EOCLocation: 0x7f98f2e01d20, {

    latitude = "51.506"; longitude = 0; title = London; } >Copy the code

As we can see, rewriting the description method allows us to know more about the object, facilitating later debugging and saving development time.

Provision of anonymous objects by protocol

An anonymous object is an anonymous object. Sometimes we use protocols to provide anonymous objects, just to show that it means “objects that comply with a protocol” and not “objects that belong to a class.”

It is represented as id . There are two main usage scenarios for providing anonymous objects through protocols:

  • As the attribute
  • As a method parameter

1. Anonymous objects as attributes

When you set a class as your proxy property, you can use an ID instead of declaring the proxy class, because the endpoint of the proxy is not an instance of a class, but a protocol.

An:

@property (nonatomic, weak) id <EOCDelegate> delegate;
Copy the code

There are two reasons for using anonymous objects here:

  1. In the future, there may be many instance objects of different classes acting as proxies for this class.
  2. We do not want to specify which class to use as a proxy for this class.

In other words, there is only one condition for a proxy of this class: it complies with the protocol.

2. Use anonymous objects as method parameters

Sometimes, anonymous objects can be used as method parameters when you don’t care about the specific type of a parameter in a method, but when you follow a protocol.

An:

- (void)setObject:(id)object forKey:(id<NSCopying>)key;
Copy the code

This method is the set method of NSDictionary, and its parameters can be passed as parameters, as long as they comply with the protocol, as the keys of NSDictionary.

Rule 32: Be aware of memory management issues when writing exception-safe code

Memory management in the event of an exception requires careful consideration of memory management:

In a try block, if an object is retained and then an exception is thrown before it is released, the memory occupied by the object will leak unless this can be handled in a catch block.

In MRC environment:


@try {
     EOCSomeClass *object = [[EOCSomeClass alloc] init];
      [object doSomethingThatMayThrow];
      [object release];

}


@catch (...) {
         NSLog(@"Whoops, there was an error. Oh well...");
}

Copy the code

Here, we use the release method to free the object in the try, but this still has a problem: what if an exception is thrown in the doSomthingThatMayThrow method?

You can’t execute the release method.

The solution is to use the @finnaly block, where the code will run whether or not an exception is thrown:


EOCSomeClass *object;
@try {
    object = [[EOCSomeClass alloc] init];
    [object doSomethingThatMayThrow];
}



@catch (...) {
     NSLog(@"Whoops, there was an error. Oh well...");
}

@finally {
    [object release];
}

Copy the code

How about in an ARC environment?

@try {
     EOCSomeClass *object = [[EOCSomeClass alloc] init];
     [object doSomethingThatMayThrow];
}



@catch (...) {
 NSLog(@"Whoops, there was an error. Oh well...");
}

Copy the code

At this point, we can’t use the release method manually. The solution is to use the: -fobjc-arc-exceptions flag to add the cleanup code, but this will make the application bigger and less efficient.

Rule 33: Avoid retaining rings with weak references

Objects that reference each other with strong Pointers create reserved rings.

Reserved rings for two objects:

Each object has an instance of the other as its property:


@interface EOCClassA : NSObject
@property (nonatomic, strong) EOCClassB *other;
@end


@interface EOCClassB : NSObject
@property (nonatomic, strong) EOCClassA *other;
@end

Copy the code

Both objects have strong Pointers to each other, so objects in these properties cannot be released.

Retention rings for multiple objects:

If the reserved ring is connected to multiple objects, and one of the objects is referred to, the entire reserved ring is leaked when the reference is removed.

The solution is to use weak references:

// retained @property (nonatomic, unsafe_unretained) EOCClassA *other; // retained @property (nonatomic, unsafe_unretained). // Weak @property (nonatomic, weak) EOCClassA *other;Copy the code

What’s the difference between these two weak references?

Unsafe_unretained: After the reference to the EOCClassA instance is removed, the unsafe_unretained attribute points back to the reclaimed instance.

Weak refers to nil:

Obviously, using the weak field should be safer, because objects that are no longer in use should be set to nil and should not have dependencies.

Rule 34: Reduce peak memory with auto release pool fast


There are two ways to release objects:

  • Call release: Decrement the retention count
  • Call autoRelease to add it to the automatic release pool. When emptying the auto-release pool in the future, the system will send a release message to the objects in it.

The high-memory waterline is the highest memory footprint of an application in a specified period of time. The addition of auto-release pool blocks can reduce this peak:

Peak reduction without automatic release pool:


for (int i = 0; i < 100000; i++) {

      [self doSomethingWithInt:i];

}

Copy the code

Here, the doSomethingWithInt: method might create a temporary object. The number of temporary objects soared as the number of loops increased, and they were only released with satisfaction after the entire for loop ended.

This is not ideal, especially if we have no control over the length of the loop, and we keep taking up memory and suddenly releasing it.

Therefore, we need to reduce this abrupt change with automatic release pools:

NSArray *databaseRecords = /* ... * /. NSMutableArray *people = [NSMutableArray new];for (NSDictionary *record indatabaseRecords) { @autoreleasepool { EOCPerson *person = [[EOCPerson alloc] initWithRecord:record]; [people addObject:person]; }}Copy the code

This way, at the end of each loop, we put temporary objects in the pool instead of the thread’s main pool.

Rule 35: Debug memory management problems with zombie objects

It is not safe to send messages to an object after it has been reclaimed, and this does not necessarily cause the program to crash.

If the program does not crash, it may be because:

  • Part of the original data in this memory is not overwritten.
  • That memory happens to be occupied by another object that can answer the method.

If the original memory occupied by the reclaimed object is occupied by the new object, the object receiving the message will not be the one we expected. In this case, if the object cannot respond to the method, the program will still crash.

Therefore, we want a way to capture when an object receives a message after it is released.

This method uses zombie objects!

Cocoa provides functionality for “zombie objects”. , if open the function of the runtime system will convert all have recovered instance to the special object of “zombie” (by modifying the isa pointer, make its point to special zombie class), rather than real recycling them, and they occupied the core memory will not be able to be reused, it also avoids the overwrite.

When a zombie object receives a message, it throws an exception that describes the sent message and the object before it was reclaimed.

Rule 38: Create a typedef for commonly used block types

A typedef can be used to define a block’s own new type if we need to repeatedly create a variable of a block (same argument, return value)

Such as:

int (^variableName)(BOOL flag, int value) =^(BOOL flag, int value){
     // Implementation
     return someInt;
}

Copy the code

This block takes a bool and an int and returns an int. We can define a type for it:

typedef int(^EOCSomeBlock)(BOOL flag, int value);

When redefined, this can be done with a simple assignment:

EOCSomeBlock block = ^(BOOL flag, int value){
     // Implementation
};

Copy the code

Define blocks as arguments:

- (void)startWithCompletionHandler: (void(^)(NSData *data, NSError *error))completion;

Copy the code

The block here has an NSData parameter and an NSError parameter that has no return value

typedef void(^EOCCompletionHandler)(NSData *data, NSError *error); - (void) startWithCompletionHandler (EOCCompletionHandler) completion;"Copy the code

The advantage of defining a block signature through a typedef is that if you want a block to add parameters, you can only modify the line of code that defines the signature.

Use handler blocks to reduce code fragmentation

Using a proxy approach when downloading network data results in a less compact distribution of code and, if there are multiple downloads, the type of request currently being called back to the proxy. With blocks, however, the code downloaded from the network can be written together with the code handled by the callback, thus solving both of the above problems:

Download with proxy:

- (void)fetchFooData {

     NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/foo.dat"];
    _fooFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
    _fooFetcher.delegate = self;
    [_fooFetcher start];

}

- (void)fetchBarData {

     NSURL *url = [[NSURL alloc] initWithString: @"http://www.example.com/bar.dat"]; _barFetcher = [[EOCNetworkFetcher alloc] initWithURL:url]; _barFetcher.delegate = self; [_barFetcher start]; } - (void)networkFetcher:(EOCNetworkFetcher*)networkFetcher didFinishWithData:(NSData*)data {// determine the type of the downloaderif (networkFetcher == _fooFetcher) {
        _fetchedFooData = data;
        _fooFetcher = nil;

    } else if(networkFetcher == _barFetcher) { _fetchedBarData = data; _barFetcher = nil; }}Copy the code

Download with block:


- (void)fetchFooData {

     NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/foo.dat"];
     EOCNetworkFetcher *fetcher =
     [[EOCNetworkFetcher alloc] initWithURL:url];
     [fetcher startWithCompletionHandler:^(NSData *data){
            _fetchedFooData = data;
   }];

}



- (void)fetchBarData {

     NSURL *url = [[NSURL alloc] initWithString: @"http://www.example.com/bar.dat"];
     EOCNetworkFetcher *fetcher =[[EOCNetworkFetcher alloc] initWithURL:url];
    [fetcher startWithCompletionHandler:^(NSData *data){
            _fetchedBarData = data;
    }];

}

Copy the code

It is also possible to put successful code in one block and failed code in another:


“#import <Foundation/Foundation.h>

@class EOCNetworkFetcher;
typedef void(^EOCNetworkFetcherCompletionHandler)(NSData *data);
typedef void(^EOCNetworkFetcherErrorHandler)(NSError *error);


@interface EOCNetworkFetcher : NSObject

- (id)initWithURL:(NSURL*)url;
- (void)startWithCompletionHandler: (EOCNetworkFetcherCompletionHandler)completion failureHandler: (EOCNetworkFetcherErrorHandler)failure;

@end



EOCNetworkFetcher *fetcher =[[EOCNetworkFetcher alloc] initWithURL:url];
[fetcher startWithCompletionHander:^(NSData *data){
     // Handle success
}

 failureHandler:^(NSError *error){
 // Handle failure
}];



Copy the code

The advantage of writing this way is that we can separate the code that deals with success and failure and write it more clearly.

We can also put both successful and failed code in the same block:


“#import <Foundation/Foundation.h>


@class EOCNetworkFetcher;
typedef void(^EOCNetworkFetcherCompletionHandler)(NSData *data, NSError *error);

@interface EOCNetworkFetcher : NSObject

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

(EOCNetworkFetcherCompletionHandler)completion;

@end



EOCNetworkFetcher *fetcher =[[EOCNetworkFetcher alloc] initWithURL:url];

[fetcher startWithCompletionHander:

^(NSData *data, NSError *error){

if (error) {

     // Handle failure

} else {

     // Handle success

}
}];

Copy the code

The advantage of this is that if the download fails or breaks in time, we can still retrieve the currently downloaded data. Also, if the requirement states that a successful download is considered a failure with very little data, then the single-block approach is suitable because it can determine whether the download was successful after the data is obtained (success).

Rule 40: Do not have a retention ring when a block refers to its owning object

If a block captures an object that directly or indirectly preserves the block itself, then you need to be careful about retention loops:

@implementation EOCClass {

     EOCNetworkFetcher *_networkFetcher;
     NSData *_fetchedData;

}


- (void)downloadData {

     NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"];
    _networkFetcher =[[EOCNetworkFetcher alloc] initWithURL:url];

    [_networkFetcher startWithCompletionHandler:^(NSData *data){

             NSLog(@"Request URL %@ finished", _networkFetcher.url);
            _fetchedData = data;

    }];

}

Copy the code

Here comes the retention loop: To set the _fetchedData variable, the block needs to capture the self variable. Self (EOCClass instance) retains the fetcher _networkFetcher via the instance variable, which in turn retains the block.

The solution is to set _networkFetcher to nil after getting data in the block.


- (void)downloadData {

     NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"];
    _networkFetcher =[[EOCNetworkFetcher alloc] initWithURL:url];
    [_networkFetcher startWithCompletionHandler:^(NSData *data){

             NSLog(@"Request URL %@ finished", _networkFetcher.url);
            _fetchedData = data;
            _networkFetcher = nil;

    }];

}

Copy the code

Rule 41: Use more queues and less synchronous locks

When multiple threads execute the same piece of code, it is likely that data will be out of sync. The authors suggest using GCD to lock the code to solve this problem.

Scheme 1: Use a serial synchronization queue to arrange read and write operations into the same queue:

_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL); // Read string - (NSString*)someString {__block NSString*localSomeString;
         dispatch_sync(_syncQueue, ^{
            localSomeString = _someString;
        });
         return localSomeString; } // Set string - (void)setSomeString:(NSString*)someString {

     dispatch_sync(_syncQueue, ^{
        _someString = someString;
    });
}

Copy the code

In this way, read and write operations are performed in serial queues, which are less prone to errors.

However, there is another way to make performance even better:

Plan two: willThe write operationPut them in the fence and let them do it alone; willRead operationConcurrent execution.

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // Read string - (NSString*)someString {__block NSString*localSomeString;
     dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
     return localSomeString;
}
Copy the code
// Set string - (void)setSomeString:(NSString*)someString {

     dispatch_barrier_async(_syncQueue, ^{
        _someString = someString;
    });

}

Copy the code

Obviously, the correctness of the data depends on the write operation, so as long as the thread is safe at the time of the write, the data can be guaranteed to be synchronized even if the read operation is concurrent.

The dispatch_barrier_async method here makes the operation “ordered” in the synchronous queue, ensuring that the write operation is in the serial queue.

Rule 42: use GCD more than performSelector

In iOS development, performSelector is sometimes used to perform a method, but the performSelector family of methods is limited in how many selectors they can handle:

  • It cannot handle selectors with multiple arguments.
  • The return value can only be void or object type.

But putting methods in blocks and manipulating them via GCD solves these problems nicely. In particular, if we want a task to be executed on another thread, it’s better to put the task in a block and hand it over to the GCD rather than performSelector.

A few examples to compare the two options:

1. Ways to postpone a task:

/ / use the performSelector: withObject: afterDelay: [self performSelector: @ the selector (doSomething) withObject: nil afterDelay: 5.0]; // dispatch_after dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)); dispatch_after(time, dispatch_get_main_queue(), ^(void){ [selfdoSomething];
});

Copy the code

2. Put the task on the main thread:

/ / use performSelectorOnMainThread: withObject:waitUntilDone:
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO]; // dispatch_async // (orif waitUntilDone is YES, then dispatch_sync)
dispatch_async(dispatch_get_main_queue(), ^{
        [self doSomething];
});

Copy the code

Note: If the waitUntilDone parameter is Yes, then it corresponds to GCD’s dispatch_sync method. As you can see, using GCD, you can write the thread operation code and the method call code in one place, which is easy to see. It is not limited by the number of selectors or method arguments to be invoked.

Rule 43: Know when to use GCD and operation queue

In addition to GCD, NSOperationQueue is also a solution to the problem of multithreaded task management. For different environments, different strategies are used to solve the problem: sometimes it is better to use GCD, and sometimes it makes more sense to use action queues.

Advantages of using NSOperation and NSOperationQueue:

  1. You can cancel: Before running a task, you can call the Cancel method on the NSOperation object to indicate that the task does not need to be executed. However, the GCD queue cannot be cancelled because it follows the “fire and forget” principle.
  2. Dependencies between operations can be specified: for example, the action of downloading and processing a file from a server can be represented as an operation. The manifest file must be downloaded before any other files can be processed. Subsequent downloads will depend on the list files downloaded first.
  3. Monitoring the properties of the NSOperation object: you can use KVO to listen the properties of the NSOperation. You can use isCancelled to determine whether the task has been cancelled. The isFinished property is used to determine whether the task is complete.
  4. You can specify the priority of an operation: The priority of an operation represents the priority of this operation in relation to other operations in the queue, and we can specify it.

Item 44: Perform tasks according to system resource status through the Dispath Group mechanism

Sometimes you need to wait for the end of multiple parallel tasks to execute a task. In this case, you can use dispath group to implement this requirement:

Using the Dispath group function, you can group concurrent tasks together so that the caller knows when they will all be completed.

Dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); Dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); Dispatch_group_t dispatchGroup = dispatch_group_create(); // Place low-priority queues into dispatch_groupfor (id object inlowPriorityObjects) { dispatch_group_async(dispatchGroup,lowPriorityQueue,^{ [object performTask]; }); } // Place the queue with the highest priority into dispatch_groupfor (id object inhighPriorityObjects) { dispatch_group_async(dispatchGroup,highPriorityQueue,^{ [object performTask]; }); } // dispatch_queue_t notifyQueue = dispatch_get_main_queue(); dispatch_group_notify(dispatchGroup,notifyQueue,^{ // Continue processing after completing tasks });Copy the code

Rule 45: Use dispatch_once to execute thread-safe code that only needs to be run once

Sometimes we only need to execute a piece of code once, and this can be done with the dispatch_once function.

A more important use of the dispatch_once function is the singleton mode: we can use the dispatch_once function when creating instances of the singleton mode to make the initialization code run only once and be thread safe internally.

Also, for a block that executes once, the tags passed in must be exactly the same each time the function is called, usually in the static or global scope.

+ (id)sharedInstance { static EOCClass *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{  sharedInstance = [[self alloc] init]; });return sharedInstance;
}

Copy the code

The code in the dispatch_once block has commented itself out of existence after just one run from startup to termination.

Rule 49: Use seamless bridging for collections that customize their memory management semantics

Using seamless bridging techniques, you can convert back and forth between OC objects in the Foundation framework and C language data structures in the CoreFoundation framework.

When you create a Collection in CoreFoundation, you can specify how to process its elements. This can then be converted to OCcollection using seamless bridging technology.

A simple seamless bridge demonstration:

NSArray *anNSArray = @[@1, @2, @3, @4, @5];
CFArrayRef aCFArray = (__bridge CFArrayRef)anNSArray;
NSLog(@"Size of array = %li", CFArrayGetCount(aCFArray));

Copy the code

Here, __bridge means that ARC still has ownership of the OC object. CFArrayGetCount is used to get the length of the array.

Why use seamless bridge technology? Because some OC objects have properties that their corresponding CF data structures do not, and vice versa. Therefore, we need to use seamless bridging technology to make the two functions “complementary”.

The last word

Finally concluded, or individual knowledge points are not very thorough understanding, need to repeatedly read and understand digestion. I hope you can put forward your valuable opinions and exchange your knowledge

This post has been synced to my blog: Portal

— — — — — — — — — — — — — — — — — — — — — — — — — — — — 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