I. Class
1. Class name
Class names should be prefixed with three capital letters (double letter prefixes are reserved for Apple's class)
Not just classes, the prefixes of public constants, Protocol, etc. are all the same three capital letters.
When you create a subclass, you should place the descriptive part between the prefix and the parent class name.
For example:
If you have a ZOCNetworkClient class, the subclass name will be ZOCTwitterNetworkClient (note that "Twitter" is between "ZOC" and "NetworkClient"); According to this convention, a subclass of a UIViewController will be ZOCTimelineViewController.
2. Initializer and dealloc
The recommended way to organize the code is to place the dealloc method at the front of the implementation file (directly after @synthesize and @dynamic), and init should follow the dealloc method.
If there are multiple initialization methods, then the specified initialization method should be placed first and the indirect initialization method should be followed.
Nowadays, with ARC, the dealloc method almost does not need to be implemented, but putting init and dealloc together emphasizes that they are a pair. What is usually done in the init method needs to be undoed in the dealloc method.
About specifying the initializer and the secondary initializer
Objective-C has the concept of specifying initializer and indirect initializer. The designed initialization method provides all parameters, the secondary initialization method is one or more, and provides one or more default parameters to call the designed initialization initialization method.
@implementation ZOCEvent
- (instancetype)initWithTitle:(NSString *)title
date:(NSDate *)date
location:(CLLocation *)location
{
self = [super init];
if (self) {
_title = title;
_date = date;
_location = location;
}
return self;
}
- (instancetype)initWithTitle:(NSString *)title
date:(NSDate *)date
{
return [self initWithTitle:title date:date location:nil];
}
- (instancetype)initWithTitle:(NSString *)title
{
return [self initWithTitle:title date:[NSDate date] location:nil];
}
@end
initWithTitle:date:location: is the designed initialization method, and the other two are the secondary initialization method. Because they are simply calling designed initialization methods that call class implementations.
A class should have and only have one designed initialization method, and other initialization methods should call this designed initialization method (with exceptions).
3. There are three different ways to define a new class:
(1) No need to overload any initialization function
(2) Overload designed initializer
(3) Define a new designed initializer
The first method does not require adding any initialization logic of the class, that is, there is no need to override the initialization method of the parent class in the class and no other operations are required.
The second method is to overload the specified initialization method of the parent class. example:
@implementation ZOCViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
// call to the superclass designated initializer
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization (custom initialization process)
}
return self;
}
@end
In this example, ZOCViewController inherits from UIViewController. Here we have some other requirements (such as wishing to assign values to some member variables during initialization), so we need to override the specified initialization method of the parent class initWithNibName:bundle: method.
Note that if this method is not overloaded here, but the parent class's init method is overloaded, then it will be an error.
Because when creating this class (ZOCViewController), the method initWithNib:bundle: will be called, so we overload this method to ensure that the parent class is initialized successfully, and then perform additional initialization operations in this method. However, if the init method is overloaded, the init method will not be called when creating this class (the specified initialization method is called initWithNib:bundle:).
The third way is to provide your own class initialization method, and you should follow the following three steps to ensure correctness:
Define your designed initializer and make sure that the designed initializer of the direct superclass is called.
Overload the designed initializer of the direct superclass. Call your new designed initializer.
Write documents for the new designed initializer.
Many developers ignore the last two steps, which is not only a careless problem, but also violates the rules of the framework and may lead to uncertain behavior and bugs.
Correct example:
@implementation ZOCNewsViewController
- (id)initWithNews:(ZOCNews *)news
{
// call to the immediate superclass's designed initializer (called the designed initializer of the direct superclass)
self = [super initWithNibName:nil bundle:nil];
if (self) {
_news = news;
}
return self;
}
// Override the immediate superclass's designed initializer (overload the designed initializer of the direct parent class)
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
// call the new designated initializer
return [self initWithNews:nil];
}
@end
Many developers will only write the first custom initialization method, without overloading the specified initialization method of the parent class.
In the first custom initialization method, because we want to define our own specified initialization method, at the beginning, we must first call the specified initialization method of the parent class to ensure that the parent class is initialized successfully, so that the ZOCNewsViewController is available. (Because the parent class is created through the specified initialization method initWithNibName:bundle:, we need to call this method of the parent class to ensure that the parent class is initialized successfully). Then assign a value to _news later.
If you just do this, there is a problem. It is also completely legal for the caller to initialize this class by calling initWithNibName:bundle:. If this is the case, then the initWithNews: method will never be called, so _news = news will not be executed, which leads to an incorrect initialization process.
The solution is to overload the specified initialization method of the parent class, and return a new specified initialization method in this method (as done in the example), so that no matter which method is called, it can be successfully initialized.
Indirect initialization method is a method that provides default values, behavior to initialization methods.
You should not have an operation to initialize instance variables in the indirect initialization method, and you should always assume that this method will not get called. What we guarantee is that the only method to be called is the designed initializer.
This means that your secondary initializer should always call the Designated initializer or the self designed initializer you customize (the third case above: custom Designated initializer). Sometimes, due to errors, it may be super, which will cause it to not conform to the initialization order mentioned above.
That is to say, you may see that a class has multiple initialization methods, which is actually a specified initialization method (or multiple, such as UITableViewController, there are several) + multiple indirect initialization methods. These concise initialization methods may do different operations based on different parameters, but essentially call the specified initialization method. Therefore, it is possible that the indirect initialization method is not called, but the specified initialization method will be called (not every one will be called, but the final call must be a specified initialization method). (We can also extend to the above mentioned problem. We can directly override the specified initialization method of the parent class, or customize the initialization method (in this method, you need to use code like self = [super parent class initialization method]), and if it is a custom initialization method, we should also override the initialization method inherited from the parent class to return our custom initialization method...).
In short, if you rewrite the specified initialization method of the parent class, you first need to call the corresponding initialization method of the parent class; if you add a custom specified initialization method, you first call the corresponding initialization method of the parent class in the newly added custom specified initialization method, and then you need to rewrite the specified initialization method of the parent class, and call the custom specified initialization method just added in the overwritten method.
4. Supplement
A class may have multiple specified initialization methods, or it may have only one specified initialization method.
Taking UITableViewController as an example, we can see:
- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
It has three specified initialization methods. We just said that when a subclass inherits from the parent class and overwrites the initialization method, the initialization method of the parent class needs to be called first, but if there are multiple initialization methods of a class, which one needs to be called?
In fact, different creation methods need to call different specified initialization methods.
For example, if we create a UITableViewController in the form of Nib, the last call is the specified initialization method - (instancetype)initWithNibName:(NSString)nibNameOrNil bundle:(NSBundle)nibBundleOrNil; if we create it in the form of a Storyboard, the last call is the specified initialization method - (instancetype)initWithCoder:(NSCoder *)aDecoder. If created in code, the last call is - (instancetype)initWithStyle:(UITableViewStyle)style This specifies initialization method. Therefore, different cases require rewriting different specified initialization methods, and when rewriting, you must first call the corresponding specified initialization method of the parent class (for example, rewriting the initWithCoder: method, then first self = [super initWithCoder:…], which are one-to-one correspondence).
Take the UIViewController as an example. When we create the UIViewController in the form of Nib, the last call is - (instancetype)initWithNibName:(NSString)nibNameOrNil bundle:(NSBundle)nibBundleOrNil, which is the same as the UITableViewController; if we create it in the form of a Storyboard, the last call is - (instancetype)initWithCoder:(NSCoder)aDecoder, which is the same as the UITableViewController; but if we create the UIViewController in the form of code(eg: CYLViewController vc = [[CYLViewController alloc] init]; CYLViewController inherits from UIViewController), so the last call is actually - (instancetype)initWithNibName:(NSString)nibNameOrNil bundle:(NSBundle)nibBundleOrNil, which is different from UITableViewController, because UIViewController does not have - (instancetype)initWithStyle:(UITableViewStyle)style method, so when creating with code, the last call is also the specified initialization method initWithNibName:bundle, and the parameter is automatically set to nil.
So now, let's look at UITableViewController again. When creating it in the code (eg: CYLTableViewController tvc = [[CYLTableViewController alloc] init]; or CYLTableViewController tvc = [[CYLTableViewController alloc] initWithStyle: UITableViewStylePlain]; ), it will call initWithStyle: this method, but if you also implement initWithNibName:bundle: this method, you will find that this method has also been called. Because the UITableViewController inherits from the UIViewController, when created with code, it will eventually use initWithNIbName:bundle: (because this is how UIViewController does it).
So when creating a UITableViewController with code, it will call the two methods initWithNibName:bundle: and initWithStyle:.
2. Attributes
Attributes should be named as descriptively as possible and are named with camels.
About the location of "*":
// recommend
NSString *text;
// Not recommended
NSString* text;
NSString * text;
Note that this habit is different from constants.
static NSString * const ...
You can never use getter and setter methods in init (and other initialization functions), you should access instance variables directly. Remember that an object is only considered to be initialized to a state when init returns.
When using the setter/getter method, try to use dot symbols.
// recommend
= [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
// Not recommended
[view setBackgroundColor:[UIColor orangeColor]];
;
Using dot notation will make the expression clearer and help distinguish between property access and method calls.
Attribute definition
@property (nonatomic, readwrite, copy) NSString *name;
The parameters of the attribute should be arranged in this order: atomicity, read-write and memory management.
When you are used to modify the modifier of a certain attribute, you usually search for the modifier that needs to be modified from the right to the left from the attribute name. The modifiers that are most likely to start modifying these attributes from the far right. According to experience, the possibility of these modifiers being modified from a high level should be: Memory Management > Read and Write Permissions > Atomic Operations
You must use nonatomic unless otherwise required. In iOS, the locks brought by atomic particularly affect performance.
If you want a public getter and a private setter, you should declare the public property as readonly and redefine the common property as readwrite in the class extension.
// in the .h file
@interface MyClass : NSObject
@property (nonatomic, readonly, strong) NSObject *object;
@end
// in the .m file
@interface MyClass ()
@property (nonatomic, readwrite, strong) NSObject *object;
@end
@implementation MyClass
//Do Something cool
@end
If the word describing the BOOL attribute is an adjective, then the setter should not be prefixed, but its corresponding getter accessor should be prefixed.
@property (assign, getter=isEditable) BOOL editable;
Any memory management type that can be used to set with a variable object (such as NSString, NSArray, NSURLRequest) attribute must be copy. (This is what I said in the original text, but what I understand is not absolute. If you do not want the original mutable object to affect the corresponding property of the class, you need to use copy. In this way, when assigning values, the mutable object will first copy and complete a deep copy, and then assign the copied value to the class attributes, so that the influence of the class attribute and the original mutable object does not affect. However, if you want the class attribute to be a strong reference to the original mutable object and point to this mutable object, then strong will be used.)
You should also avoid exposing mutable objects in exposed interfaces, as this allows users of your class to change the class's own internal representation and break the class's encapsulation. You can provide read-only properties to return an immutable copy of your object.
/* .h */
@property (nonatomic, readonly) NSArray *elements
/* .m */
- (NSArray *)elements {
return [ copy];
}
While using lazy loading is good in some cases, it should be thought about before use, as lazy loading usually has some side effects. (But lazy loading is still quite commonly used, such as the following example)
Side effects refer to the fact that when a function is called, in addition to returning the function value, it also has an additional impact on the main calling function. For example, modify global variables (variables outside functions) or modify parameters. Function side effects will cause unnecessary trouble to programming, bring very difficult to find errors to the program, and reduce the readability of the program.
- (NSDateFormatter *)dateFormatter {
if (!_dateFormatter) {
_dateFormatter = [[NSDateFormatter alloc] init];
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[_dateFormatter setLocale:enUSPOSIXLocale];
[_dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:"];//ms are SSS, not SSSSS
}
return _dateFormatter;
}
3. Method
1. Parameter Assertion
Your method may require some parameters to satisfy a specific condition (such as not nil). In this case, it is best to use NSParameterAssert() to assert whether the condition is true or an exception is thrown.
- (void)viewDidLoad
{
[super viewDidLoad];
[self testMethodWithAParameter:0];
}
- (void)testMethodWithAParameter: (int)value
{
NSParameterAssert(value != 0);
NSLog(@"Execute correctly");
}
In this example, if the passed parameter is 0, the program will throw an exception.
2. Private method
Never prefix your private method. This prefix is reserved by Apple. Don't risk reloading Apple's private methods.
Remember this convention when you want to achieve equality: you need to implement both isEqual and hash methods. If two objects are considered equal by isEqual, their hash method needs to return the same value. But if hash returns the same value, it does not ensure that they are equal.
@implementation ZOCPerson
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[ZOCPerson class]]) {
return NO;
}
// check objects properties (name and birthday) for equality
...
return propertiesMatch;
}
- (NSUInteger)hash {
return [ hash] ^ [ hash];
}
@end
You should always use isEqualTo<#class-name-without-prefix#>: to implement an equality check method. If you do this, you will prioritize calling this method to avoid the above type checking.
So a complete isEqual method should look like this:
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[ZOCPerson class]]) {
return NO;
}
return [self isEqualToPerson:(ZOCPerson *)object];
}
- (BOOL)isEqualToPerson:(Person *)person {
if (!person) {
return NO;
}
BOOL namesMatch = (! && !) ||
[ isEqualToString:];
BOOL birthdaysMatch = (! && !) ||
[ isEqualToDate:];
return haveEqualNames && haveEqualBirthdays;
}
4. Category
The category method is prefixed with its own lowercase prefix and underscore. (It's really ugly, but Apple also recommends it)
- (id)zoc_myCategoryMethod
This is very necessary. Because if the same method name is already used in the extended category or other category, it will lead to unpredictable consequences. (The last loaded method will be called)
// recommend
@interface NSDate (ZOCTimeExtensions)
- (NSString *)zoc_timeAgoShort;
@end
// Not recommended
@interface NSDate (ZOCTimeExtensions)
- (NSString *)timeAgoShort;
@end
It is recommended to use Category to group methods according to different functions.
@interface NSDate : NSObject <NSCopying, NSSecureCoding>
@property (readonly) NSTimeInterval timeIntervalSinceReferenceDate;
@end
@interface NSDate (NSDateCreation)
+ (instancetype)date;
+ (instancetype)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs;
+ (instancetype)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti;
+ (instancetype)dateWithTimeIntervalSince1970:(NSTimeInterval)secs;
+ (instancetype)dateWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
// ...
@end
V. NSNotification
When you define your own NSNotification you should define the name of your notification as a string constant, just like other string constants you expose to other classes. You should declare it as extern in the exposed interface file and define it in the corresponding implementation file.
Because you exposed symbols in the header file, you should follow the unified namespace prefix rule and use the class name prefix as the prefix for this notification name. (Usually, constants provided to the outside in the header file need to be prefixed to declare extern + const, and are not defined in the header file, but are defined in the implementation file. If it is not a constant exposed to the outside, it is usually declared as static + const directly in the implementation file, and also prefixed to be defined directly later.)
At the same time, it is also a good practice to name this notification with a verb like Did/Will and the suffix of "Notifications".
//
extern NSString * const ZOCFooDidBecomeBarNotification
//
NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification";