Objective—C常见特性(instancetype和id比较、@property、枚举宏,Enumeration Macros

这么多年过去了,Objective—C不断的成长和进化。虽然核心思想和实践保持不变,但是语言还是产生了标志性的改变和提高。这些现代化的提高体现在类型安全、内存管理、性能和Objective—C的其他方面,这些让我们更加容易的编写正确的代码。

instancetype和id比较

使用instancetype作为关键字的函数返回一个类的实例,这个函数方法里面包含alloc、init和这个类的工厂方法。

使用instancetype代替id会提高代码的类型安全。例如

@interface MyObject : NSObject
+ (instancetype)factoryMethodA;
+ (id)factoryMethodB;
@end

@implementation MyObject
+ (instancetype)factoryMethodA { return [[[self class] alloc] init]; }
+ (id)factoryMethodB { return [[[self class] alloc] init]; }
@end

void doSomething() {
    NSUInteger x, y;

    x = [[MyObject factoryMethodA] count]; // Return type of +factoryMethodA is taken to be "MyObject *"
    y = [[MyObject factoryMethodB] count]; // Return type of +factoryMethodB is "id"
}

因为用instancetype作为返回类型的函数+factoryMethodA,这个消息的类型表示的是Myobject *。因为Myobject类没有-count方法。编译器会再 x 行报出警告:

main.m: ’MyObject’ may not respond to ‘count’

然而,使用id类型的话,表示的是任何一个类型,所以里面可能会包含有-count,编译器就不会报出错误提示。

在子类中,为了确保instancetype的工厂方法有正确的子类行为,当分配一个类的时候,要用[self class]代替直接使用类名。

例如在上面的类的子类如下:

@interface MyObjectSubclass : MyObject
@end

void doSomethingElse() {
        NSString *aString = [MyObjectSubclass factoryMethodA];
}

编译器会给出以下错误提示:

main.m: Incompatible pointer types initializing ’NSString *’ with an expression of type ’MyObjectSubclass *’

How to Adopt如何让代码采用instancetype方式

在你的代码中,用instancetype替代已经存在的id。有代表性的是init方法和类工厂方法。编辑器只会替我们自动把以alloc、init、new开头的方法和返回值为id的方法转换成返回instancetype的方法,但是编辑器不会覆盖其他的方法。Objective-C 中约定明确的为所有方法写上instancetype。

注意:你只需要在返回值的地方用instancetype替换id,er'bu'而不是把所有的id都用instancetype替换。instancetype只能作为函数声明的返回值。这是和id的不同点。

例如:

@interface MyObject
- (id)myFactoryMethod;
@end

应该变成

@interface MyObject
- (instancetype)myFactoryMethod;
@end

还有一种方法,你可以在xcode中使用Objective-C的转换器自动改变你的代码,方法是下面的用xcode重构代码

用xcode重构代码

xcode提供现代Objective-C转换工具,在开发工程中可以辅助我们,它可以识别并应用现代化工具转换代码,但它并不能解释代码的意思。例如:它不会知道你的-toggle方法会影响你的对象的状态,并且它可能会错误的认为这个行为是一个属性。所以凡是自动转换的代码都要手动进行审查和确认。

它会帮我们做这些事情:

  • 在正确的地方把id转换为instancetype
  • 改变enum成为NS_ENUM或NS_OPTIONS
  • 更新@property语法

除了这些,它还会更改你的代码的地方如下:

  • 转换成文字,所以这样的声明[NSNumber numberWithInt:3]成为@3。
  • 使用加下标,所以这样的声明[dictionary setObject:@3 forKey:key]成为dictionary[key]= @3。

使用这种模式,选择Edit > Refactor > Convert to Modern Objective-C Syntax.

属性

使用@property声明属性比使用实例变量带来的好处是:

  • 自动生成getters和setters方法
  • 更好的声明一套方法的意图,这样使得编辑器更好的知道getter和setter方法是干什么的
  • 会提供一些关于行为的信息,因为这样声明的属性会带有assign (vs copy), weak, atomic (vs nonatomic),等等

属性的名字有一套命名规则:属性的getter方法名就是属性的名字本身(例如:属性date的getter方法就是date),属性的setter方法就是在属性名前面加上set前缀(例如:属性date的setter方法就是setDate)。Boolean属性的getter方法以is开头

@property (readonly, getter=isBlue) BOOL blue;

这样做就能像下面这样使用了:

if (color.blue) { }
if (color.isBlue) { }
if ([color isBlue]) { }

枚举宏(Enumeration Macros)

使用NS_ENUM 和 NS_OPTIONS宏定义枚举,可以明确的指定你的宏的类型和大小,,除此之外,这个语法在老的编辑器中也能被识别。

使用NS_ENUM宏定义一组互斥的枚举值:

typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
        UITableViewCellStyleDefault,
        UITableViewCellStyleValue1,
        UITableViewCellStyleValue2,
        UITableViewCellStyleSubtitle
};

NS_ENUM帮助定义名字和类型的列举,这里名字是UITableViewCellStyle,类型是NSInteger。这中类型的枚举应该是NSInteger。

使用NS_OPTIONS定义一个可以组合的值:

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
        UIViewAutoresizingNone                 = 0,
        UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
        UIViewAutoresizingFlexibleWidth        = 1 << 1,
        UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
        UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
        UIViewAutoresizingFlexibleHeight       = 1 << 4,
        UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

这种类型的枚举通常使用NSUInteger。