Objective-C
Objective-C
语言的起源使用 #import "ClassName.h"
可以引入其他文件的所有接口细节。
使用 @class ClassName
“向前声明”(forward declaring),只声明有这个类,没有具体细节,可以解决上述问题。
除非确实有必要,否则不要引入头文件。一般来说,应在某个类的头文件中使用向前声明来提及别的类,并在实现文件中引入那些类的头文件。这样做可以尽量降低类之间的耦合(coupling)。
继承 和 遵从协议 不能使用向前声明。 有时无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵循某协议”的这条声明移至“分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入。
向前声明的作用:
- 防止引入根本用不到的内容,减少头文件细节引用。
- 解决两个类相互引用的问题。
将引入头文件的时机尽量延后,只在确有需要时才引入,这样可以减少类的使用者所需引入头文件的数量。
使用字面量语法(literal syntax)可以缩减源代码的长度,使其更为易读。
#define
预处理指令#define
定义的常量没有类型信息,编译器只会在编译前据此执行查找与替换操作。即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致。
// .h 文件
@interface 类名: 父类名
...
@end
// .m 文件
// 类内使用
static const 类型 常量名 = 常量值;
@implementation 类名
...
@end
// .h 文件
// 类外可用声明
extern 类名 const 常量名;
@interface 类名: 父类名
...
@end
// .m 文件
// 类外可用声明
类名 const 常量名 = 常量;
@implementation 类名
...
@end
常量名称常用命名法是:
k
。switch
语句中不要实现 default
分支,便于加入新枚举后,编译器报错,知道需要修改的地方。==
操作符比较的是两个指针本身,不是其所指的对象。NSObject
协议中声明的 isEqual
方法判断两个对象的等同性。
isEqual
默认实现是:当且仅当其 “指针值” 完全相等时,这两个对象才相等。NSString
: isEqualToString:
NSArray
: isEqualToArray:
NSDIctionary
: isEqualToDictionary:
若比较的对象不是对应的类型,就会抛出异常,崩溃。类族模式:使用继承,实现多种职能的子类,父类通过设定不同的类型来创建某种子类,执行其相应的职能。 作用:将实现细节隐藏在一套简单的公共接口后面。 需要注意的是,创建的实例的真实类型是什么,需要我们知道
新增 Cocoa
中 NSArray
这样的类族的子类,需要遵守以下几条规则:
NSArray
本身只是包在其他隐藏对象外面的壳,它仅仅定义了所有数组都需具备的一些接口。在对象中存放相关信息:
- 从对象所属的类中继承一个子类,然后修改这个子类对象。
- 通过“关联对象”的特性,给某对象关联许多其他对象,这些对象通过“键”来区分。
以给定的键和存储策略为某对象设置关联对象值
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
参数说明:
object
关联的源对象。key
关联的 key
。通常使用静态全局变量做键。value
关联 key
所对应的值。传 nil
可以清除现有的关联。policy
关联的存储策略,也就是对应的内存管理语义,是一个枚举值。objc_AssociationPolicy
枚举值如下表:
| 关联类型 | 等效的 @property 属性 |
| --- | --- |
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic, retain |
OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic, copy |
OBJC_ASSOCIATION_RETAIN | retain |
OBJC_ASSOCIATION_COPY | copy |根据给定的键从某对象中获取对应的关联对象值。
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
移除指定对象的全部关联对象。
objc_removeAssociatedObjects(id _Nonnull object)
注:只有在其他方法都行不通时才考虑使用它。若是滥用,则很快就会令代码失控,使其难于调试。
objc_msgSend
的作用代码:
id returnValue = [someObject messageName: paramter];
代码说明:
someObject
接受者。messageName
选择子。选择子和参数合起来称为“消息”
底层C语言代码实现:
id returnValue = objc_msgSend(someObject, @selector(messageName:), paramter);
原型代码:
void objc_msgSend(id self, SEL cmd, ...)
这是个“参数个数可变函数”,能接受两个或者两个以上的参数。 参数说明:
self
接受者。cmd
选择子(方法的名字)。- 后续参数为消息中的那些参数,顺序不变。
具体实现:
注:OC
方法调用需要很多步骤,较为耗时。objc_msgSend
会将匹配结果缓存在“快速映射表”,每个类都有这样一块缓存。虽然还是不如“静态绑定的函数调用操作”那么迅速,但是也不会慢很多。
边界情况:
objc_msgSend_stret
待发送的消息要返回结构体,就交由此函数处理。objc_msgSend_fpret
待发送的消息要返回浮点数,就交由此函数处理。objc_msgSendSuper
要给超类发消息,就交由此函数处理。如:[super message:parameter]
。
Objective-C
对象的每个方法都可以看做简单的 C
函数,其原型如下:
<return_type> Class_selector(id self, SEL _cmd, ...)
这个原型和 objc_msgSend
函数很像,是为了利用 “尾调用优化” 技术。令 “跳至方法实现” 这一操作跟简单些。
使用范围:某函数的最后一项操作仅仅是调用另一个函数而不会将其返回值另作他用。 步骤:编译器会生成调转至另一函数所需的指令码,不会向调用堆栈中推人新的 “栈帧”。 不优化后果:
Objective-C
方法之前,都需要为调用 objc_msgSend
函数准备 “栈帧”,可以在 “栈踪迹” 中看到。消息转发: 第一阶段:动态方法解析: 征询接收者(所属的类),看其是否能动态添加方法,以处理当前这个 “未知的选择子”。 第二阶段: 1. 备援的接收者: 请接收者看看有没有其他对象(备援的接收者)能处理这条消息。 2. 完整的消息转发机制: 运行期系统会把与消息有关的全部细节都封装到
NSInvocation
对象中,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。
是否能新增一个实例方法来处理选择子,调用方法如下:
// 实例方法
+ (BOOL)resolveInstanceMethod:(SEL)selector
// 类方法
+ (BOOL)resolveClassMethod:(SEL)selector
使用前提:相关方法的实现代码已经写好,只等运行的时候动态插入到类里面。
动态添加方法函数如下:
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
参数说明:
cls
添加方法的类name
被添加方法的名字imp
函数指针,指向待添加的方法(C语言实现)。type
待添加方法的 “类型编码” 。
是否有其他对象处理这条消息,调用方法如下:
- (id)forwardingTargetForSelector:(SEL)selector
若找到备援对象,该方法返回备援对象,反之,返回 nil
。
注意:我们无法操作经由这一步所转发的消息。
创建 NSInvocation
对象,此对象包含 选择子 、目标 及 参数。
消息派发调用方法如下:
- (void)forwardInvocation:(NSInvocation *)invocation
若发现某调用操作不应由本类处理,则向上寻找,直至 NSObject
。如果最后调用了 NSObject
的方法,那么该方法还会继续调用 doesNotRecognizeSelector:
以抛出异常,表明选择子最终未能得到处理。
消息转发全流程如下图:
接收者在每一步中均有机会处理消息。步骤越往后,处理消息的代价就越大。
消息转发代码:https://github.com/AlonerOwl/Runtime/tree/master/Runtime/MessageSend
函数指针(IMP):id (*IMP)(id, SEL, ...)
方法表:函数指针所组成的一个集合。
操作类的方法表:
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
第12条已经说过了。func method_exchangeImplementations(_ m1: Method, _ m2: Method)
参数:两个待交换的方法实现
获取方法实现函数:
Method class_getInstanceMethod(Class cls, SEL name)
这个方法可以为那些 “完全不知道具体实现” 的黑盒方法增加日志记录功能,这非常有助于程序调试。 若是滥用,反而会令代码变的不易读懂且难于维护。
method swizzling代码:https://github.com/AlonerOwl/Runtime/tree/master/Runtime/MethodSwizzling
Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法(Matt Galloway著)读书笔记(一)
原文:https://www.cnblogs.com/gfxxbk/p/12095998.html