程序猿和程序媛iOS tips

iOS - 采用现代化的Objective-C

2015-08-05  本文已影响497人  Mitchell

说明:本篇文章是作者(Mitchell)参考苹果的 Adopting Modern Objective-C 这篇文章加上自己的总结所翻译,如需转载请注明出处。


一、instancetype###

 @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"
}

因为 factoryMethodA 的返回值类型是instancetype ,返回值的类型被表达为MyObject*,然而MyObject并没有-count的方法,所以编译器在x的行给出了一则警告。

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

然而,factoryMethodB 返回的是一个id 类型的返回值,编译器不能在 y 行给出警告。因为一个 id 类型的对象可能是任何的类,并且一个叫 -count 的方法可能存在于其中某些类中,对于编译器来说这个返回值是有可能去响应这个方法的。
为了确保instancetype的工厂方法有正确的子类化行为,一定要确保在分配类的时候使用的是 [self class] ,而不是直接引用类的名称。遵循这个惯例会确保编译器将正确推断出子类的类型。举例:考虑尝试让 MyObject 的子类做一个这样的事情:

@interface MyObjectSubclass : MyObject
@end
 void doSomethingElse() {
        NSString *aString = [MyObjectSubclass factoryMethodA];
}

这里如果这样写了,编译器会报错:

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

也就是说编译器已经能够识别你的这个类型就是MyObjectSubclass*。但是如果将 instancetype 改成 id ,我们又会发现这个警告将会消失,也就是说编译器不会知道你这个对象的准确类型是什么。

 @interface MyObject
 - (id)myFactoryMethod;
 @end

应该被改为:

```
@interface MyObject
- (instancetype) myFactoryMethod;
```
或者,您可以用现代Objective-C变化器在Xcode自动进行更改您的代码,具体请参考   [Refactoring Your Code Using Xcode](https://developer.apple.com/library/ios/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html#//apple_ref/doc/uid/TP40014150-CH1-SW13) 。

二、属性###

一个Objective-C的属性是由@property语法来定义是公有或者私有。

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

属性捕捉对象的状态。他们反映了对象固有的属性和其他对象们的关系。属性提供了一个安全,方便的方式关联这些属性,而无需编写一组自定义访问器方法(虽然属性允许定制getter、setter方法,如果需要的话)。

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

据此,所有的接下来的工作可以这样:

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

当决定什么可以称为一个属性的时候,请记住以下的几点不可以称为属性:
- init 方法
- copy 方法,mutableCopy方法
- 一个类的工厂方法
- 一个初始化方法并且返回一个BOOL值的方法
- 一个就像getter方法作用的一方面来明确内部状态变化的方法。
除此之外,考虑考虑以下的规则当在你的代码中定义一个潜在的属性:
- readwrite的属性有两个访问器方法,setter带一个参数无返回值。getter没有参数但是有一个返回值。如果你想把这组方法转换成属性,标记它一个readwrite关键词。
- read-only的属性只有一个访问器方法,getter。如果你想把这个方法转换成属性,标记它一个readonly关键词。
- getter方法应该被幂等(如果一个getter被调用两次,第二次的返回的结果应该是和第一次相同的)。然而,这也是可以接受的就是getter每次被调用时都计算结果。

-  (NSColor*)backgroundColor;
 - (void)setBackgroundColor:(NSColor*)color;

然后用带着适合的关键字的@property语法糖去声明它们:

@property(copy)NSColor* backgroundColor;

关于属性关键词的信息和一些其他的注意事项,请看Encapsulating Data


三、枚举、宏###

NS_ENUM和NS_OPTION的宏提供了一个简洁的基于C语言的方式去定义枚举和选项。这两个宏在显式地指定枚举类型和大小的和选项方面提高了代码的完成效率。除此之外,这个语法生命枚举的方式是由老的编程式来准确评估的,由新的编程式来解释潜在的类型信息。

使用 NS_ENUM 宏来定义枚举,一组相互独立的值:

typedef NS_ENUM (NSInteger,UITableViewCellStyle) {
    UITableViewCellStyleDefault,
    UITavleViewCellStyleValue1,
    UITavleViewCellStyleValue2,
    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
};

像枚举一样,NS_OPTIONS宏定义了名称和类型两个值。然而,选项的类型应该是NSUInteger。

enum {
        UITableViewCellStyleDefault,
        UITableViewCellStyleValue1,
        UITableViewCellStyleValue2,
        UITableViewCellStyleSubtitle
};
typedef NSInteger UITableViewCellStyle;

使用 NS_ENUM 语法:

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

当你使用 enum 去定义一组位掩码的时候,可以像下面这样:

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

使用 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
};

四、对象的初始化###

  - (instancetype)init NS_DESIGNATED_INITIALIZER;

五、自动引用计数(ARC)###

上一篇下一篇

猜你喜欢

热点阅读