第一章-熟悉Objective-C

第一章主要内容

  • 第一条: 了解Objective-C语言的起源
  • 第二条:在类的头文件中尽量少引入其他头文件
  • 第三条:多用字面量语法,少用与之等价的方法
  • 第四条:多用类型常量,少用#define预处理指令
  • 第五条:用枚举表示状态、选项、状态码

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

首先,Objective-C在C语言的基础上添加了面向对象的特性,该语言采用“消息结构”,而非其他语言的“函数调用”。“消息结构”与“函数调用”的关键区别在于“消息结构”执行的代码由运行环境来决定,而“函数调用”由编译器决定。编译器甚至不关心接收消息对象是何类型(接收消息的对象问题也在运行时处理,其过程为**动态绑定**,第11条详述)。
OC的重要工作由Runtime组件完成,其面向对象特性所需的数据结构和函数都在Runtime组件中。比如Runtime组件中含有全部的内存管理方法。这个Runtime组件本质上是一种与代码相链接的动态库。这种方式使得,只更新Runtime组件就可以提升应用程序性能。而那种工作在 编译期完成的语言,需要重新编译代码(这个地方还是有些不理解,重新编译怎么了。。。)
如果想理解好OC需要理解C语言的内存模型(这个需要搞懂,目前还没有与看,下次再看的时候需要搞懂!),有助于理解OC的内存模型以及引用计数机制的工作原理。若要理解内存模型需要明白,**OC中的指针用来指示对象**。
比如我们声明一个**变量**,令其指代一个**对象**:
    NSString *str = @"string";   //要注意区分,str是变量,@"string"是对象 str变量指向分配在堆中的某块内存
要强调的是,所有的**OC语言的对象都是分配在堆中的**(那block算什么,我得再研究研究),不能在栈中分配OC对象。(细节问题啊,我能说面试的时候考这个了吗。。。要不说细节决定成败呢,唉)
![内存布局](https://img-blog.csdn.net/20160312200808677)
此时的内存布局为上图,NSString中的数据代表字符串实际内容的字节
分配在堆中的内存必须直接管理,而分配在栈上用于保存变量的内存会在栈帧弹出时自动清理。**OC将堆内存管理**(malloc和free)**抽象为一套叫做“引用计数”**(具体见29条)**的内存管理架构**。另外在OC代码中你也会看到不带*的变量,它们可能会使用栈空间。这些变量保存的不是OC对象。比如:CGRect,因为它只是一个C结构体。与创建结构体相比,创建对象有额外的开销,例如分配和释放内存,会影响性能。如果只保存非对象类型,用结构体就可以。


第一条先到这里,之前只是随便看了看,再一次字斟句酌的看时才发现其中的含金量,如果你看到了我的文章,我奉劝你,对于那些大家熟知的好书,要认真的看,如果你明白认真的意思。

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

这一条的核心概念就是如题,尽量不要在头文件中引入其他的头文件。如果你只需要声明一个类,那么你就使用一种叫做“前向声明”的办法,即@class。然后在.m文件中引入其头文件。这种将引入头文件的时机延后的方式,可以避免增加编译时间。同时,也解决了两个类互相引用的问题。
但是,有的时候我们不得不在头文件中引入其他的头文件,例如**你写的类继承自某个超类,就必须引入定义那个超类的头文件**。同理,**如果要声明你写的类遵从某个协议,那么这个协议必须有完整定义,不能使用前向声明,前向声明只能告诉编译器有某个协议,而此时,编译器要知道该协议中定义的方法**。此时有两种途径,一是将该协议放在一个单独的文件中,否则容易产生相互依赖,而且还增加编译时间。二是有些协议,例如委托协议就不用单独写在一个头文件中。此时该协议与接受协议委托的类放在一起定义才有意义。这个时候最好能在实现文件中声明此类实现了该委托协议。
每次导入头文件时思考一下有没有必要。可以用@class来取代,或者如果要实现属性、实例变量或者遵循协议,尽量移至"class-continuation分类"中。这样可以缩减编译时间,降低彼此的依赖程度,即降低耦合度。依赖关系过于复杂会给维护带来麻烦。 

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

这个字面量语法呢就是介个“@”。它可以让你声明一些OC对象时更加简单,减少了代码量,更易读。
NSString
NSString,可以酱 NSString *str = @"123";(这在OC1.0以前做不到哟)
NSNumber
还有用于封装基本数据类型的NSNumber,你可以酱紫操作:
NSNumber *intNum = @1;
NSNumber *floatNum = @1.1f;
NSNumber *intNum = @(3*4);
NSNumber *boolNum = @YES;
NSArray
NSArray *animals = @[@"cat", @"dog"]; //此时要注意,当数组元素中有nil会抛出异常
NSString *dog = animals[1];
下面考虑这样两种情况:
NSArray *A = [NSArray arrayWithObjects:obj1, obj2, obj3, nil];
NSArray *B = [@obj1, @obj2, @obj3];
如果此时obj2为nil,那么A中只会剩下obj1,因为arrayWithObjects:方法会依次处理各个参数知道nil为止。而B会抛出异常。所以使用字面量会使得程序更加安全,因为通常插入nil说明程序有错误。
NSDictionary
通常用NSDictionary *dic = [NSDictionary  dictiionaryWithObjectsAndKeys:@"Matt", @"first",nil];这种先是对象再是key,觉得别扭。不如酱紫
NSDictionary *dic = @{@"first": @"Matt"};
NSString *last = dic[@"first"];
很简单,很清晰。同数组一样,有nil抛异常。
字面量语法的局限性
如果你想使用字面量语法,那么必须是属于Foundation框架。如果自定义了其子类,就无法使用了。
还有就是用字面量语法创建出来的是不可变的,需要酱紫:
NSMutableArray *mutableArray = [@[@obj1, @obj2, @obj3] mutableCopy];

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

如果我们像这样去定义常量
#define ANIMATION_DURATION 0.3
这样没有类型信息,预处理过程会对所有的ANIMATION_DURATION做个替换。假如它定义在头文件中,一旦其他类引入这个头文件,也都会进行替换。所以对于常量我们可以这样定义
static const NSTimeInterval kAnimationDuration = 0.3;
(注意:在这里static 和 const 要一起用,const代表不能修改,static代表该变量仅在此编译单元(即实现文件)可见)
这样我们就可以知道他的常量类型,有助于编写开发文档,更易读,关于常量的命名方法是,若常量局限于某编译单元之内,要在前面加k;若常量在类之外可见,需要以类名为前缀。
(注意:因为OC没有namespace,所以最好不要把常量定义在头文件。如果定义在头文件最好加上类名前缀)
有时候你想对外公开某个常量,比如在类代码中调用NSNotificationCenter以通知他人。派发通知时通过字符串表示通知的名称,这个名称可以为一个外界可见的常值变量,要注意前面要加类名前缀。
// .h extern关键字会告诉编译器全局符号表中有这个符号
extern NSString *const EOCStringConstant;
// .m 这里const代表这个指针变量不能被修改
NSString *const EOCStringConstant = @"VALUE";
由实现文件生成目标文件时,编译器会在**数据段**为字符串分配空间。

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

一般我们在定义状态或者选项的时候可以用枚举。 
enum EOCConnectionState {
    EOCConnectionStateDisconnected;
    EOCConnectionStateDisconnecting;
    EOCConnectionStateConnected;
};
这种方式使得实现枚举所用的数据类型取决于编译器
后期有了些改进,可以指明用何种“底层数据类型”保存枚举类型的变量。这样就可以向前声明枚举变量了,不然因为编译器不清楚底层数据类型大小,就不知道应分配多少空间。
enum EOCConnectionState : NSInteger{ 
    EOCConnectionStateDisconnected = 1;
    EOCConnectionStateDisconnecting;
    EOCConnectionStateConnected;
};
把第一个值设置为1,后面的值会递增1。
在Foundation框架中定义了一些辅助的宏,用这些宏来定义枚举类型。(推荐这种方式),这些宏具备向后兼容的能力,如果目标平台支持新标准,就用新式语法,否则用旧式语法。
枚举状态
typedef NS_ENUM(NSInteger, EOCConnectionState) {
    EOCConnectionStateDisconnected;
    EOCConnectionStateDisconnecting;
    EOCConnectionStateConnected;
};
枚举选项,可以通过或操作,进行多选
typedef NS_OPTIONS(NSInteger, EOCPermittedDirection) {
    EOCPermittedDirectionUp = 1<<0;
    EOCPermittedDirectionDown = 1<<1;
    EOCPermittedDirectionLeft = 1<<2;
    EOCPermittedDirectionRight = 1<<3;
};
如何用宏实现的就先不打出来了。
注意如果枚举需要按或进行相互组合,一定要用OPTIONS,因为C++编译器会默认运算结果的数据类型应该是NSInteger,且不允许隐式转换,就需要自己显式转换。
还有一点需要注意,就是我们在用switch枚举状态时不要加default,因为当我们新加入一种状态时编译器会发出警告,提示新加入状态未在switch分支中处理,加上default就默认处理了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值