欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

[编写高质量iOS代码的52个有效方法](一)Objective-C基础

程序员文章站 2022-06-02 23:46:11
先睹为快 1.了解objective-c语言的起源 2.在类的头文件中尽量少引入其他头文件 3.多用字面量语法,少用与之等价的方法 4.多用类型常量,少用#define预处理器指令 5.用枚举表示状...

先睹为快

1.了解objective-c语言的起源
2.在类的头文件中尽量少引入其他头文件
3.多用字面量语法,少用与之等价的方法
4.多用类型常量,少用#define预处理器指令
5.用枚举表示状态、选项、状态码

 

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对象的内存地址。

[编写高质量iOS代码的52个有效方法](一)Objective-C基础喎? f/ware/vc/"="" target="_blank" class="keylink">vcd4ncjxwpjxjb2rlignsyxnzpq=="hljs javascript">分配在堆中的内存必须直接管理,而分配在栈中用于保存变量的内存则会在其栈帧弹出时自动清理。

在objective-c代码中,有时也会遇到定义里不含*的变量,它们可能会使用栈空间,例如:

cgrect frame;

cgrect是c结构体,整个框架都在使用这种结构体。如果改用objective-c对象来做,需要额外开销,如分配及释放堆内存等。如果只需要保存int、float、double、char等非对象类型,通常使用cgrect这种结构体就可以了。

第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条:多用字面量语法,少用与之等价的方法

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预处理器指令

编写代码时经常要定义常量,例如,要写一个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条:用枚举表示状态、选项、状态码

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

// 套接字连接状态
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语句能正确处理所有枚举值。

喎?>