码迷,mamicode.com
首页 > 移动开发 > 详细

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

时间:2016-07-21 16:21:58      阅读:265      评论:0      收藏:0      [点我收藏+]

标签:

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

参考书籍:《Effective Objective-C 2.0》 【英】 Matt Galloway

先睹为快

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

目录

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

技术分享

分配在堆中的内存必须直接管理,而分配在栈中用于保存变量的内存则会在其栈帧弹出时自动清理。

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

CGRect frame;

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

第2条:在类的头文件中尽量少引入其他头文件

与C和C++一样,Objective-C也使用头文件与实现文件来区隔代码。

创建一个EOCPerson类,并在类中用到另一个类EOCEmployer类的实例

// EOCPerson.h
#import <Foundation/Foundation.h>

@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 <EOCDrawable>
@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 <UIKit/UIKit.h>

@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 <UIKit/UIKit.h>

// 声明常量
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语句能正确处理所有枚举值。

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

标签:

原文地址:http://blog.csdn.net/sps900608/article/details/51983198

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!