Objective—C中常见特性
这么多年过去了,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。