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

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

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

先睹为快

1.了解Objective-C语言的起源

2.在类的头文件中尽量少引入其他头文件

3.多用字面量语法,少用与之等价的方法

4.多用类型常量,少用#define预处理器指令

5.用枚举表示状态、选项、状态码

目录

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

    • 先睹为快
    • 目录
    • 第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语句能正确处理所有枚举值。

时间: 2024-11-03 22:01:09

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

[编写高质量iOS代码的52个有效方法](三)消息和运行期

[编写高质量iOS代码的52个有效方法](三)消息和运行期 参考书籍:<Effective Objective-C 2.0> [英] Matt Galloway 先睹为快 11.理解objc_msgSend的作用 12.理解消息转发机制 13.用"方法调配技术"调试"黑盒方法" 14.理解"类对象"的用意 目录 编写高质量iOS代码的52个有效方法三消息和运行期 先睹为快 目录 第11条理解objc_msgSend的作用 第12条理解

[编写高质量iOS代码的52个有效方法](十)Grand Central Dispatch(GCD)

[编写高质量iOS代码的52个有效方法](十)Grand Central Dispatch(GCD) 参考书籍:<Effective Objective-C 2.0> [英] Matt Galloway 先睹为快 41.多用派发队列,少用同步锁 42.多用GCD,少用performSelector系列方法 43.掌握GCD及操作队列的使用时机 44.通过Dispatch Group机制,根据系统资源状况来执行任务 45.使用dispatch_once来执行只需要运行一次的线程安全代码 46.不

[编写高质量iOS代码的52个有效方法](十一)系统框架

[编写高质量iOS代码的52个有效方法](十一)系统框架 参考书籍:<Effective Objective-C 2.0> [英] Matt Galloway 先睹为快 47.熟悉系统框架 48.多用块枚举,少用for循环 49.对自定义其内存管理语义的容器使用无缝桥接 50.构建缓存时选用NSCache而非NSDictionary 51.精简initialize与load的实现代码 52.别忘了NSTimer会保留其目标对象 目录 编写高质量iOS代码的52个有效方法十一系统框架 先睹为快

【iOS】编写高质量iOS代码的52个有效方法

<编写高质量iOS与OS X代码的52个有效方法>的笔记,通读了一遍,感觉印象不是特别深刻,写下笔记记录下吧. 这个数的结构很清晰,5章52条建议,分点介绍了编写高效代码的建议,每点后面有总结,这里简单的记录下这些总结,方便温习. 因时间原因,先写5条,有时间逐渐加进来. 第一章:熟悉oc 1,了解oc起源 oc为C语言添加了面向对象特性,是其超集.oc使用动态绑定的消息结构,也就是说已在运行时才会检查对象类型.接收一条消息之后,究竟应执行何种代码,由运行期环境而非编译器来决定. 理解C语言的

编写高质量JavaScript代码的68个有效方法

简介: <Effective JavaScript:编写高质量JavaScript代码的68个有效方法>共分为7章,分别涵盖JavaScript的不同主题.第1章主要讲述最基本的主题,如版本.类型转换要点.运算符注意事项和分号局限等.第2章主要讲解变量作用域,介绍此方面的一些基本概念,以及一些最佳实践经验.第3章主要讲解函数的使用,深刻解析函数.方法和类,并教会读者在不同的环境下高效使用函数.第4章主要讲解原型和对象,分析JavaScript的继承机制以及原型和对象使用的最佳实践和原则.第5章

Effective Python之编写高质量Python代码的59个有效方法

                                                     这个周末断断续续的阅读完了<Effective Python之编写高质量Python代码的59个有效方法>,感觉还不错,具有很大的指导价值.下面将以最简单的方式记录这59条建议,并在大部分建议后面加上了说明和示例,文章篇幅大,请您提前备好瓜子和啤酒! 1. 用Pythonic方式思考 第一条:确认自己使用的Python版本 (1)有两个版本的python处于活跃状态,python2和pyt

编写高质量Python代码的59个有效方法

作者Brett Slatkin是 Google公司高级软件工程师.他是Google消费者调查项目的工程主管及联合创始人,曾从事Google App Engine的Python基础架构工作,并利用Python来管理众多的Google服务器.Slatkin也是PubSubHubbub协议的联合创始人,还用Python为Google实现了针对该协议的系统.他拥有哥伦比亚大学计算机工程专业学士学位. 精彩书评 "Slatkin所写的这本书,其每个条目(item)都是一项独立的教程,并包含它自己的源代码.

JavaScript手札:《编写高质量JS代码的68个有效方法》(一)(1~5)

编写高质量JS代码的68个有效方法(一) *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* BLOCKS =============================================================================*/ p, blockquote, ul, ol, dl, table, pre { marg

编写高质量JS代码的68个有效方法(三)

[20141030]编写高质量JS代码的68个有效方法(三) *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* BLOCKS =============================================================================*/ p, blockquote, ul, ol, dl, table,