@Mattt Zihan Xu 🚩🌱

观鸟人将那些某一特定事物的说不清楚的特性称为“Jizz”(我发誓这不是我编造出来的)。

这一表达可以被我们拿来描述资深人士如何只看一眼就从 Go 中区分 Rust ,或者从 Elixir 中区分 Ruby。

但有些东西就像是竖着的酸痛的大拇指:

Perl,它的短小的带有特殊字符的变量名,读起来就像是Q*bert 的脏话。

Lisp,它使用括号之多由那个古老的笑话最能体现,据说俄罗斯在 80 年代为了证明他们成功窃取了一些 SDI 导弹拦截代码的源代码而展示了如下页面:

                )))
              ) )
            ))) ) ))
           )))))
          )))
        ))
      )))) ))
    )))) ))
  )))
)

所以如果我们要对这难以捉摸的 Objective-C 品种观“码”,我们要看些什么?对了,以下就是:

@ 或者 “at” 符号编译器指令对于理解 Objective-C 的格式以及其起源和底层机制非常重要。它是使得 Objective-C 如此强大,具有表现力,并仍能一路编译成底层 C 语言的关键。

它的用途多种多样,用它本身来描述 @ 的含义的唯一转确的说法就是“和 Objective-C 有关的简写符号”。它们涵盖了广泛的实用性却也由晦涩难懂的用法,从主要的用途如 @interface 和 @implementation 到你的整个职业生涯或许都不会遇到的如 @defs 和 @compatibility_alias。

但对于有抱负想成为一个 NSHipster 的人来说,十分熟悉@指令就如同一个音乐爱好者按时间顺序循环披头士的全部歌曲的能力(更重要的是,对它们中的每一首都有及其强烈的看法)。

Interface & Implementation

@interface 和 @implementation 是你学习 Objective-C 时最先学的东西:

你之后才会学习到类别(Category)和类扩展(Class Extension)。

类别通过增加新的类和实例方法来扩展现有类的行为。作为惯例,类别被定义在它们自己的.{h,m}文件里,比如:

MyObject+CategoryName.h

@interface MyObject (CategoryName)
  - (void)foo;
  - (BOOL)barWithBaz:(NSInteger)baz;
@end

MyObject+CategoryName.m

@implementation MyObject (CategoryName)
  - (void)foo {
    // ...
  }

  - (BOOL)barWithBaz:(NSInteger)baz {
    return YES;
  }
@end

类别对于在标准框架类上添加便利函数非常有用(只是不要过分使用你的工具函数)。

小贴士:你可以创建一个定义类似 +appNameDarkGrayColor 类方法的 NSColor/UIColor 调色板类别,而不是胡乱使用随机的,任意的颜色值。之后你可以通过创建方法的别名如 +appNameTextColor 的方式增加一个语义层,新的方法返回 +appNameDarkGrayColor。

扩展看上去很像类别,但是省略了类别名称。这些通常在 @implementation 前定义来指定私有接口,甚至会覆盖 interface 中定义的属性:

@interface MyObject ()
@property (readwrite, nonatomic, strong) NSString *name;
- (void)doSomething;
@end

@implementation MyObject
@synthesize name = _name;

// ...

@end

属性

属性指令也是你会很早学会的知识:

关于属性值得注意的一点是,从 Xcode 4.4 开始,我们再也不需要明确的合成属性了。在 @interface 中被声明的属性在 implementation 中被自动的合成(与前面有下划线的 ivar 名称一起,比如 @synthesize propertyName = _propertyName )。

正向类声明

有的时候,@interface 声明会在属性中引用外部类或者作为参数类型。而不是给每个类添加 #import 语句,在头文件使用前置声明,并且在 implementation 中引入它们是很好的做法。

编译时间更短,循环引用的机会更少;如果你还没有习惯这样做,那你就应该这么做了。

实例变量可见性

类提供状态以及通过属性和方法提供变化的接口而不是直接展示实例变量,这是一个通用的惯例。

尽管 ARC 通过内存管理使得使用实例变量更加安全,但上述的自动属性合成删除了实例变量被声明的地方。

不管怎么说,在实例变量被直接操作的情况下,有以下可见性指令:

@interface Person : NSObject {
  @public
  NSString name;
  int age;

  @private
  int salary;
}

协议

当一个 Objective-C 程序员意识到她可以定义自己的协议时,这就是她的编程进化的转折点。

协议的美好之处在于,它们可以允许程序员设计可在类的层次结构之外采用的合同。这是美国梦的中心,平等的口号:无论你是谁,你从哪里来:只要足够努力任何人都可以获得成功。

…至少这很理想化,不是吗?

没有连接协议的负担,结构的稳定性和表现力简直棒极了。

要求选项

你可以通过指定方法为必须和可选来更深入的定制一个协议。可选的方法在接口上存根,以便能被 Xcode 自动完成但如果方法没有被实现又不生成警告。协议方法默认是必须的。

@required 和 @optional 的语法遵循能见宏:

@protocol CustomControlDelegate
  - (void)control:(CustomControl *)control didSucceedWithResult:(id)result;
@optional
  - (void)control:(CustomControl *)control didFailWithError:(NSError *)error;
@end

处理异常

Objective-C 主要通过 NSError 来沟通意想不到的异常状态。而其他语言使用异常处理,Objective-C 则将异常以及程序员错误降级为真正的异常的行为。

@指令用于 try/catch/finally 块的传统惯例上:

@try{
  // 试图执行下列语句
  [self getValue:&value error:&error];

  // 如果有异常或者被显式抛出...
  if (error) {
    @throw exception;
  }
} @catch(NSException *e) {
  // …在这里处理异常
}  @finally {
  // 总是在@try或@catch block的尾部执行这个
  [self cleanup];
}

常量

常量是一些特定固定值的简短表示。常量或多或少直接影响着程序员的心情。用这个来衡量,Objective-C 长久以来都是程序员的痛苦。

对象常量

不久前,Objective-C 只有 NSString 常量。但随着Apple 的 LLVM 4.0 编译器的发布,让我们欣喜的是NSNumber,NSArray和NSDictionary常量被添加了。

Objective-C 常量

选择器和协议可以作为方法参数。@selector()和@protocol()作为伪常量指令返回一个指向特定选择器(SEL)或协议(Protocol *)的指针。

C 常量

常量也能以别的方式工作,如将 Objective-C 对象转换成 C 值。这些指令能让我们揭开 Objective-C 的神秘面纱,让我们开始了解究竟发生了什么。

你知不知道所有的 Objective-C 的类和对象都只是被美化了的 struct?又或者一个对象的整个身份的关键在于那个 struct 的一个 isa 字段?

对于大多数人来说,至少大多数时间,学习这方面只是仅仅是学术练习。但对于任何在低层最优化冒险的人来说,这仅仅是根本的出发点。

struct {
  @defs(NSObject)
}

编者注:正如读者@secboffin & @ameaijou所指出的,@defs 已经无法在现在的 Objective-C runtime 中使用。

优化

有一些 @ 编译器指令专门用来为常用的优化提供快捷。

兼容性

如果之前的指令对你来说都不新鲜了,那么有很大可能你并不知道这一个:

比如 PSTCollectionView 使用了 @compatibility_alias 来显著提高对 UICollectionView 向后兼容的直接替换的使用体验:

// 允许代码使用 UICollectionView 如同它可以在iOS SDK 5使用一样。
// https://developer.apple.com/legacy/mac/library/#documentation/DeveloperTools/gcc-3.3/gcc/compatibility_005falias.html
#if __IPHONE_OS_VERSION_MAX_ALLOWED < 60000
@compatibility_alias UICollectionViewController PSTCollectionViewController;
@compatibility_alias UICollectionView PSTCollectionView;
@compatibility_alias UICollectionReusableView PSTCollectionReusableView;
@compatibility_alias UICollectionViewCell PSTCollectionViewCell;
@compatibility_alias UICollectionViewLayout PSTCollectionViewLayout;
@compatibility_alias UICollectionViewFlowLayout PSTCollectionViewFlowLayout;
@compatibility_alias UICollectionViewLayoutAttributes     PSTCollectionViewLayoutAttributes;
@protocol UICollectionViewDataSource <PSTCollectionViewDataSource> @end
@protocol UICollectionViewDelegate <PSTCollectionViewDelegate> @end
#endif

只要聪明的使用这些宏的组合,开发者可以通过引入 PSTCollectionView 来开发 UICollectionView --而不需要担心最终项目的部署目标。作为快速替换,同样的代码在 iOS6 中工作起来几乎和在 iOS 4.3 中一样。


回顾:

接口与实现

实例变量可视性

属性

协议

异常处理

对象常量

Objective-C 常量

C 常量

优化

兼容

这就是@不同面孔下的详尽的描述。它是一个多功能的,电力十足的字符,它体现了这门语言的潜在的设计和机制。

这应该是一个完整的清单了,但是我们总有可能忽视一些新的或者长期被遗忘的用途。如果你知道有哪些 @ 指令不在清单里,一定要让 @NSHipster 知道啊。


除非另有声明,本文采用知识共享「署名-非商业性使用 3.0 中国大陆」许可协议授权。