4. 开发实践

本章主要描述开发过程中一些比较固定的实践技巧,写代码时可以直接套用

初始化

  • 初始化方法的返回类型用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;
}

这将会预防有时可能产生的许多崩溃

字面量

每当创建NSStringNSDictionaryNSArray,和NSNumber类的不可变实例时,都应该使用字面量。要注意nil值不能传给NSArrayNSDictionary字面量,这样做会导致崩溃。

推荐:

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 函数

当访问一个CGRectxywidthheight时,应该使用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
};

私有属性

私有属性应该声明在类实现文件的延展(匿名的类目)中。有名字的类目(例如ASMPrivateprivate)永远都不应该使用,除非要扩展其他类。

@interface NYTAdvertisement ()

@property (nonatomic, strong) GADBannerView *googleAdView;
@property (nonatomic, strong) ADBannerView *iAdView;
@property (nonatomic, strong) UIWebView *adXWebView;

@end

布尔值

  • Objective-C的布尔值只使用YESNO

  • truefalse只能用于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();
        }
    }

Last updated