[编写高质量iOS代码的52个有效方法](一)Objective-C基础
先睹为快
1.了解objective-c语言的起源
2.在类的头文件中尽量少引入其他头文件
3.多用字面量语法,少用与之等价的方法
4.多用类型常量,少用#define预处理器指令
5.用枚举表示状态、选项、状态码
c语言的起源">第1条:了解objective-c语言的起源
objective-c与c++,java等面向对象语言的区别在于objective-c使用“消息结构”,而不是“函数调用”。objective-c语言是由消息型语言鼻祖smalltalk演化而来。
消息与函数调用之间的语法区别:
// 消息(objective-c) object *obj = [object new]; [obj performwith:parameter1 and:parameter2]; // 函数调用(c++) object *obj = new object; obj->perform(parameter1,parameter2);
关键区别在于:使用消息结构的语言,其运行时所应执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器决定。objective-c的重要工作都由运行时而非编译器完成。
objective-c是c的超集,所以c语言中的所有功能在编写objective-c代码时依然适用。理解c语言的内存模型尤为重要,这有助于理解objective-c的内存模型及其引用计数机制的工作原理。objective-c语言中的指针是用来指示对象的。想要声明一个变量,另其指代某个对象:
nsstring *somestring = @"the string"; nsstring *anotherstring = somestring;
两个变量都是指向nsstring的指针。所有objective-c语言的对象都必须这样声明,因为对象所占内存总是分配在堆中,而不是栈中。不在再栈上分配objective-c对象。而两个变量所占内存都分配在栈上,且两块内存里的值一样,都是nsstring对象的内存地址。
喎? f/ware/vc/"="" target="_blank" class="keylink">vcd4ncjxwpjxjb2rlignsyxnzpq=="hljs javascript">分配在堆中的内存必须直接管理,而分配在栈中用于保存变量的内存则会在其栈帧弹出时自动清理。
在objective-c代码中,有时也会遇到定义里不含*的变量,它们可能会使用栈空间,例如:
cgrect frame;
cgrect是c结构体,整个框架都在使用这种结构体。如果改用objective-c对象来做,需要额外开销,如分配及释放堆内存等。如果只需要保存int、float、double、char等非对象类型,通常使用cgrect这种结构体就可以了。
第2条:在类的头文件中尽量少引入其他头文件
第2条:在类的头文件中尽量少引入其他头文件
与c和c++一样,objective-c也使用头文件与实现文件来区隔代码。
创建一个eocperson类,并在类中用到另一个类eocemployer类的实例
// eocperson.h #import @class eocemployer; @interface eocperson : nsobject @property(nonatomic, copy) nsstring *firstname; @property(nonatomic, copy) nsstring *lastname; @property(nonatomic, strong) eocemployer *employer; @end // eocperson.m #import "eocperson.h" #import "eocemployer.h" @implementation eocperson // 实现方法 @end
在头文件中用
@class eocemployer;语句告诉编译器需要用到这个类,不需要知道该类的全部细节,再在需要知道其所有接口细节的实现文件中用
#import "eocemployer.h"引入eocemployer类。这种做法叫做向前声明。如果直接在头文件中引入eocemployer.h,则会一并引入eocemployer.h中的所有内容,此过程持续下去,则要引入许多根本用不到的内容,增加编译时间,还可能造成循环引用。
但有些时候无法使用向前声明,例如自定义的类继承自某个超类或遵从某个协议。
// eocrectangle.h #import "eocshape.h" #import "eocdrawable.h" @interface eocrectangle : eocshape @property(nonatomic, assign) float width; @property(nonatomic, assign) float height; @end
如果自定义的类继承于某个超类,则必须引入定义那个超类的头文件,如果要遵从某个协议,尽量把该类遵循某协议的声明移到分类()中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入。
第3条:多用字面量语法,少用与之等价的方法
第3条:多用字面量语法,少用与之等价的方法
foundation框架中的nsstring、nsnumber、nsarray、nsdictionary这4个类的实例的声明,即可以用常见的alloc及init方法,也可以直接用字面量语法来声明:
// 将整数、浮点数封入到objective-c对象中,可以用字面量,也可以调用nsnumber中的方法。 // 字面量 nsnumber *intnumber = @1; // 等价方法 nsnumber *intnumber = [nsnumber numberwithint:1]; // 字面量 nsnumber *doublenumber = @3.14159 // 等价方法 nsnumber *doublenumber = [nsnumber numberwithdouble:3.14159]; // 字面量语法也适用于表达式 int x = 5; float y = 6.32f; nsnumber *expressionnumber = @(x * y);
// 使用方法和字面量语法创建数组 nsarray *animals = [nsarray arraywithobjects:@"cat",@"dog",@"mouse",nil]; nsarray *animals = @[@"cat",@"dog",@"mouse"]; // 使用方法和字面量语法获取下标对应的对象 nsstring *dog = [animals objectatindex:1]; nsstring *dog = animals[1];
使用字面量的好处:假如创建一个含有3个对象的数组,第二个对象为nil,其他两个对象都有效,如果用字面量语法创建,则在运行时会抛出异常,可以更快找到错误。而如果用arraywithobjects:方法,则不会抛出异常,但创建的数组会只包含第一个对象,因为该方法会依次处理各个参数,直到发现nil为止。这样会导致错误不容易被发现。
// 使用方法和字面量语法创建字典 nsdictionary *persondata = [nsdictionary dictionarywithobjectandkeys:@"matt",@"firstname",@"galloway",@"lastname",[nsnumber numberwithint:28],@"age",nil]; nsdictionary *persondata = @{@"firstname":@"matt",@"lastname":@"galloway",@"age":@28}; // 使用方法和字面量语法获取下标对应的对象 nsstring *lastname = [persondata objectforkey:@"lastname"]; nsstring *lastname = persondata[@"lastname"];
用字面量语法创建字典也有类似优点,有助于查错。
使用字面量语法创建出来的字符串、数组、字典对象都是不可变的。若想要可变版本的对象,需要复制一份。
nsmutablearray *mutable = [@[@1,@2,@3] mutablecopy];
这么做会多调用一个方法,而且还需要再创建一个对象,但使用字面量语法利大于弊。
第4条:多用类型常量,少用#define预处理器指令
第4条:多用类型常量,少用#define预处理器指令
编写代码时经常要定义常量,例如,要写一个ui视图类,此视图显示出来之后就播放动画,然后消失。如果想将播放动画的时间提取为常量,通常会这么写:
#define animation_duration 0.3
但是这样定义出来的常量没有类型信息,且预处理过程会把碰到的animation_duration一律替换成0.3,假设此指令声明在某个头文件中,那么所有引入这个头文件的代码都会进行替换。更好的方式是定义一个类型为nstimeinterval的常量
// eocanimatedview.h #import @interface eocanimatedview : uiview @end // eocanimatedview.m #import "eocanimatedview.h" // 声明定义常量 static const nstimeinterval eocanimationduration = 0.3; @implementation eocanimatedview @end
变量一定要同时用static于const来声明。const修饰符可以保护变量不被修改。static修饰符则意味着仅在定义此变量的编译单元中可见。如果不加static,在另一个编译单元也声明了同名变量就会报错。这样创建的常量是不公开的。
如果需要对外公开某个常量,就需要常量放在全局符号表中,以便可以在定义该常量的编译单元之外使用:
// eocanimatedview.h #import // 声明常量 extern const nstimeinterval eocanimationduration; @interface eocanimatedview : uiview @end // eocanimatedview.m #import "eocanimatedview.h" // 定义常量 const nstimeinterval eocanimationduration = 0.3; @implementation eocanimatedview @end
此类常量必须要定义,而且只能定义一次。因为要放到全局符号表里,所以命名常量时需谨慎,避免名称冲突。
第5条:用枚举表示状态、选项、状态码
第5条:用枚举表示状态、选项、状态码
枚举是一种常量命名方式。某个对象所经历的各种状态就可以定义为一个简单的枚举集:
// 套接字连接状态 enum eocconnectionstate{ eocconnectionstatedisconnected, eocconnectionstateconnecting, eocconnectionstateconnected, };
编译器会为每个枚举值分配一个独有的编号,从0开始,每个枚举递增1。实现枚举所用的数据类型取决于编译器,不过其二进制位(bit)的个数必须能完全表示下枚举编号才行。
c++11标准扩充了枚举的特性,objective-c也能得益于c++11标准。其中一项改动是:可以指明用何种底层数据类型来保存枚举类型的变量。这样做的好处是,可以向前声明枚举变量了。
// 指定底层数据类型 enum eocconnectionstate : nsinteger {/* . . . */}; // 向前声明枚举变量 enum eocconnectionstate : nsinteger; // 手动指定枚举成员的值,接下来的枚举值都会在上一个的基础上自动递增1 enum eocconnectionstate{ eocconnectionstatedisconnected = 1, eocconnectionstateconnecting, eocconnectionstateconnected, };
还有一种情况应该使用枚举类型,那就是定义选项的时候。若这些选项可以彼此组合,则更应如此:
// 设备支持方向 enum eocpermitteddirection{ eocpermitteddirectionup = 1 << 0, // 0001 上 eocpermitteddirectiondown = 1 << 1, // 0010 下 eocpermitteddirectionleft = 1 << 2, // 0100 左 eocpermitteddirectionright = 1 << 3, // 1000 右 }; // direction枚举值为0101,表示支持上和左两个方向 enum eocpermitteddirection direction = eocpermitteddirectionup|eocpermitteddirectionleft;
使用宏创建枚举类型(ns_enum与ns_options都是foundation框架中定义的辅助宏)
// 普通枚举类型 typedef ns_enum(nsuinteger, eocconnectionstate){ eocconnectionstatedisconnected = 1, eocconnectionstateconnecting, eocconnectionstateconnected, }; // 选项枚举类型 typedef ns_options(nsuinteger, eocpermitteddirection){ eocpermitteddirectionup = 1 << 0, eocpermitteddirectiondown = 1 << 1, eocpermitteddirectionleft = 1 << 2, eocpermitteddirectionright = 1 << 3, };
在switch语句中使用枚举:
typedef ns_enum(nsuinteger, eocconnectionstate){ eocconnectionstatedisconnected, eocconnectionstateconnecting, eocconnectionstateconnected, }; eocconnectionstate state = eocconnectionstateconnected; switch (state) { case eocconnectionstatedisconnected: // handle disconnected state break; case eocconnectionstateconnecting: // handle connecting state break; case eocconnectionstateconnected: // handle connected state break; }
在switch语句中使用枚举时,最好不要有default分支,如果枚举中加入了一个新状态,编译器会发出警告信息提示有状态未在switch语句中处理,假如写上了default分支,那么就会导致编译器不会发出警告信息。通常要确保switch语句能正确处理所有枚举值。
推荐阅读
-
[编写高质量iOS代码的52个有效方法](十)Grand Central Dispatch(GCD)
-
[编写高质量iOS代码的52个有效方法](十一)系统框架
-
[编写高质量iOS代码的52个有效方法](一)Objective-C基础
-
[编写高质量iOS代码的52个有效方法](五)接口与API设计(下)
-
[编写高质量iOS代码的52个有效方法](八)内存管理(下)
-
编写高质量iOS与OSX代码的52个有效方法-第四章:协议与封装
-
Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法(Matt Galloway著)读书笔记(一)
-
编写高质量iOS与OS X代码的52个有效方法 读后感
-
[编写高质量iOS代码的52个有效方法](十)Grand Central Dispatch(GCD)
-
[编写高质量iOS代码的52个有效方法](十一)系统框架