南宁网站快,邯郸网络科技有限公司哪家好,公司变更名称和经营范围,做seo网站营销推广目录 高质量iOS之熟悉OC
了解OC语言的起源
在类的头文件中尽量少引入其他头文件
多用字面语法#xff0c;少用与之等价的方法
字面数值
字面量数组
字面量字典
局限性
多用类型常量#xff0c;少用#define预处理指令
用枚举表示状态、选项、状态码
高质量iOS之对象…目录 高质量iOS之熟悉OC
了解OC语言的起源
在类的头文件中尽量少引入其他头文件
多用字面语法少用与之等价的方法
字面数值
字面量数组
字面量字典
局限性
多用类型常量少用#define预处理指令
用枚举表示状态、选项、状态码
高质量iOS之对象、消息、运行期
理解”属性“这一概念
属性特质
原子性
读/写权限
内存管理语义
方法名
在对象内部尽量直接访问实例变量
理解“对象等同性”这一概念
特定类的等同性判定方法
等同性判定的执行深度
容器中可变类的等同性
以“类族模式”隐藏实现细节
在既有类中使用关联对象存放自定义数据
理解objc_msgSend的作用
理解消息转发机制
动态方法解析
备援接收者
完整的消息转发
消息转发全流程
用“方法调配技术”调试“黑盒方法”
理解“类对象”的用意 高质量iOS之熟悉OC
了解OC语言的起源
OC语言由Smalltalk演化而来后者是消息型语言的鼻祖使用“消息结构”而非“函数调用”。 两者之间的区别看上去就像上面这样。
关键区别在于使用消息结构的语言其运行时所应执行的代码由运行环境来决定而使用函数调用的语言则由编译器决定。
OC是C的“超集”所以C语言中所有功能在编写OC代码时依然适用。理解C语言的内存模型有助于理解OC的内存模型及其“引用计数”机制的工作原理。
OC语言中的指针是用来指示对象的要声明某个变量令其指代某个对象可用如下语法 这种语法就是照搬C语言的声明了一个指向NSString的指针所有OC对象都必须这样声明因为对象所占内存总是分配在“堆空间”中而不会分配在“栈”上。
分配在堆中的内存必须直接管理而分配在栈上用于保存变量的内存则会在其栈帧弹出时自动清理。OC将对内存管理抽象出来不再需要用malloc及free来分配1或释放内存而是在运行期把这部分工作抽象为一套内存管理架构名叫“引用计数”。
在OC代码中有时遇到定义里不含*的变量它们可能使用“栈空间”所保存的不是OC对象。
在类的头文件中尽量少引入其他头文件
OC与C和C一样也使用”头文件“与”实现文件“来区隔代码。用OC语言编写任何类几乎都需要引入Foudation.h。如果不引入这个文件就要引入与其超类所属框架相对应的“基本头文件”。比如UIViewController的子类的头文件需要引入UIKit.h
#import EOCEmployer.h
当某个类需要声明其他类为属性时以前我们通常会使用上述代码来引入某个类的头文件。
但其实在编译当前类时我们并不需要知道它属性中的类也就是EOCEmployer类的全部细节只要知道有一个类叫做EOCEmployer就好可以采用以下办法
class EOCEmployer;
这叫做“向前声明”该类。
而当前类的实现文件则需引入EOCEmployer类的头文件因为如果要使用EOCEmployer就必须知道其所有接口细节。
这样向前声明不仅可以节约编译时间也解决了两个类互相引用的问题。
但是有时候必须引入其他头文件如果类继承某个超类以及遵从某个协议时必须要引入定义那个超类或协议的头文件。
因为这时协议必须有完整定义要知道该协议中定义的方法。
除了例如“委托协议”的有些协议最好把协议单独放在一个头文件中。
委托协议只有协议和接受协议委托类放在一起定义才有意义此时最好在实现文件中声明此类实现了该委托协议并把这段实现的代码放在分类中。这样只要在实现文件引入包含委托协议的头文件即可
多用字面语法少用与之等价的方法
字面语法是一种更精简声明NSString、NSNumber、NSArray、NSDictionary类的实例的语法使用这种语法可以缩减源代码长度使其更为易读。
字面数值
以下是使用字面量来创建NSNumber的语法
NSNumber *intNumber 1;
NSNumber *floatNumber 2.5f;
NSNumber *doubleNumber 3.14159;
NSNumber *boolNumber YES;
NSNumber *charNumber a;
//等号右边还可以是表达式
int x 5;
float y 6.23f;
NSNumber *expressionNumber (x * y);
字面量数组
使用字面量创建数组
NSArray *animals [cat, dog, mouse, badger];
//还可以使用字面量取下标
NSString* dog animals[1];
使用字面量语法如果元素对象中有nil则会抛出异常。
字面量字典
NSDictionary *personData {firstName:Matt,lastName:Galloway,age:23};
//取键值
NSString *lastName personData[lastName];
与字面量数组相同如果有值为nil便会抛出异常
如果数组和字典可变那么也可以用字面语法来修改元素值。
局限性
字面量不适用于以上类的自定义子类。
并且字面语法创建出来的对象都是不可变的。
多用类型常量少用#define预处理指令
编写代码时经常要定义常量有一种方法是使用#define预处理命令
#define ANIMATION_DURATION 0.3
这种方法一般可以实现想要的效果但是这样定义的常量没有类型信息并且如果使用这种方法预处理会把碰到的所有ANIMATION_DURATION都替换成0.3这样的话假设此指令声明在某个头文件中那么所有引入了这个头文件的代码ANIMATION_DURATION都会被替换。
因此定义常量是更好的选择比如
static const NSTimeInterval kAnimationDuration 0.3
这行代码定义了一个类型为NSTimeInterval的常量
要注意常量名称常用的命名法是若常量局限于某”编译单元““实现文件”之内则在前面加字母k若在类之外可见则也类名为前缀。
定义的位置也很重要不应定义在头文件中如果常量定义在头文件中相当于声明了一个全局变量应该加上前缀来表明所属的类。
如果不打算公开某个常量则应将其定义在使用该常量的实现文件里。
变量一定要同时用static与const来声明。const确保变量不会被修改而static将变量限制在当前编译单元。
如果常量需要公开定义方式有所不同
//header file
extern NSString *const EOCStringConstant
//implementation file
NSString *const EOCStringConstant VALUE;
extern这个关键字可以告诉编译器在全局符号表中有一个名叫EOCStringConstant的符号。
由于要放在全局符号表里所以命名要谨慎。为避免名称冲突最好用与之相关的类名作前缀。
用枚举表示状态、选项、状态码
在以一系列常量来表示错误状态码或可组合的选项时宜使用枚举为其命名。枚举是一种常量命名方式某对象经历的各种状态可以定义为一个简单的枚举集。比如 编译器为每个枚举分配一个独有的编号从0开始每个枚举递增1。
定义枚举的语法如下
enum EOCConnectionState state EOCConnectionStateDisconnected;
//可用typedef关键字定义这样就不用每次敲入enum了
enum EOCConnectionState {EOCConectionStateDisconnected,EOCConectionStateConnecting,EOCConectionStateConnected,
};
typedef enum EOCConnectionState EOCConectionState;
可以指定枚举使用哪种底层数据类型语法如下
enum EOCConnectionStateConnectionState : NSInteger;
//指定底层数据类型为NSInteger
//还可以不使用编译器分配的序号手工指定某个枚举成员对应的值
enum EOCConnectionStateConnectionState {EOCConnectionStateDisconnected 1,EOCConnectionStateConnecting,EOCConnectionStateConnected,
};
定义选项也应使用枚举类型若选项可以彼此组合更应如此。只要枚举定义得对各选项可通过“按位或操作符”来组合。比如
enum UIViewAutoresizing {UIViewAutoresizingNone 0,UIViewAutoresizingFlexibleLeftMargin 1 0,UIViewAutoresizingFlexibleWidth 1 1,UIViewAutoresizingFlexbleRightMargin 1 2,UIViewAutoresizingFlexibleTopMargin 1 3,UIViewAutoresizingFlexibleHeight 1 4,UIViewAutoresizingFlexibleBottomMargin 1 5,
}
这样每个选项都可启用或禁用因为每个枚举值对应的二进制表示中只有一位是1可以用“按位与操作符”判断是否已启用某个选项。
Foundation框架中有一些宏可以用来定义这些枚举类型并指定底层数据类型用法如下
typedef NS_ENUM(NSUInteger, EOCConnectionState) {EOCConnectionStateDisconnected,EOCConectionStateConnecting,EOCConectionStateConnected,
};
typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection) {EOCPermittedDirectionUp 1 0,EOCPermittedDirectionDown 1 1,EOCPermittedDirectionLeft 1 2,EOCPermittedDirectionRight 1 3,
};
需要注意凡是需要以按位或操作来组合的枚举都应使用NS_OPTIONS定义若枚举不需要互相组合则应使用NS_ENUM来定义。
枚举还可以放在switch语句里要注意在switch语句中如果用枚举来定义状态集则最好不要有default分支这样如果加入新的状态会有警告信息。
高质量iOS之对象、消息、运行期
理解”属性“这一概念
属性是一种用property语法来定义的用来封装对象中的数据的变量。
属性特质
属性特质分为四类
原子性
如果具备nonatomic特质则不使用同步锁如不声明nonatomic特质默认为atomic。
读/写权限
readwrite和readonly两个特质分别表示拥有存取方法和只有获取方法
内存管理语义
编译器合成存取方法时要根据此特质来决定生成的代码。 assing “设置方法”只会执行针对“纯量类型”的简单赋值 strong 先保留新值再释放旧值再将新值设置上去 weak 不保留新值不释放旧值与assign类似。属性所指对象摧毁时属性值清空 unsafe_unretained 与assign相同但适用于对象类型摧毁时不清空与weak相反 copy 与strong类似但不保留新值而是将其“拷贝” 方法名
即存取方法
在实现初始化方法时一定遵循属性定义中的语义
在对象内部尽量直接访问实例变量
在对象之外访问实例变量时总是应该通过属性来做而在对象内部推荐的做法是读取实例变量时采取直接访问的形式而设置实例变量时通过属性来做。
有一些特殊情况
1.在初始化方法中总是应该直接访问实例变量。如果待初始化的实例变量声明在超类中子类中无法直接访问则调用设置方法。
2.惰性初始化必须通过“获取方法”来访问属性否则永远不会初始化。
理解“对象等同性”这一概念
对象等同性判定方法与操作符不同比较的是两个指针本身。isEqual方法可以判断对象的等同性某些对象有特殊的“等同性判定方法”。
NSObject协议中有两个用于判断等同性的关键方法
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
默认实现是当且仅当指针值相等时对象才相等。
如果想覆写方法那就要理解其约定
如果isEqual方法判定相等那么hash方法也必须返回同一个值如果hash返回同一个值isEqual方法未必判定相等。
编写hash方法时应该用当前对象做做实验以便减少碰撞频度与降低运算复杂程度之间取舍。
特定类的等同性判定方法
在编写判定方法时应一并覆写isEqual方法实现方式为如果受测的参数与接受该消息的对象都属于一个类就调用自己编写的判定方法否则交给超类来判断。
等同性判定的执行深度
创建等同性判定方法时需要决定是整个对象还是根据其中几个字段。NSArray对比方法是先看对象个数再对比每个对象这叫做“深度等同性判定”。可以创建标识符来帮助判定等同性。
容器中可变类的等同性
在容器中放入可变类对象时放入collection之后就不应再改变其哈希码了。
以“类族模式”隐藏实现细节
“类族”是一种很有用的模式可以隐藏“抽象基类”背后的实现细节。类族中的所有类基于一个基类并且一般不允许直接创建。
判断是否为类族中的类不要检测两个类对象是否等同而是应该采用isKindOfClass:方法。
从公共抽象基类中继承子类时应先阅读开发文档。
如NSArray的类族要新增子类要遵守几条规则
1.子类应继承自类族中抽象基类
2.子类应定义自己的数据存储方式
3.子类应覆写超类文档中指明需要覆写的方法
在既有类中使用关联对象存放自定义数据
关联对象相当于把对象变成字典不同的键对应不同的值。设置关联对象值时通常使用静态全局变量做键。
存储关联对象时可以指明存储策略来维护内存管理语义。
下列方法管理关联对象 理解objc_msgSend的作用
在对象上调用方法用OC术语来说叫“传递信息”。OC中对象收到消息后调用哪个方法于运行期决定因此是一门动态语言。
OC中将方法转变为消息调用的是objc_msgSend这个函数 这个函数会依据接收者与选择子的类型来调用适当的方法。先搜寻方法列表若能找到相符合的就跳至实现代码若找不到就沿着继承体系向上查找找到合适的方法之后再跳转。如果最后还找不到就执行“消息转发”。
理解消息转发机制
消息转发分为两大阶段第一阶段先征询接收者看是否能动态添加方法这叫做“动态方法解析”。第二阶段分为两小步首先查看有没有其他对象可以处理该消息有的话就会转给那个对象。如果没有就启动完整的转发机制吧消息封装到NSInvocation对象中令接收者设法解决当前未处理的这条信息。
动态方法解析
对象收到无法解决的消息后首先调用类方法 使用这种方法的前提是相关方法的实现代码已经写好只等着运行时动态插在类里面。
备援接收者
当前接收者可以处理未知的选择子这一步系统会问接收者是否可以把这条消息装给其他接收者来处理 若找到备援对象就将其返回若找不到就返回nil。
我们无法操作这一步转发的消息若想先修改消息内容再发送就得启用完整的消息转发机制。
完整的消息转发
这里将消息封装在NSInvacation对象中并调用方法来转发信息 消息转发全流程 每一步均有机会处理消息步骤越往后处理消息的代价就越大。
用“方法调配技术”调试“黑盒方法”
可以在运行期改变对象给定的选择子名称对应的方法这被称为“方法调配”。
比如可以交换两个方法实现可以通过下列方法 通过此方案可以为那些完全不知道具体实现的黑盒方法增加日志记录功能有助于程序调试。
理解“类对象”的用意
描述OC对象所用的数据结构定义在运行期程序库的头文件里id类型也定义在这里 该结构体首个成员是Class类变量定义了对象的类称为isa指针。Class对象也定义在这个头文件中 说明Class本身也是OC对象类对象所属的类型是”元类“。每个类仅有一个类对象。
在查询类型信息时尽量使用类型信息查询方法而不要直接比较两个类对象是否等同。