本章主要描述开发过程中一些比较固定的实践技巧,写代码时可以直接套用
instancetype
,不要用id
关于instancetype的介绍参见NSHipster.com。
单例对象应该使用线程安全的模式创建共享的实例。
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
这将会预防有时可能产生的许多崩溃。
每当创建NSString
,NSDictionary
,NSArray
,和NSNumber
类的不可变实例时,都应该使用字面量。要注意nil
值不能传给NSArray
和NSDictionary
字面量,这样做会导致崩溃。
推荐:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
反对:
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];
当访问一个CGRect
的x
,y
,width
,height
时,应该使用CGGeometry
函数代替直接访问结构体成员。苹果的CGGeometry
参考中说到:
All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.
推荐:
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
反对:
CGRect frame = self.view.frame;
CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;
定义常量时,除非明确的需要将常量当成宏使用,否则优先使用const
,而非#define
只在某一个特定文件里面使用的常量,用static
static CGFloat const RWImageThumbnailHeight = 50.0;
当使用enum
时,建议使用新的基础类型规范,因为它具有更强的类型检查和代码补全功能。现在 SDK 包含了一个宏来鼓励使用新的基础类型NS_ENUM
typedef NS_ENUM(NSInteger, NYTAdRequestState) {
NYTAdRequestStateInactive,
NYTAdRequestStateLoading
};
当用到位掩码时,建议使用NS_OPTIONS
类型的宏
typedef NS_OPTIONS(NSUInteger, NYTAdCategory) {
NYTAdCategoryAutos = 1 << 0,
NYTAdCategoryJobs = 1 << 1,
NYTAdCategoryRealState = 1 << 2,
NYTAdCategoryTechnology = 1 << 3
};
私有属性应该声明在类实现文件的延展(匿名的类目)中。有名字的类目(例如ASMPrivate
或private
)永远都不应该使用,除非要扩展其他类。
@interface NYTAdvertisement ()
@property (nonatomic, strong) GADBannerView *googleAdView;
@property (nonatomic, strong) ADBannerView *iAdView;
@property (nonatomic, strong) UIWebView *adXWebView;
@end
Objective-C的布尔值只使用YES
和NO
true
和false
只能用于CoreFoundation,C或C++的代码中
禁止将某个值或表达式的结果与YES
进行比较
因为BOOL被定义成signed char。这意味着除了YES(1)和NO(0)以外,它还可能是其他值。(32位系统)
因此C或C++中的非0为真并不一定就是YES
//以下都是被禁止的
- (BOOL)isBold {
return [self fontTraits] & NSFontBoldTrait;
}
- (BOOL)isValid {
return [self stringValue];
}
if ([self isBold] == YES) {
//...
}
//以下才是赞成的方式
- (BOOL)isBold {
return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
}
- (BOOL)isValid {
return [self stringValue] != nil;
}
- (BOOL)isEnabled {
return [self isValid] && [self isBold];
}
if ([self isBold]) {
//...
}
虽然nil
会被直接解释成NO
,但还是建议在条件判断时保持与nil的比较,因为这样代码更直观。
//比如,更直观的代码
if (someObject != nil) {
//...
}
//没那么直观的代码
if (!someObject) {
//...
}
在C或C++代码中,要注意NULL指针的检测。
向一个nil的Objective-C对象发送消息不会导致崩溃。但由于Objective-C运行时不会处理给NULL指针的情况,所以为了避免崩溃,需要自行处理对于C/C++的NULL指针的检测。
如果某个BOOL
类型的property的名字是一个形容词,建议为getter方法加上一个"is"开头的别名。
@property (assign, getter = isEditable) BOOL editable;
在方法实现中,如果有block参数,要注意检测block参数为nil的情况。
- (void)exitWithCompletion:(void(^)(void))completion {
// 错误。 如果外部调用此方法时completion传入nil,此处会发生EXC_BAD_ACCESS
completion();
// 正确。如果completion不存在则不调用。
if (completion) {
completion();
}
}
如果使用定位,需要在定位完毕之后关闭定位,或者降低定位的频率,不停的定位会消耗电量。
如果使用蓝牙,需要使用的时候再开启蓝牙,用完之后关闭蓝牙,蓝牙也很耗电。
优化算法,减少循环次数,大量循环会让CPU一直处于忙碌状态,特别费电。
尽量不要使用网络轮询(心跳包、定时器),推荐使用推送。
timer的时间间隔不宜太短,满足需求即可。
不要频繁刷新页面,能刷新1行cell
的最好刷新一行,尽量不要reloadData
。
线程适量,不宜过多。
NSDateFormatter
和NSCalendar
。UIImage
加载图片方式一般有两种:A.imagedNamed
初始化、B.imageWithContentsOfFile
初始化二者不同之处在于,imageNamed
默认加载图片成功后会内存中缓存图片,这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象.如果缓存中没有找到相应的图片对象,则从指定地方加载图片然后缓存对象,并返回这个图片对象.而imageWithContentsOfFile
则仅只加载图片,不缓存.大量使用imageNamed
方式会在不需要缓存的地方额外增加开销CPU的时间来做这件事.当应用程序需要加载一张比较大的图片并且使用一次性,那么其实是没有必要去缓存这个图片的,用imageWithContentsOfFile
是最为经济的方式,这样不会因为UIImage
元素较多情况下,CPU会被逐个分散在不必要缓存上浪费过多时间.使用场景需要编程时,应该根据实际应用场景加以区分,UIImage
虽小,但使用元素较多问题会有所凸显。