《Effective OC》读书笔记(一):熟悉Objectiv

2020-02-23  本文已影响0人  MichealXXX

第一条:了解Objective-C语言起源

OC和许多面向对象的语言类似,但还是有本质上的差别,OC使用了消息结构,而不是函数调用

//Objective-C
Object *obj = [Object new];
[obj performWith:param and:param1];

//c++
Object *obj = new Object;
obj->perform(param,param1)

最关键的区别就是,采用消息结构的语言,其运行时所执行的代码由运行环境来决定,而函数调用的语言,则由编译器决定。函数调用的方法在运行时就已经确定了到底要执行哪个函数实现,而消息结构的语言则是在运行时才去查找要去执行的函数。

Objective-C的重要工作都由运行时组件来完成,而不是编译器,Objective-C的面向对象特性所需的全部数据结构以及函数都在运行时组件里面。

Objective-C语言中的指针是用来指示对象的,想要声明一个变量,令其指代某个对象,就如以下语法:

NSString *someString = @"the string";

它声明了一个名为someString的变量,其类型是NSString *,也就是说此变量是指向NSString的指针,对象所占的内存总是分配在“堆空间”,而不会分配在“”上,someString变量指向分配在堆里的某块内存,其中含有一个NSString对象,如果我们进行以下操作:

NSString *someString = @"the string";
NSString *anotherString = someString;

这两个变量会同时指向此对象,当前栈帧中分配了两块内存,每块内存的大小都能容下一枚指针,这两块内存的值都一样,就是NSString实例的内存地址

OC代码中有时会遇到不含*的变量,它们可能会使用栈空间,这些变量保存的不是OC对象,比如CoreGraphics中的CGRect

CGRect frame;
//CGRect是C结构体
struct CGRect{
  CGPoint origin;
  CGSize size;
}
typedef struct CGRect CGRect;

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

C++一样,OC也是用“头文件”和“实现文件”来区隔代码,如果在一个类中引入了另一个类,则需引入其头文件:

#import "Test.h"

这种方法可行,但不够优雅,如果不需要知道Test类的全部细节,只需要知道有这样一个类就好我们可以使用向前声明:

@class Test;

但在实现文件中,我们需要知道Test文件的详细内容,则需要使用import来引入。将引入头文件的时机尽量延后,只在却有需要的时候才引入,这样就可以减少类的使用者所需引入的头文件数量,引入过多头文件会增加编译时间,获取有很多用不到的内容也被引入了。

有时无法使用向前声明,比如要声明某个类要遵循一项协议,这种情况下就把协议的声明迁移至“class-continuation分类”中。

第三条:多用字面量语法,少用与之等价的方法。

Objective-C1.0开始就可以使用很简单的方法创建NSString对象,这就是“字符串字面量”。

NSString *someString = @"Effective OC 2.0";

不用这种方法就要以常见的allocinit方法来分配并初始化NSString对象了,现在也可以使用这种方式来声明NSNumber,NSArray,NSDictionary类的实例。这种方式可以缩减源码长度,使其更为易读。

有时需要把整数、浮点数、布尔值封装到对象中,这时可以使用NSNumber类,该类可以处理多种类型的数值。

NSNumber *someNumber = [NSNumber numberWithInt:1];

我们还可以更简洁

NSNumber *someNumber = @1;

能够以NSNumber实例表示的所有数据类型都可以使用该语法:

NSNumber *float = @2.5f;
NSNumber *double = @3.1415;
NSNumber *bool = @YES;

数组字典的创建:

NSArray *array = @[@"cat",@"dog"];

NSDictionary *dictionary = @{
    @"name":@"Kevin"
}

另外要注意,使用字面量创建出来的字符串,数组,字典都是不可变的,要是想生成可变对象则需要复制一份:

NSMutableArray *m_Array = [@[@"1",@"2"] mutableCopy];

第四条:多用类型常量,少用#define预处理指令

我们经常会使用以下方式声明常量:

#define ANIMATION_DURATION 0.3

上述指令会把源代码中所有的ANIMATION_DURATION替换为0.3,这可能就是我们想要的结果,不过这样定义出来的常量没有类型信息,加入这个宏被定义在某个头文件中,那么引入这个头文件的代码都会被替换。

有个方法比使用宏定义更好

static const NSTimeInterval kAnimationDuration = 0.3;

这种方式定义的常量包含类型信息,更清楚的描述了常量的定义,变量一定要同时用static const来声明,如果试图修改const修饰符所声明的变量,编译器就会报错,而使用static符号则表示该变量仅在定义此变量的编译单元中可见

如果我们需要定义一个全局可见的的常量,其定义的方式则有所不同,应如下定义:

//header file
extern NSString *const EOCString;

//implement file
NSString *const EOCString = @"VALUE";

const的位置一定要注意,EOCString是一个指针,指向一个NSString对象,我们不希望有人修改它指向其他的NSString对象。extern会告诉编译器在全局符号表中有一个名为EOCString的符号,编译器无需查看其定义就允许使用此常量,这类常量一定要定义,而且只能定义一次。由于是全局可使用,在命名时一定要注意,以免发生冲突。

第五条:用枚举表示状态、选项、状态码

由于Objective-C基于C语言,所以C语言有的功能它都有,其中之一就是枚举类型,系统框架中频繁使用到此类型。

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

enum ConnectionState{
  ConnectionStateDisconnected,
  ConnectionStateConnecting,
  ConnectionStateConnected,
}

每种状态都用一个便于理解的值来表示,所以这样写出来的代码更易于读懂,编译器会为枚举分配一个独有的编号,从0开始,每个枚举递增1,实现枚举所用的数据类型取决于编译器。

然而定义枚举变量的方式缺不太简洁,要依如下语法编写:

enum ConnectionState state = ConnectionStateDisconnected;

我们可以使用typedef关键字重新定义枚举类型,来简化声明:

enum ConnectionState{
  ConnectionStateDisconnected,
  ConnectionStateConnecting,
  ConnectionStateConnected,
};
typedef enum ConnectionState ConnectionState;

这样我们就可以用简写的ConnectionState来代替完整的enum ConnectionState了:

ConnectionState state = ConnectionStateDisconnected;

C++11修改了枚举的某些特性,可以指明用何种“底层数据类型”来保存枚举类型的变量。

enum ConnectionState:NSInteger{
  ConnectionStateDisconnected = 1,
  ConnectionStateConnecting,
  ConnectionStateConnected,
}

上述代码把ConnectionStateDisconnected的值设为1,而不是编译器所分配的0.

还有一种情况应该使用枚举值,那就是定义选项的时候,若这些选型可以彼此组合,则更应该如此,只要枚举定义的对,各选项之间就可以通过按位或操作符来组合:

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

用按位操作符即可判断出是否已经启用某选项:

enum UIViewAutoresizing resizing = UIViewAutoresizingFlexibleWidth| UIViewAutoresizingFlexibleHeight;

系统库中频繁使用这个办法。

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

typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

凡是需要以按位或操作来组合的枚举都应使用NS_OPTIONS定义,若是枚举不需要互相组合,则应使用NS_ENUM来定义。

上一篇 下一篇

猜你喜欢

热点阅读