《Objective-C 2.0编写高质量iOS与OS X代码的52个有效方法》---学习笔记(11-20)

11. 理解objc_msgSend的作用

objc_msgSend,又称为“消息传递”
消息有“名称(name)”、“选择子(selector)”,可以接受参数,而且可能还有返回值

在OC中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。
在底层,所有方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全于运行期决定,甚至可以在程序运行时改变,这些特性使得OC称为一门真正的动态语言。

普通C语言函数的调用(静态绑定):

在这里插入图片描述

使用动态绑定的C语言函数:

在这里插入图片描述
给对象发送消息,可以这样写:
id returnValue = [someObject messageName:parameter];
someObject叫做 接收者(receiver)
messageName:叫做 选择子(selector)
选择子与参数合起来,称为消息(message)

编译器将该语句转换为C语言函数调用,叫做objc_msgSend
void objc_msgSend(id self, SEL cmd, ...)
这是个“参数个数可变的函数”(variadic function),能接受两个或两个以上的参数。

id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);

objc_msgSend函数会依据接收者与选择子的类型来调用适当的方法。
为了完成此操作,该方法需要在接收者所属的类中搜寻其“方法列表”(list of methods)。
如果能找到与选择子名称相符的方法,就跳至其实现代码。
若是找不到,那就沿着继承体系继续向上查找,等找到合适的方法之后再跳转。
如果最终还是找不到相符的方法,那就执行“消息转发”(message forwarding)操作。

objc_msgSend会将匹配结果缓存在“快速映射表”(fast map)里面,每个类都有这样一块缓存,若是稍后还向该类发送与选择子相同的消息,那么执行起来就很快了。

12. 理解消息转发机制

对象在收到无法解读的消息之后,会发生什么情况?

若想令类能理解某条消息,我们必须以程序码实现出对应的方法才行。
但是,在编译期向类发送了其无法解读的消息并不会报错,因为在运行期可以继续向类中添加方法,所以编译器在编译时还无法确知类中到底会不会有某个方法实现。
当对象接收到无法解读的消息后,就会启动“消息转发“(message forwarding)机制,程序员可经由此过程告诉对象应该如何处理未知消息。

消息转发分为两大阶段。
第一阶段先征询接收者,所属的类,看其是否能动态添加方法,以处理当前这个”未知的选择子“(unknown selector),这叫做”动态方法解析“(dynamic method resolution)

第二个阶段涉及”完整的消息转发机制“(full forwarding mechanism)

动态方法解析

对象在收到无法解读的消息后,首先将调用其所属类的下列类方法:
+(BOOL)resolveInstanceMethod:(SEL)selector

如果未实现的方法是类方法,那么运行期会调用:
+(void)resolveClassMethod:(SEL)selector

在继续往下执行转发机制之前,本方法有机会新增一个处理此选择子的方法。

使用这种办法的前提是:相关方法的实现代码已经写好,只等着运行的时候动态插在类里面就可以了
此方案常用来实现@dynamic属性

备援接收者

如果动态方法解析没有处理未知选择子,则系统会将改消息转发给给其他接收者处理
- (id)forwardingTargetForSelector:(SEL)aSelector

其实就是 消息转发

方法参数代表未知的选择子
若当前接收者能找到备援对象,则将其返回;
若找不到,就返回nil;

完整的消息转发

创建NSInvocation对象,把与尚未处理的那条消息有关的全部细节都封装于其中。此对象包含 选择子、目标及参数。
将消息指派给目标对象:
- (void)forwardInvocation:(NSInvocation *)anInvocation

实现此方法时,若发现某调用操作不应由本类处理,则需调用超累的同名方法。
这样,继承体系中的每个类都有机会处理此调用请求,直至NSObject。
如果最后调用了NSObject类的方法,那么该方法会调用==”doesNotRecognizeSelector:“==,抛出异常。

本书介绍的少了一个方法签名

在这里插入图片描述

13. 用“方法调配” 技术调试 “黑盒方法”

方法调配:Method Swizzling

类的方法列表会把选择子的名称映射到相关的方法实现之上,使得”动态消息派发系统“能够据此找到应该调用的方法。
这些方法均以函数指针的形式来表示,这种指针叫做IMP
其原型如下:
id (*IMP) (id, SEL, ...)

在这里插入图片描述

也就是一个选择子,对应一个函数指针
每个类都维护一个这样的表

OC运行期系统提供了几个方法,能够操作这张表,方法包括:
新增选择子、改版选择子对应的方法实现、交换两个选择子所映射到的指针

在这里插入图片描述
这样,无须编写子类,只要修改了”方法表“的布局,就会反映到程序中所有的NSString实例之上。

方法交换

如何交换两个已经写好的方法实现?

可用如下函数实现:
void method_exchangeImplementations(Method m1, Method m2)
其中,参数Method,可以用:
Method class_getInstanceMethod(Class aClass, SEL aSelector)实现

在实际开发中,可以使用该功能,用来为已有方法添加新功能。
还可以在“完全不知道其具体实现的”黑盒方法增加日志记录功能,这非常有助于程序调试。

14. 理解“类对象”的用意

每个OC对象实例,都是指向某块内存数据的指针。
所以在声明变量时,类型后面要跟一个 “*” 字符
NSString *pointerVariable = @"Some string";

id类型本身的定义:

typedef struct objc_object {
	Class isa;
} *id

每个对象结构体的数个成员是Class类的变量。
该变量定义了对象所属的类,通常称为“is a”指针。

Class对象的定义:
在这里插入图片描述
此结构体首个变量也是isa指针,说明Class本身也是OC对象。
super_class,定义了本类的超类(父类)
类对象的类型,是“元类”(metaclass),里面定义了“类方法”

在这里插入图片描述
super_class 指针确立了继承关系,而isa指针描述了实例所属的类。

isMemberOfClass:判断对象是否为某个特定类的实例;
isKindOfClass:判断对象是否为某类或其派生类的实例。

某个对象可能会把其收到的所有选择子都转发给另外一个对象。这样的对象叫做“代理”(proxy),此种对象均以NSProxy为根类。

第三章:接口与API设计

15. 用前缀避免命名空间冲突

OC没有内置的命名空间(namespace)机制。因此,在起名时要避免潜在的命名冲突。

文中提到,在引入第三方库的时候,最好将第三方库的所有名字都加上自己的前缀,感觉大可不必,也没见过将所有文件都改名的

16. 提供“全能初始化方法”

所有对象均要初始化。
我们把这种可为对象提供必要信息以便其能完成工作的初始化方法叫做“全能初始化方法”(designated initializer)

也就是,带参数的初始化

初始化方式可能会不止一种,需要选定一个作为全能初始化方法,令其他初始化方法都来调用它。

如果子类的全能初始化方法 与 超类方法的名称不同,那么总应覆写超类的全能初始化方法。

每个子类的全能初始化方法都应该调用其超类的对应方法,并逐层向上,实现“initWithCoder:”时也要这样,应该先调用超类的相关方法,然后再执行与本类有关的任务。

17. 实现description方法

用以调试使用

在打印日志的字符串时,object对象会收到description消息,该方法所返回的描述信息将取代“格式字符串”(format string)里的“%@”。
在Xcode里面打印的时候,很多自定义对象,打印的时候输出内容为:
object = <NSPerson: 0x7fd9a1600600>
这种信息,除了比较两个指针是否一样的时候,才会有用,其余基本对开发者来说没有任何意义。

如果想打印出有用信息,则可以在NSObject协议里,重写description方法的实现。
比如:

- (NSString *)description {
    return [NSString stringWithFormat: @"<AFImageDownloaderResponseHandler>UUID: %@", [self.uuid UUIDString]];
}

建议:
借助NSDictionary类的description方法。在自定义的description方法中,把待打印的信息放到字典里面,然后将字典对象的description方法所输出的内容包含在字符串里并返回。
例如:
在这里插入图片描述

在NSObject协议中还有个方法,debugDescription,该方法是开发者在调试器中以控制台命令打印对象时才调用。
比如:在Xcode里面使用po 命令打印某一对象

18. 尽量使用不可变对象

设计类的时候,应充分运用属性来封装数据。而在使用属性时,则可以将其声明为“只读”。

例如,可以写成:
@property (nonatomic, copy, readonly) NSString *identifier;
这样就不能修改该属性,原因是,没有set设置方法

问:既然没有set方法,那么为何还需要内存管理语义(copy)

能写成:@property (nonatomic, readonly) NSString *identifier;吗?

可以是可以,但还是建议带上内存管理语义,以后将只读修改为读写的时候,方便修改。

使用readonly修饰的属性,只可以在属性所属类内部赋值
严格来说,外部可以使用KVC修改只读属性

19. 使用清晰而协调的命名方式

方法命名

不要吝于使用长方法名。把方法名起的稍微长一点,可以保证其能准确传达出方法所执行的任务。

清晰的方法名,从左至右读起来好似一段句子。

  • 如果方法的返回值是新创建的,那么方法名的首个词应是返回值的类型。
  • 把表示参数类型的名词放在参数前面。
  • 不要使用str简称,使用string全称

自己老写简称,比如btn、VC,也能看懂,自己也舒服,但,别人看着可能不爽。写成Button、ViewController,大家都没话

  • BOOL属性加is前缀。如果某方法返回非属性的BOOL值,那么应该根据其功能,选用has或is当前缀

类与协议的命名

应在类与协议名称加上前缀,以避免命名空间冲突,并像给方法起名时那样把词句组织好。

继承的子类,要遵守父类的命名习惯。
比如继承UIView的子类,类名末尾的词必须是View

20. 为私有方法名加前缀

将公共方法与私有方法区分出来,便于自己识别。
建议,将私有方法命名为-(void)p_privateMethod{}
不要单用一个下划线做私有方法的前缀,因为这种做法是预留给苹果公司用的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值