扯会淡
这周项目有点忙,又要新版本的提测,又要老版本适配iOS 11,确实没有什么成段的时间可以静下心来好好看看书,只能周日的早上迎着”朝阳“看看书(其实是阴天,冷死,杭州这几天天天下雨,I hate rainy days),最近可能会对于项目中适配iOS 11的东西,写一篇文章(看心情吧,哈哈)。
今天就先对《Effective Objective-C 2.0》的前5条做一个记录和总结。话说这本书好像有很多人推荐,看了一会觉得确实对于代码的细节会有一个深刻的反思,我觉得是本可以细细咀嚼的书。
第一条:了解Objective-C语言的起源
OC是基于C编写的,这个应该大家都知道,所以OC支持c,c++的混编,我个人觉得一个真正厉害的OC程序员,应该对于三者混编应该有自己独到的理解(轻喷)。但是OC跟c++,Java等面向对象的语言有一个很大的不同,就是消息结构机制,而不是函数调用机制。
//Message (OC):
Object *obj = [Object new];
[obj performWith:parameter and:parameter1];
//Function Calling (c++)
Object *obje = new Object;
obj->perform(parameter,parameter1);
这在程序的编译和执行时有很大的区别,最关键的一点区别是:使用消息结构的语言,在运行的时候,怎么执行是由运行环境决定的,就是在编译的时候,根本不关心接收消息的对象是何类型;而使用函数调用的语言,则是由编译器来决定,在编译的时候就已经明确的知道这个函数是谁来执行。
OC的重要工作都依赖于OC独有的一个运行期组件(Runtime Component)。运行期组件本质上就是一种与开发者写的代码链接的动态库(dynamic library),其功能就是把开发者的所有代码粘合起来。
想要理解OC的内存模型以及“引用计数”(reference counting)机制,首先需要明白一个前提:Objective-C语言中的指针是用来指示对象的。就是对象多占内存总是分配在堆中,而指向这个对象的指针是分配在栈中。OC将堆内存管理抽象出来了,不需要用malloc和free来分配或释放对象所占内存。OC运行期环境把这一部分工作抽象为一套内存管理架构,名为“引用计数”。
在OC代码中,有时会遇到不含*的变量,也就是基本数据类型,它们使用的是可能是“栈空间”。比如我们设置frame,经常用到的CGRect,它是个c结构体,
struct CGRect {
CGPoint origin;
CGSize size;
};
typedef struct CGRect CGRect;
OC的整个系统框架,可以看到很多这种结构体,因为相比创建对象来说,节省了分配及释放堆内存的额外开销。如果只需保存基本数据类型,那么通常使用这种c结构体。
第二条:在类的头文件中尽量少引入其他头文件
关于这一点,我想所有的程序员应该多多少少都有体会,如果在类A的头文件中引用类B的头文件,又在类B的头文件中引用类A的头文件,就会造成循环引用。对于这一点OC有一个专门的引用指令#import相比#include指令,虽然不会导致死循环,但这却意味着两个类中有一个类无法被正确编译。
所以正确的做法应该是向前声明,就是只在头文件中声明有另一个类,并不需要知道另一个类的全部细节。就是在类A的头文件中使用@class B;而在实现文件中正确的引入类B#import “B.h”
//A.h
#import <Foundation/Foundation.h>
@class B;
@interface A : NSObject
@property (nonatomic, strong) B *b;
//...
@end
//A.m
#import "A.h"
#import "B.h"
@implementation A
//...
@end
有时无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵循某协议”的这条声明移至“class-continuation分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入。
”class-continuation“分类就是.m文件中的@interface那一块。
第三条:多用字面量语法,少用与之等价的方法
什么是字面量语法?
NSString *string = @"hello world!"; //字符串字面量
NSNumber *intNumber = @1; //字面数值
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.14159;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';
int i = 5;
folat f = 5.3f;
NSArray *array = @[@"cat",@"dog",@"pig"]; //字面量数组
NSString *dog = array[1];
NSDictionary *dic = @{@"firstName":@"tao",@"lastName":@"bingzhi",@"age":@25};
以上都是一些字面量的写法,那么有什么好处呢?很直白的一点是,代码清晰明了易读,没有多余的语法。
同时在声明数组和字典时,如果有一个元素是nil,这样还会报错,而如果用数组或字典的类初始方法创建的话“arrayWithObjects:”会一次处理各个参数,知道发现nil停止,不至于莫名其妙创建好数组之后发现元素少了。
当然也有一定的局限性,除了字符串之外,所创建出来的对象必须属于Foundation框架才行。如果自定义了这些类的子类,则无法用字面量语法创建其对象。还有就是字面量创建出来的字符串、数组和字典都是不可变的(immutable)。如果想变成可变的,需要mutaleCopy以下。
NSMutableArray *mutableArr = [@[@"hh",@"ss",@"tt"] mutableCopy];
第四条:多用类型常量,少用#define预处理指令
写代码时常常需要定义常量,那么怎么定义常量才是正确且合适的,常量定义的位置又有什么讲究呢?
如果我们用#define来预处理一个宽度,可能会这么写
#define kIconWidth 30;
这虽然能解决,但是这样定义出来的常量没有类型,代码不易读,可以使用下面这行代码:
static const CGFloat kIconWidth = 30;
同时还要注意常量名称,常用的:若常量局限于某实现文件内,则在前面加子母“k”;若常量在类之外可见,则通常以类名为前缀。
定义常量的位置也很重要,若不打算公开这个常量,则应将其定义在使用该常量的实现文件里。
这里需要理解一下const和static两个关键字的意思,
const:如果试图修改由const修饰符所声明的变量,那么编译器就会报错,不能被更新;
static:就是限制了变量的可用范围,如果是在实现文件中用static声明了一个变量,那么变量的可用域就是该类所生成的目标文件,如果是在方法内用static声明,那么可用域就是这个代码块。
最主要的一点还是,用这种方法声明常量带有类型信息。
有的时候你需要公开一个常量,就是无需知道这个常量名背后的意义,只是需要用到这个常量名,例如通知名称的公开定义。这种时候,可以将常量放在“全局符号表“中,此时应该这么声明:
//.h
extern NSString *const WLLoginControllerNotification;
//.m
NSString *const WLLoginControllerNotification = @"WLLoginControllerNotification"
;
个人更多的还是在pch文件中,直接写一个通知名static const NSString * WLLoginControllerNotification = @“WLLoginControllerNotification”;
第五条:用枚举表示状态、选项、状态码
在日常开发中,枚举是很好用的一个东西,可以用来表示状态,传递给方法的选项以及状态码等值,需要主要的是,新的C++11标准修订了枚举的一项改动:可以指明用何种“底层数据类型”来保存枚举类型的变量。这样做的好处是可以向前声明枚举变量,编译器在用到枚举的时候,就知道该给枚举变量分配多少内存空间了。
如果把传递给某个方法的选项表示为枚举类型,而多个选项又可以同时用,那么就将各选项值定义为2的幂,以便通过换位或操作将其组合起来。
这里要注意一点:如果想要通过换位或操作的话,定义枚举是就不能用NS_ENUM来定义,而需要使用另一种方式定义,NS_OPTIONS,以便省去类型转换过程。
最后一个小的tip,在处理枚举类型的switch语句中不要事先default分支,这样的话,加入新枚举之后,编译器会提示开发者:switch语句并未处理所有枚举。
总结
今天状态不好,写的时候一直是很赶的情绪,不知道为什么,就没有静下心,很急,很抱歉。可能起太早了,天气又阴沉沉的,还没吃早饭,情绪不太对。sorry sorry。
最后的最后,还是要说,换季了,大家不要感冒,出门上班可以带件外套。希望大家身体健康,吃嘛嘛香。
Better Late Than Never!
努力是为了当机会来临时不会错失机会。
共勉!