Effective Objective-C 2.0 读书笔记 -- 熟悉Objective-C语言

看到Effective这个词,大家一定会想到《Effective C++》、《Effective Java》等业界名著,那些书里汇聚了多项实用技巧,又系统而深入的讲解了各种编程知识。那么,《Effective Objective-C 2.0》也是如此。

作为Mac OS X与iOS应用程序的开发语言,Objective-C作为首选。那么,它有哪些需要注意的呢?

起源

Objective-C与C++、Java一样,是面向对象的语言,是由Smalltalk演化而来。Smalltalk是消息型语言的鼻祖。消息与函数调用之间的区别看上去就像这样:

//Messaging (Objective-C)
Object *obj = [Object new];
[obj performWith:parameter1 and:parameter2];

//Function calling (C++)
Object *obj = new Object;
obj->perform(parameter1, parameter2);

关键区别在于:使用消息结构的语言,其运行时所应执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器决定。

Objective-C是C的“超集”(superset),所以C语言中的所有功能在编写Objective-C代码时依然适用。理解C语言的内存模型(memory model),有助于理解Objective-C的内存模型及其“引用计数”(reference counting)机制的工作原理。Objective-C语言中的指针是用来指示对象的。

关于使用头文件

主要使用 import 关键字。然而,我们在 .h 文件中一般首选使用 @class 关键字,它能“向前声明”一个类。对于不需要知道类细节的情况下我们使用它。否则不会轻易使用 import 来引入整个头文件。

过多的引入头文件,会增加编译时间。这就是我们多使用 @class 关键字的直接原因。

除非确有必要,否则不要引入头文件。一般来说,应在某个类的头文件中使用“向前声明”来提及别的类,并在实现文件中引入那些类的头文件。这样做可以尽量降低类之间的耦合(coupling)。

有时无法使用“向前声明”,比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵循某协议”的这条声明移至“class-continuation分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入。

字面量语法

在编写Objective-C程序时,总会用到某几个类,它们属于Foundation框架。虽然从技术上来说,不用Foundation框架也能写出Objective-C代码,但是实际上却经常要用到此框架。这几个类是NSString、NUNumber、NSArray、NSDictionary。从类名上即可看出各自所表示的数据结构。

Objective-C以语法繁杂而著称。不过从Objective-C 1.0起,有一种简单的方式能创建NSString 对象。这就是“字符串字面量”(string literal),其语法如下:

NSString *string = @"Effective Objective-C 2.0";

字面数值

NSNumber *number = [NSNumber numberWithInt:10];
//等价于
NSNumber *number = @10;

更多表示:

NSNumber *intNumber = @11;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.1415926;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'ABC';

字面量语法也适用于下述表达式

int x =5;
float y = 6.5f
NSNumber *expressionNumber = @(x * y);

字面量数组

NSarray *animals = [NSArray arrayWithObjects:@"cat", @"dog", @"mouse", @"badger", nil];
// 等价于
NSarray *animals = @[@"cat", @"dog", @"mouse", @"badger"];

使用数组

NSString *dog = [animals objectAtIndex:1];
// 等价于
NSString *dog = animals[1];

字面量字典

NSDictionary *personData = [NSDictionary dictionaryWithObjectsAnsKeys:@"Matt", @"firstName", @"Galloway", @"lastName", [NSNumber numberWithInt:28], @"age", nil];
// 等价于
NSDictionary *personData = @{@"firstName":@"Matt", @"lastName":@"Galloway", @"age":[NSNumber numberWithInt:28]};

使用字典

NSString *lastName = [personData objectForKey:@"lastName"];
// 等价于
NSString *lastName = personData[@"lastName"];

可变数组和字典

[mutableArray replaceObjectAtIndex:1 withObject:@"dog"];
[mutableDictionary setObject:@"Galloway" forKey:@"lastName"];
// 等价于
mutableArray[1] = @"dog";
mutableDictionary[@"lastName"] = @"Galloway";

局限性

字面量语法有个小小的限制,就是除了字符串以外,所创建出来的对象必须属于Foundation框架才行。如果自定义了这些类的子类,则无法用字面量语法创建其对象。要想创建自定义子类的实例,必须采用“非字面量语法”(nonliteral syntax)。

使用字面量语法创建出来的字符串、数组、字典对象都是不可变的(immutable)。若想要可变版本的对象,则需要复制一份:

NSMutableArray *mutable = [@[@1, @2, @3, @4] mutableCopy];

这么做会多调用一个方法,而且还要再创建一个对象,不过使用字面量语法所带来的好处还是多于上述缺点的。

用字面量语法创建数组或字典时,若值中有nil,则会抛出异常。因此,务必确保值里不含nil。

多用类型常量 少用#define预处理指令

编写代码时经常要定义常量。掌握了Objective-C与其C语言的基础的人,也许会用这种方法来做:

#define ANIMATION_DURATION 0.3

上述预处理指令会把源代码中的ANIMATION_DURATION字符串替换为0.3.预处理过程会把碰到的所有ANIMATION_DURATION一律替换成0.3,这样的话,假设此指令声明在某个头文件中,那么所有引入了这个头文件的代码,其ANIMATION_DURATION都会被替换。

要解决此问题,应该设法利用编译器的某些特性才对。

static const NSTimeInterval kAnimationDuration = 0.3;

用此方式定义的常量包含类型信息,其好处的清楚地描述了常量的含义。

常用的命名法是:

  • 若常量局限于某”编译单元”(translation unit,也就是“实现文件”,implementation file)之内,则在前面加字母k;
  • 若常量在类之外可见,则通常以类名为前缀。

定义常量的位置很重要。在头文件里声明预处理指令,这样会增加常量名称互相冲突的可能性。

在头文件中使用extern来声明全局常量,并在相关实现文件中定义其值。这种常量要出现在全局符号表中,所以其名称应加以区隔,通常用与之相关的类名做前缀。

枚举使用

枚举只是一种常量命名方式。某个对象所经历的各种状态就可以定义为一个简单的枚举集(enumeration set)。

enum IHConnectionState {
    IHConnectionStateDisconnected,
    IHConnectionStateConnecting,
    IHConnectionStateConnected
};

默认情况下,枚举起始值为0,以后依次递增,1,2,3...

其实还可以我们自己指定枚举值:

enum IHConnectionState {
    IHConnectionStateDisconnected = 1,
    IHConnectionStateConnecting,
    IHConnectionStateConnected
};

也可以定义为位移值:

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

关于枚举,Foundation框架中定义了一些辅助的宏,用这些来定义枚举类型时,也可以指定用于保存枚举值的底层数据类型。

typedef NS_ENUM(NSUInteger, IHConnectionState) {
    IHConnectionStateDisconnected = 1,
    IHConnectionStateConnecting,
    IHConnectionStateConnected
};

typedef NS_OPTIONS(NSUInteger, IHPermittedDirection) {
    IHPermittedDirectionUp = 1 << 0,
    IHPermittedDirectionDown = 1 << 1,
    IHPermittedDirectionLeft = 1 << 2,
    IHPermittedDirectionRight = 1 << 3
};

这些宏的定义如下:

#if(__cplusplus && __cplusplus >= 201103L && (__has_extension(cxx_strong_enums) || __has_feature(objc_fixed_enum))) || (!__cplusplus && __has_feature(objc_fixed_enum))

    #define NS_ENUM(_type, _name)
        enum _name:_type _name; enum _name:_type
    #if (__cplusplus)
        #define NS_OPTIONS(_type, _name)
            type _name; enum:_type
    #else
        #define NS_OPTIONS(_type, _name)
            enum _name:_type _name; enum _name:_type
    #endif
#else
    #define NS_ENUM(_type, _name) _type _name; enum
    #define NS_OPTIONS(_type, _name) _type _name; enum
#endif

第一个#if用于判断编译器是否支持新式枚举。如果不支持,那么就用老式语法来定义枚举。

在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举。

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容