The target
Recently, I have been reading some advanced iOS books, and I have made some simple notes to deepen my impression. Effective+Objective-C 2.0 52 Effective Ways to Write High Quality iOS and OS X Code
Note: 20170719 has made some amendments to the article
# 1: Understand the origins of Objective-C
Objective-c evolved from Smalltalk, the granddaddy of messaging languages.
Object *obj = [Object new]; [obj performWith:parameter1 and:parameter2]; // Object *obj = new Object; obj->preform(parameter1,parameter2);Copy the code
- Objective-c is a superset of C that adds object-oriented features. Objective-c uses a dynamically bound message structure, that is, object types are checked at run time. The runtime environment, not the compiler, decides what code to execute after receiving a message.
- Understanding the core concepts of C helps you write good Objective-C programs. In particular, learn memory models and Pointers.
Rule 2: Minimize references to other headers in class header files
- Do not introduce header files unless absolutely necessary. In general, use forward declarations (@class syntax) in the header files of one class to refer to other classes, and introduce the headers of those classes in the implementation file. Doing so reduces the coupling between classes.
- Sometimes forward declarations are not possible, such as declaring that a class follows a protocol. In this case, try to move the declaration “this class follows a protocol” into the class-continutation class. If not, place the protocol in a separate header file and import it.
Note: “class-continutation classification” refers to “extension”. 1) reduce program editing time; 2) reduce coupling between classes, making classes clearer and easier for users to understand; 3) effectively avoid cross-reference problems
Rule 3: Use literal syntax more often than equivalent syntax
NSNumber *someNumber = @(1);
NSArray *animals = @[@"dog"The @"cat"The @"mouse"The @"badger"]; NSString *dog = animal[1]; NSDictionary *personData = @{@"firstName": @"shi"The @"lastName": @"xueqian",age:@(26)};
NSString *lastName = personData[@"lastName"]; // mutableArray replaceObjectAtIndex:1 withObject:@"dog"];
[mutableDictionary setObject:@"xueqian" forKey:@"lastName"]; // Use literal syntax to replace mutableArray[1] = @"dog";
mutableDictonary[@"lastName"] = @"xueqian";
Copy the code
- Literal syntax should be used to create strings, numbers, arrays, and dictionaries. This is much more concise than the normal method of creating such objects.
- The application accesses elements corresponding to an array subscript or a dictionary key by fetching subscripts.
- When creating an array or dictionary using literal syntax, an exception is thrown if there is nil in the value. Make sure there’s no nil in the value.
Use type constants more often than #define preprocessors
// Preprocessing instruction# define ANIMATION_DURATION 0.3Static const NSTimeInterval kAnimationDuration = 0.3; // Declare extern NSString *const EOCStringConstant; // The global constant implementation file defines NSString *const EOCStringConstant = @"VALUE";
Copy the code
- Do not define constants with preprocessor instructions. The resulting constants contain no type information, which the compiler uses to perform lookup and replace operations before compilation. Even if someone redefines a constant value, the compiler will not generate a warning message, which will result in inconsistent constant values in the application.
- Use static const in the implementation file to define “constants visible only within the compilation unit”. Since such constants are not in the global symbol table, there is no need to prefix their names.
- Use extern in header files to declare global constants and define their values in the associated implementation files. For such constants to appear in the global symbol table, all their names are delimited, usually prefixed by the name of the associated class.
Enumerations represent states, options, and status codes
/ / common enumeration typedef NS_ENUM (NSUInteger EOCConnectionState) {EOCConnectionStateDisconnected EOCConnectionStateConnecting, EOCConnectionStateConnected, }; // Switch statements should not add the defalut branch switch(_currentState) {case: EOCConnectionStateDisconnected: / / workbreak;
case: EOCConnectionStateConnecting: / / workbreak;
case: EOCConnectionStateConnected: / / workbreak; } // Binary enumeration typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection){EOCPermittedDirectionUp = 1 << 0, EOCPermittedDirectionDown = 1 << 1, EOCPermittedDirectionLeft = 1 << 2, EOCPermittedDirectionRight = 1 << 3, } / / binary enumeration using EOCPermittedDirection direction = EOCPermittedDirectionUp | EOCPermittedDirectionDown;if(Direction & EOCPermittedDirectionUp){// EOCPermittedDirectionUp}Copy the code
- Use enumerations to represent the state of the state machine, the options passed to the method, and the status code equivalents, giving the values easy-to-understand names.
- If the option passed to a method is represented as an enumeration type and multiple options can be used at the same time, the value of each option is defined as a power of two so that it can be combined by bitwise or operation.
- Do not use the default branch in switch statements that handle coin types. That way, after a new enumeration is added, the compiler will tell the developer that the switch statement does not process all the enumerations.
### ### ## 6: Understand the concept of “attributes.
Objective-c objects typically store the data they need as various instance variables. Instance variables are typically accessed through an “access method.” Getters are used to read variable values, and setters are used to write variable values.
@synthesize syntax: specify the name of an instance variable (rarely used) :
@implementation EOCPerson
@synthesize firstName = _myFirstName;
@end
Copy the code
The @dynamic keyword: This tells the compiler not to automatically create the instance variable used to implement the property, nor to create an access method for it. Moreover, when compiling code that accesses properties, the compiler does not report an error even if it finds that access methods are not defined, trusting that they will be found at run time.
@implementation EOCPerson @dynamic firstName, lastName; @endCopy the code
Atomicity: The default atomic attribute. You can use a locking mechanism to ensure atomicity of getter method operations. But there is no guarantee of “thread-safety.” Due to the overhead of using synchronous locks in iOS, nonatomic is generally used only. Read/write permissions: Readonly and ReadWrite Getter = : Specifies the method name of the getter.
@property (nonatomic, getter=isOn) BOOL on;
Copy the code
- You can use the @property syntax to define the data encapsulated in an object. 2. Use attributes to define the correct semantics needed to store data.
- When setting the instance variable corresponding to a property, be sure to follow the semantics declared for that property.
- Use nonatomic properties when developing iOS applications because atomic properties can seriously affect performance.
Rule 7: Try to access instance variables directly from within an object
- When reading data from within an object, it should be read directly through instance variables, and when writing data, it should be written using properties.
- In the initializer and dealloc methods, you should always use instance variables to read and write data.
- Sometimes a piece of data is configured using lazy loading techniques, in which case properties are required to read the data.
Rule 8: Understand the concept of “object equality”
- To check the equality of objects, provide the isEqual and hash methods.
- The same object must have the same hash code, but two objects with the same hash code are not necessarily the same.
- Instead of blindly testing each attribute individually, you should tailor your testing plan to your specific needs.
- When writing hash methods, use algorithms that are fast and have a low probability of hash code collisions.
Rule 9: Hide implementation details in a “class manner.
- The family of classes pattern hides implementation details behind a simple set of common interfaces.
- Class families are often used in system frameworks.
- Be careful when inheriting subclasses from the common abstract base class of a family of classes, and read development documentation first, if any.
Use associated objects to store custom data in existing classes
Association types | Equivalent @property |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic retain |
OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic copy |
OBJC_ASSOCIATION_RETAIN | retain |
OBJC_ASSOCIATION_COPY | copy |
Void objc_setAssociatedObject(id object, void *key, id value, Void objc_getAssociatedObject(id object, objc_associationPolicy policy) // This method obtains the value of the associated object from an object using the given key. Void objc_removeAssociatedObject(id object) void objc_removeAssociatedObjectCopy the code
- You can connect two objects through the “associate object” mechanism.
- You can specify memory management semantics when defining associated objects to mimic all of the “owning” and “non-owning” relationships used when defining attributes.
- Use associated objects only when other methods are not available, because this use often introduces hard-to-find bugs.
Rule 11: Understand the role of objc_msgSend
- C: THE C language uses “static binding,” which means that at compile time you can decide which functions to call at run time.
- Objective-c: The function to be called is not determined until run time. At the bottom, all methods are plain C functions, but which method to call once an object receives a message is entirely up to the runtime, and can even be changed while the program is running. These features make Objective-C a truly dynamic language.
// Send the message ID to the objectreturnValue = [someObject messageName:parameter]; Void objc_msgSend(id self, SEL _cmd,...) // Send the underlying message ID to the objectreturnValue = objc_msgSend(some Object, @selector(messageName:),parameter);
Copy the code
- The message consists of the receiver, selectors, and parameters. To “invoke a message” to an object is to “call a method” on that object.
- All messages sent to an object are processed by the dynamic message dispatch system, which looks up the corresponding method and executes its code.
Rule 12: Understand the message forwarding mechanism
- If the object cannot respond to a selector, the message forwarding process is entered.
- With dynamic method resolution at run time, we can add a method to a class when it is needed.
- Objects can pass on certain selectors that they cannot decipher to other objects for processing.
- If you still can’t handle selectors after the previous two steps, start the full message forwarding mechanism.
Article 13: Use “method deployment” technique to debug “black box method”
// methods swap void method_exchangeImplementations(Method m1, Method m2) // methods implement Method class_getInstanceMethod(Class aClass, SEL aSelector) / / demo, OriginalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString)); Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString)); method_exchangeImplementations(originalMethod,swappedMethod);Copy the code
- At run time, you can add or replace method implementations applied by selectors to the class.
- Replacing a method implementation with another implementation is a process called ‘method swizzing,’ a common technique used by developers to add new functionality to an existing implementation.
- In general, the only time you need to modify a method implementation at run time is when debugging the program, and this practice should not be abused.
Rule 14: Understand the meaning of class objects
IsMemberOfClass: can determine whether the object is an instance of a class isKindOfClass: can determine whether the object is an object of a class or a derived classCopy the code
- Each instance has a pointer to Class objects to indicate its type, and these Class objects form the Class inheritance system.
- If the object type cannot be determined at compile time, the type information query method should be used to find out.
- Try to use type information query methods to determine object types rather than directly comparing class objects, some of which may implement message forwarding.
Use prefixes to avoid namespace collisions
Objective-c doesn’t have the built-in namespace mechanism that other languages do. Because of this, we need to avoid potential naming conflicts when naming. The only way to avoid this problem is to implement namespaces in disguise: give all names the appropriate prefix.
- Prefix your class name with the name associated with your company, application, or both, and use that prefix in all your code.
- If a third party library is used in your own library, prefix its name.
Article 16: Provide “universal initialization methods”
- Provide a universal initialization method in a class and specify it in the documentation. All other initialization methods should call this method.
- If the universal initialization method of the superclass is different, the method in the superclass needs to be overridden.
- If the initialization method of the superclass does not apply to the subclass, override the superclass method and throw an exception in it.
Article 17: Implement the Description method
- (NSString *)description {
return [NSString stringWithFormat:@"<%@:%p %@",[self class], self, @{@"firstName":_firstName, @"lastName":_lastName}];
}
- (NSString *)description {
return [NSString stringWithFormat:@"% @ % @",_firstName,_lastName];
}
- (NSString *)debugDescription {
return [NSStrig stringWithFormat:@"< %@ ,%p %@ ,%@",[self class], self, _firstName, _lastName];
}
Copy the code
- Implement the Description method to return a meaningful string that describes the instance.
- To print more detailed object description information during debugging, implement the debugDesription method.
Rule 18: Use immutable objects whenever possible
Mutable is the “readwrite” property object immutable is the “readonly” property object
- Try to create immutable objects
- If a property can only be used for internal modification, extend it from the readonly property to the readwrite property in Extend.
- Instead of exposing mutable collections as properties, provide methods to modify mutable collections in objects.
Rule 19: Use clear and coordinated naming
Considerations when naming methods:
- If the return value of a method is newly created, the first word of the method name should be the type of the return value, unless it is preceded by a modifier, such as localizedString. Property access methods do not follow this naming pattern because they are not expected to create new objects, even if they sometimes return a copy of the internal object, which we consider equivalent to the original object. These access methods should be named according to their corresponding properties.
- You should place the parameter type noun before the parameter.
- If a method performs an operation on the current object, it should include a verb; If you need parameters to perform an operation, you should follow the verb with one or more nouns.
- Don’t use short names like STR, use full names like string.
- Boolean attributes should be prefixed with is. If a method returns a Boolean value that is not an attribute, it should be prefixed with has or is, depending on its function.
- Leave get as a prefix for methods that store the return value as an “output parameter,” such as a method that populates the return value in a “C-array.”
- Follow the standard Objective-C naming conventions to create interfaces that are easier for developers to understand.
- The method name should be concise and read from left to right like an everyday sentence.
- Do not use abbreviated type names in method names.
- The first thing you need to do when naming a method is to make sure that the style matches your own code or the framework you want to integrate.
Rule 20: Prefix private method names
- Prefix the names of private methods so that they can be easily distinguished from public methods.
- Do not prefix private methods with a single underscore, as this is reserved for Apple.
# 21: Understand the Objective-C error model
The NSError object encapsulates three pieces of information:
- Error domain: The scope in which an Error occurs, which is of type string and is usually defined by a unique global variable.
- Error Code: Unique Error code of type integer. Used to specify what kind of error occurred within a range, usually defined as an enum.
- User info: User information. The type is dictionary. Additional information about this error, which may contain a “localization description.”
- (BOOL)doSomething:(NSError **)error {
if (/* there was an error */) {
if (error) {
*error = [NSError errorWithDomain:domain code:code userInfo:userInfo];
}
return NO;
else {
return YES;
}
}
NSError *error = nil;
BOOL ret = [object doSomethig:&error];
if (error) {
}
Copy the code
- Exceptions should be used only when there is a serious error that could crash the entire application.
- In less serious cases, a “delegate method” can be assigned to handle the error, or the error information can be placed in an NSError object and returned to the caller via an “output parameter.”
Article 22: Understand the NSCopying agreement
// A class that supports the copy function needs to implement the NSCopying protocol only this method. NSZone currently has only one default value, which can be ignored. - (id)copyWithZone:(NSZone *)zone - (id)copyWithZone:(NSZone *)zone {EOCPerson *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName andLastName:_lastName];return copy;
}
Copy the code
- If you want to copy your own objects, you need to implement the NSCopying protocol.
- NSCopying and NSMutableCopying protocols are implemented simultaneously if the custom object is of mutable and immutable versions.
- When copying objects, you need to decide whether to use shallow copy or deep copy. Generally, you should perform shallow copy as much as possible.
- If you are writing objects that require deep copies, consider adding a method that performs deep copies exclusively.
Article 23: Communication between objects via delegate and data source protocols
An object delegates responsibility for a behavior to another class. The normal delegate pattern: information flows from the class to the delegate. Data Source pattern: Information flows from Data sources to classes.
- The delegate pattern provides a set of interfaces for objects to communicate related events to other objects.
- The interfaces that delegate objects should support are defined as protocols, in which events that might need to be handled are defined as methods.
- The delegate pattern can be used when an object needs to fetch data from another object. In this context, the pattern is also known as the “data source protocol.”
- If necessary, you can implement a structure with bits to cache information about whether the delegate object can correspond to the relevant protocol method.
Rule 24: Divide the implementation code of a class into manageable categories
- Use a classification mechanism to divide the implementation code of a class into manageable chunks.
- Hide implementation details by grouping methods that should be considered “Private” into a category called Private.
Rule 25: Always prefix third-party class names
- When adding categories to third-party classes, always give their names your own prefix.
- When adding classes to third-party classes, always prefix the method names with your own special prefix.
Do not declare attributes in a classification
- All attributes used to encapsulate data are defined in the master interface.
- In categories other than the class-Continuation category, you can define access methods, but try not to define properties.
Rule 27: Hide implementation details using a “class-Continuation classification.
Note: The “class-continuation” category here is what we normally call an “extension”.
- Add instance variables to a class using the class-Continuation classification.
- If a property is declared “read-only” in the main interface, and the class internally changes it with a setup method, expand it to “read/write” in the class-Continuation class.
- Declare the prototype of a private method in the class-Continuation class.
- If you want to keep the protocol that your class follows unknown, you can declare it in the class-Continuation class.
Provision of anonymous objects by protocol
- The protocol can provide anonymous typing to some extent. A specific object type can be diluted to an ID type that complies with a protocol that specifies the object’s corresponding implementation method.
- Use anonymous objects to hide type names (or class names).
- If the specific type is not important, but the object can be represented by the corresponding property method (defined in the protocol), then anonymous objects can be used.
Rule 29: Understand reference counting
Since Mac OS X 10.8, the “garbage collector” has been officially deprecated and should no longer be used when writing Mac OS X programs in Objective-C code, while iOS has never supported garbage collection.
- Retain Increments the retention count
- Release decrements the retention count
- Autorelease decrements the retention count later when the Autorelease pool is cleaned up. Call autoRelease, which decrement the count later, usually on the next “event loop.”
- The reference counting mechanism manages memory by incrementing and decrementing counters. After an object is created, its retention count is at least 1. If the retention count is positive, the object continues to live. When the retention count drops to 0, the object is destroyed.
- During the object’s life, the rest of the objects retain or release the object by reference. The retention and release operations increase and decrease the retention count, respectively.
Rule 30: Simplify reference counting with ARC
Does not cut in the ARC mode with retain, release, autorelease, dealloc method to alloc, new, copy, mutableCopy ` ` method in the beginning, all the object returned to the caller (the object returned will retain count + 1).
- With ARC, programmers don’t have to worry about memory management. Using ARC to program saves a lot of boilerplate code in your classes.
- ARC manages object lifetimes basically by inserting “hold” and “release” operations where appropriate. In an ARC environment, the memory-management semantics of variables can be indicated by modifiers, instead of manually performing “save” and “release” operations.
- ARC is only responsible for managing memory for Objective-C objects. In particular, CoreFoundation objects are not managed by ARC and developers must call CFRetain/CFRealease when appropriate.
In the dealloc method, only the reference is released and the listener is unlistened
The dealloc method should never be called actively.
- In the dealloc method, all you need to do is release references to other objects and unsubscribe from the previously subscribed KVO or NSNotificationCenter notifications, and do nothing else.
- If the object holds system resources such as file descriptors, you should write a special method to release such resources. Such classes have a convention with the consumer that they must call the close method when they run out of resources.
- Methods that perform asynchronous tasks should not be called in dealloc; Methods that can only be executed in normal state should also not be called in dealloc because the object is already in the recycling state.
Rule 32: Be aware of memory management issues when writing exception-safe code
- When catching exceptions, it is important to clean up the objects created in the try block.
- By default, ARC does not generate the cleanup code needed to safely handle exceptions. When the compiler flag is turned on, this code is generated, but it makes the application larger and less efficient.
Rule 33: Avoid retaining rings with weak references
- Set some references to weak to avoid “retention rings”.
- Weak references may or may not be automatically cleared. Automatic emptying is a new feature introduced with ARC and implemented by run-time systems. On weak references with auto-cleaning, the data can be read at will because they do not point to objects that have already been reclaimed.
Rule 34: Reduce peak memory with “auto release pool block”
IOS automatically creates threads that, by default, have an automatic release pool that is emptied every time an Event loop is executed. Autofree pool range: open parentheses to close parentheses ({autofree pool range}). Objects in that range will receive a Release message at the end.
- The autorelease pool is arranged in the stack, and when an object receives an AutoRelease message, it is placed in the top pool.
- Using automatic release pools properly can reduce application memory spikes.
- The new @autoreleasepool notation creates a lighter, automatic releasepool.
Rule 35: Debug memory management problems with zombie objects
- When the system recycles an object, it can convert it to a zombie object instead of actually recycling it. You can enable this function by using the environment variable NSZomebieEnabled.
- The system changes the ISA pointer to the object to point to a special zombie class, making the object a zombie object. The zombie class can respond to all selectors by printing a message containing the message content and its recipient, and then terminating the application.
36. Do not use retainCount
- Retention counts for objects may seem useful, but they are not, because an “absolute retention count” at any given point in time does not capture the full picture of an object’s lifetime.
- The retainCount method was officially deprecated after ARC was introduced, and calling it in ARC resulted in an error from the compiler.
Rule 37: Understand the concept of blocks
Return_type (^block_name)(parameters) // Block definition syntax: ^ return_type (parameters){function body}, where 'return_type' and 'parameters' can be omitted. The ^ {}Copy the code
The power of a block is that it can capture all variables in the scope in which it is declared. That is, all the variables in that range are still available in the block. If the variable captured by the block is an object type, it is automatically retained. When a block is defined, the area of memory it occupies is allocated on the stack. That is, a block is only valid in the scope in which it is defined. To solve this problem, send a copy message to the block object to copy it. That way, you can copy blocks from stack to heap. Global block; No state (such as peripheral variables, etc.) is captured, and the runtime does not need any state to participate.
- Blocks are syntactic closures in C, C++, and Objective-C.
- Block can accept arguments and return values.
- Blocks can be allocated on a stack or heap, or they can be global. Blocks allocated on the stack can be copied to the heap so that, like standard Objective-C objects, they have reference counts.
Rule 38: Create a typedef for commonly used block types
//typedef typedef return_type(^block_name)(parameters);Copy the code
- Redefining block types with typedefs makes block variables easier to use.
- When defining a new type, follow existing naming conventions and do not allow the name to conflict with another type.
- You may wish to define multiple type aliases for the same block signature. If the code to be refactored uses an alias for a block type, you only need to modify the block signature in the corresponding typedef, leaving the other typedefs unchanged.
Use handler blocks to reduce code fragmentation
One disadvantage of the delegate pattern is that if the class uses multiple fetchers to download different data, it will have to switch according to which fetchers are passed in the delegate back method.
- When you create an object, you can use an inline handler block to declare the relevant business logic together.
- When you have multiple instances to monitor, if you use delegate mode, you often need to switch based on incoming objects, but if you use handler blocks instead, you can put blocks directly with related objects.
- If you design your API with a handler block, you can add a parameter that allows the caller to determine on which queue the block should be scheduled to execute.
Rule 40: Do not have a retention ring when a block refers to its owning object
- If the object captured by the block directly or indirectly preserves the block itself, then beware of retention rings.
- It is important to find an appropriate time to remove the reserved ring and not pass the buck to the API caller.
Rule 41: Use more queues and less synchronous locks
- Dispatch queues can be used to represent synchronization semantics, which is simpler than using @synchronized blocks or NSLock objects.
- The combination of synchronous and asynchronous dispatch enables the same synchronous behavior as normal locking without blocking the thread that performs the asynchronous dispatch in the queue.
- Using synchronous queues and fence blocks can make synchronous behavior more efficient.
Rule 42: use GCD more than performSelector
// can call method at run time - (id)performSelector:(SEL)selector // can take one argument - (id)performSelector:(SEL)selector withObject:(id)object // take two arguments - (id)performSelector:(SEL)selector withObject:(id)object withObject:(id)object (void)performSelector:(SEL)selector withObject:(id)argument afterDelay:(NSTimeInterval)delay // can be executed in another thread - (void)performSelector:(SEL)selector onThread:(NSThread *)thread withObject:(id)argumentwaitUntilDone:(BOOL)wait
- (void)performSelectorOnMainThread:(SEL)selector withObject:(id)argument waitUntilDone:(BOOL)wait[self performSelector:@selector()doSomething) withObject: nil afterDelay: 5.0]; 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]; }); / / put the task in the main thread executing two ways [self performSlectorOnMainThread: @ the selector (doSomething) withObject:nil waitUntilDone:NO];
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething];
});
Copy the code
- The performSelector family is prone to lapses in memory management. It cannot determine exactly what selector will be executed, so the ARC compiler cannot insert proper memory management methods.
- The performSelector family of methods is very limited in the number of selectors they can handle, both in the type of value they return and in the number of arguments they can send to the method.
- If you want to execute a task on another thread, it is better not to use the performSelector family of methods. Instead, you should wrap the task in a block and call the related methods of GCD to do so.
Rule 43: Know when to use GCD and operation queue
NSOperationQueue, where the developer can put operations in the queue as NSOperation subclasses, and those operations can also be executed concurrently. GCD is a pure C API, while NSOperationQueue is an Objective-C object. The operation queue is used with the NSBlockOperation class with the “addOerationWithBlock:” method of the NSOperationQueue class. The syntax is very similar to that of pure GCD. The NSOperationQueue and NSOperation classes have the following benefits:
- Cancel an operation.
- Specify dependencies between operations.
- Monitor the properties of NSOperation objects through KVO.
- Specify the priority of the operation.
- Reuse NSOperation objects.
- Dispatching queues is not the only solution to solve the problems of multithreading and task management.
- Action queues provide a high-level Objective-C API that implements most of the functionality of pure GCD, as well as more complex operations that require additional code to be implemented using GCD.
Rule 44: Perform tasks based on system resource status through the Dispatch Group mechanism
Dispatch Groups are a GCD feature that allows you to group tasks. The caller can either wait for the set of tasks to complete or continue after providing a callback function, and is notified when the set of tasks is complete.
Dispatch_group_t dispatch_group_create(); Void dispatch_group_asunc(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block); Void dispatch_group_enter(dispatch_group_t group); void dispatch_group_leave(dispatch_group_t group); // Wait for dispatch_group to complete. (timeout can be DISPATCH_TIME_FOREVER, indicating that the function waits for dispatch_group to complete. Long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout); // Wait for dispatch_group. Then run block void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);Copy the code
- A series of tasks can be grouped into a Dispatch group. Developers can be notified when this set of tasks is complete.
- With dispatch groups, multiple tasks can be performed simultaneously in concurrent dispatch queues. The GCD schedules these concurrent tasks based on system resources, which requires a lot of code for developers to implement themselves.
Rule 45: Use dispatch_once to execute thread-safe code that only needs to be run once
void dispatch_once (dispatch_once_t *token, dispatch_block_t block);
+ (id)sharedInstance {
static EOCClass *sharedInstance = nil;
static icdispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Copy the code
Using Dispatch_once simplifies code and is completely thread-safe, with no need to worry about locking or synchronization at all. Because the exact same tag must be used every time you call it, the tag is declared static. Defining this variable in the static scope ensures that the compiler will reuse this variable every time the sharedInstance method is executed without creating a new variable.
- It is often necessary to write “thread-safe code that only needs to be executed once”. This is easily done with the dispatch_once function provided by GCD.
- Tags should be declared in the static or global scope, so that once blocks are passed to the dispatch_once function, the same tags are passed in.
Do not use dispatch_get_CURRENT_queue
- The dispatch_get_CURRENT_queue function often behaves differently from what developers expect. This function is deprecated and should only be used for debugging. 2. The concept of “current queue” cannot be described by a single queue object because distribution queues are organized hierarchically.
- The dispatch_get_CURRENT_queue function is used to resolve deadlocks caused by non-reentrant code, but problems that can be solved with this function can often be solved with queue-specific data instead.
Rule 47: Familiarize yourself with the system framework
A framework is the result of encapsulating a series of code into a dynamic library, in which a header file describes its interface. Sometimes third-party frameworks built for the iOS platform use static libraries because iOS applications are not allowed to include dynamic libraries in them. These things are not really frameworks, strictly speaking, but are often considered frameworks. However, all iOS frameworks still use dynamic libraries. When you develop “applications with a graphical interface” for Mac OS X or iOS, you use a framework called Cocoa, which becomes Cocoa Touch on iOS. Cocoa itself is not a framework, but it contains a number of frameworks that are often used to create applications.
- Many system frameworks can be used directly. The most important of these are Fundation and CoreFoundation, which provide many of the core functions needed to build an application.
- Many common tasks can be done with frameworks. Such as audio and video processing, network communication, data management, etc.
- Remember: Frameworks written in pure C are just as important as those written in Objective-C, and to be a good Objective-C developer, you need to understand the core concepts of C.
Rule 48: Use block enumerations more and use for loops less
//forLoop through NSArray *arr1 = @[@1,@2,@3,@4,@5];for (int i = 0; i < arr1.count; ++i) {
NSLog(@"arr1[i]=%@",arr1[i]); } / /forThe loop iterates backwardsfor (NSInteger i = arr1.count-1; i >= 0; --i) {
NSLog(@"arr1[i]=%@",arr1[i]); } / / NSEnumerator traversing NSArray * arr1 = @ [@ 1, @ 2, @ 3, @ 4, @ 5]; NSEnumerator *enumerator = [arr1 objectEnumerator]; id object;while((object = [enumerator nextObject]) ! = nil) { NSLog(@"object=%@",object); } //NSEnumerator *reverseenu = [arR1 reverseObjectEnumerator]; id object1;while((object1 = [reverseenu nextObject]) ! = nil) { NSLog(@"object1=%@",object1); } // NSArray *arr1 = @[@1,@2,@3,@4,@5];for (NSObject *obj in arr1) {
NSLog(@"obj=%@",obj); } // Fast traversal reverse traversalfor (NSObject *obj1 in [arr1 reverseObjectEnumerator]) {
NSLog(@"obj1=%@",obj1); } // block enumeration NSArray *arr1 = @[@1,@2,@3,@4,@5]; [arr1 enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"idx=%zd,obj=%@",idx,obj); }]; / / piece of enumeration method of reverse traversal [arr1 enumerateObjectsWithOptions: NSEnumerationReverse usingBlock: ^ (id _Nonnull obj, NSUInteger independence idx. BOOL * _Nonnull stop) { NSLog(@"idx=%zd,obj=%@",idx,obj);
}];
Copy the code
- There are four ways to traverse a collection. The most basic method is for loop, followed by NSEnumerator traversal and fast traversal, and the latest and most advanced method is “block enumeration”.
- The block enumeration method itself allows concurrent traversal via GCD without the need to write additional code. This cannot be easily achieved with other traversal methods.
- If you know in advance what objects the collection to be traversed contains, you should modify the block signature to indicate the specific type of object.
Rule 49: Use seamless bridging for collections that customize their memory management semantics
The Objective-C classes in the Foundation framework provide some features that the C data interface in the CoreFoundation framework does not, and vice versa. __bridge: ARC still has ownership of this Objective object. __bridge_retained:ARC surrenders ownership of the object. __bridge_transfer: C is converted to OC
- Seamless bridging allows you to convert objective-C objects in the Foundation framework back and forth to C data structures in the CoreFoundation framework.
- When you create a collection at the CoreFoundation level, you can specify a number of callback functions that indicate how the Colection should handle its elements. This can then be converted into objective-C collections with special memory management semantics using seamless bridging techniques.
Rule 50: Use NSCache instead of NSDictionary when building a cache
- Use NSCache instead of NSDictionary objects when implementing caching. Because NSCache offers elegant auto-delete functionality, it’s “thread-safe,” and unlike dictionaries, it doesn’t copy keys.
- An NSCache object can be capped to limit the total number of objects in the cache and its “total cost,” and these scales define when the cache removes objects from it. But never think of these metrics as reliable “hard limits”; they are only guidelines for NSCache.
- When NSPurgeableData is used with NSCache, it automatically clears data. That is, when an NSPurgeableData object is discarded by the system, the object itself is removed from the cache.
- If the cache is used properly, the corresponding speed of the application can be increased. Only data that is “cumbersome to recalculate” is worth putting in the cache, such as data that needs to be fetched from the network or read from disk.
Simplify the initialize and Load implementation code
When the program starts, classes and classes must invoke the load method only once. The load method of the class is called first, then the load method of the class is called. The load method of the superclass is called first, then the load method of the subclass is called. The loading order of each class cannot be determined. The load method needs to be implemented leaner because the entire application blocks when it is executed. The Initialize method is called only once, before the class is first used by the program. Initialize is lazy-loaded; if a class is not in use, the initialize method is not executed. The Initialize method can be used safely and can call any method in any class. The Initialize method should only be used to set internal data, and no other methods should be called init.
- During the load phase, the system calls the load method if the class implements it. This method can also be defined in a class, where the load method is called first. Unlike the other methods, the LOAD method does not participate in an override mechanism.
- The system sends an Initialize message to a class before it is first used. Since this method follows normal overwrite rules, it is usually in there to determine which class is currently being initialized.
- Global variables that cannot be set by the compiler can be initialized in the initialize method.
Rule 52: Don’t forget that NSTimer keeps its target object
An NSTimer object retains its target, that is, an NSTimer object strongly references the target object. Repeated execution mode: When a task is executed repeatedly, you must call the invalidate method to stop it. The timer can only trigger the task properly if it is placed in a run loop.
- The NSTimer object retains its target until the timer itself expires, which can be invalidated by calling the invalidate method. In addition, one-time timers also expire after a task is triggered to complete.
- A timer that repeatedly executes a task can easily introduce a retention loop, which is guaranteed to result if the target object of such a timer preserves the timer itself. This conservation relationship may occur directly or through other objects in the object diagram.
- You can extend the functionality of NSTimer to break retention rings with “blocks”. However, unless NSTimer provides this functionality in a public interface in the future, you will have to create a category to include the relevant implementation code.