这本书中的内容都是精华,文章也很简洁了,但是我要让他更简洁更易懂,由于我是一个初学者,所以就用我自己能够理解的直白话语总结一下这52个方法,希望再看的时候理解起来不用这么麻烦,好的,马上开始吧!
第一章 熟悉Objective-C
Objective-C(以下简称OC)是C语言的超集,是在C语言的基础上添加了面向对象特性
1. 了解Objective-C的起源
首先,OC是一门面向对象语言,但是语法和其他的面向对象语言有很大差异,运用大量的中括号,这让写习惯其他语言的人感觉很别扭,不过,什么东西都是,习惯就好。
OC使用的是“消息结构”(messaging structure)而不是“函数调用”(function calling)是由Smalltalk演化而来,Smalltalk是消息型语言的鼻祖。好的,那么解释一下什么是消息型语言,函数调用相信大家都理解,消息结构语言,是运行时所执行的代码是由运行环境决定的,而不是由编译器决定的,这就决定了OC是一门动态型语言,也就是人们说的运行时(runtime),这个怎么理解呢,就是你先声明一下你是一个类型,但是具体是什么类型,等真正运行的时候在确定,好了,就到这,说多了我也不懂。
下面说一下OC的内存管理,OC采用引用计数机制,就是你调用一次对象,我就把引用计数加一,销毁一次就减一,当引用计数为0的时候,系统就会把这个对象销毁,现在在ARC环境下,计数是由编译器帮我们管理的,所以我们很幸运,不用太纠结于这个地方。
C语言中*代表的是指针,OC中指针是用来指示对象的,所以声明一个变量语法是:
NSString *aString = @"The string"
OC中对象都是分配在堆内存上的,所以aString这个指针指向堆内存,在堆内存中有一个NSString对象,这也就是说,如果我们再创建一个变量,让其指向同一个内存地址:
NSString *aString = @"The string";
NSString *bString = aString;
这样我们就创建了指向同一个内存地址的两个指针,而不是拷贝该对象,在OC中我们有时候会看到定义一个变量不带*,这种变量有可能使用栈内存,例如CGRect,这种变量保存的不是OC对象,而是系统框架的结构体,创建这种结构体比创建对象节省内存开销。
2. 在头文件中尽量少引入其他头文件
OC编写类的标准方式是文件名做为类名,同事生成一个.m文件和一个.h文件,一般情况下.h文件负责对外提供接口,.m文件负责内部实现,当在一个类中需要引入其他类的时候,我们就要通过#import导入其他头文件,其实有很多时候我们在.h中引入头文件是不需要的,我们只要用@class声明一个类就可以了,我下面用例子说一下这么做的好处,现在有三个类
one.h
two.h
three.h
我们在two类中需要one类,我们可以这样引入
two.h
#import "one.h"
当我们在three类中需要two类的时候我们这样引入
three.h
#import "two.h"
那么问题来了,这样做我们在three类中引入two类的时候间接引入了one类,而在three类中我们可能不需要one类,这样我们在编译的时候会增加编译时间(注意:只是增加编译时间,和运行时间没有关系),虽然这样做不会增加我们的运行时间,但是当我们间接引入过多头文件的时候,编译时间会大大增加,并且影响代码的阅读性,这个时候@class就很好用了,我们只要这样写:
two.h
@class one;
three.h
@class two;
这样就可以避免在three类引入two类的时候间接引入one类了
这里的@class只是声明一个类,而不是把类的所有细节引入,当我们要用的类的属性、方法的时候,我们只要在.m中引入头文件就可以了,这样做可以提高我们的编译效率,并且可以避免循环引用,虽然#import可以避免我们循环引用,但是在a类引用b类同时b类又引用a类的时候还是有一个类无法被正确编译,另外这样做还可以降低类之间的耦合。
有时候我们不得不引入头文件,例如某个类要继承一个父类,这个时候必须引入父类头文件,还有我们要遵从某个协议的时候(比如代理协议),此时我们就必须要引入头文件,来知道细节。
一般我们解决协议的引用,都是把协议放在分类里(class-continuation category)
3. 多用字面量语法
字面量语法是一种“语法糖”,OC中常用的字面量:
字面量类型 | 实现方式 |
---|---|
字符串 | NSString *aString = @"string" |
数值 | NSNumber *aNumber = @1 |
数组 | NSArray *aArray = @[@"object1", @"object2", @"object3"] |
字典 | NSDictionary *aDictionary = @{@"key1":@"value1", @"key2":@"value2", @"key3":@"value3"} |
我们可以看到,字面量语法简洁易懂,字面量语法的另一个好处就是当我们输入的值为nil的时候编译器会提示错误,这样可以确保我们得到想要的字典或数组,如果我们用方法生成数组或字典可能会有不一样的结果,例如:
id object1 = @"string1";
id object2 = nil;
id object3 = @"string3";
NSArray *aArray = [NSArray arrayWithObjects:object1, object2, object3, nil];
这样创建出来的数组实际上只有一个对象,因为object2是nil,如果用字面量创建,会直接报错
字面量的局限性就是创建的数组和字典都是不可变的,还有除了字符串以外,创建的对象都必须属于Foundation框架
4. 多用类型常量,少用#define预处理指令
我们在定义一个常量的时候通常会用#define,例如:
#define kBottomMargin 25
首先说一下这么做坏处,这样定义首先没有描述常量类型,另外如果这样定义在.h文件中,所有引用这个.h文件的代码都会把kBottomMargin替换为25,我们应尽量避免这样定义。
替代方法:
static const NSUInteger kBottomMargin = 25;
这样定义一个常量好处是我们确定了常量类型,并且,当我们试图修改这个常量的时候,编译器会报错,因为有const修饰。
这样定义,我们解决了类型的问题,但是如果放在.h文件中定义的时候,还是无法消除引用的问题,而实际上我们有的时候要把这种常量暴露在外面,供别人使用(例如通知名称),这时候我们可以用另外一种方法:
在.h文件中:
extern NSString *const kNotifictionName;
在.m中
NSString* const kNotifictionName = @"kNotifictionName";
这样就圆满解决了我们的问题,所以我们以后可以放弃用#define定义一个常量了,同时我们也要避免在.h文件中用static const
来定义一个常量
5. 用枚举表示状态、选项、状态吗
枚举的好处相信大家都知道,基本的写法大家也都会,这里提一下大家可能忽略的地方:
- 编译器为枚举值分配独有的编号,从0开始,然而这个初始值我们可以自己设定,只要设定第一个,后面的会自动加1
- 我们可以自行设定保存枚举类型变量的“底层数据类型”
enum buttonState: NSInteger{/* ... */};
- 系统为我们提供了两种宏创建枚举,分别是定义普通枚举的宏
NS_ENUM
和创建可选多项可选类型的枚举宏NS_OPTIONS
例子:
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
};
- switch语句中,若用枚举来定义状态机,最好不要有default分支,这样做是为了确保switch语句能处理所有状态