1、类与结构体
类能被继承,结构体不能
类的对象是引用类型,而结构体是值类型,所以类的赋值是传递引用,而结构体则是传递值。
(Swift)可以让一个类的实例反初始化释放存储控件,结构体做不到。
(Swift)由于结构体和枚举都是值类型,所以在默认情况下,实例方法是不可以修改值类型的属性的,而使用mutating之后就可以修改属性的值了。
Tips:相同点
两者都可以扩展,都可以定义属性用来储存值,定义方法用来提供功能,定义下标用于通过下标语法访问值,定义初始化器用于生成初始化值。
2、消息发送、消息转发机制
对于OC执行方法的一句代码[receiver method]来说,用clang将其编译成C代码
就变成了objc_msgSend((id)receiver, sel_registerName("method")),也就是对receiver对象发送消息。首先会根据receiver对象的isa指针获取它对应的class,然后在class的cache中查找method,如果未查找到,那么再到methodLists里查找,如果再没有就到super_class中查找,一直找到rootclass(NSObject)。如果在查找的过程中找到方法,那么根据receiver对象的self指针找到当前对象,调用当前对象的具体实现的方法(IMP),然后传递参数,调用实现方法。当对象收到无法解读的消息的时候,就会启动“消息转发”(message forwarding)机制,程序员可以由此过程告诉对象应该如何处理未知消息。
Tips:关于消息发送时候检索方法的时候,在cache中检索使用二分查找,而在methodLists里查找就只能一步一步遍历了。
消息转发:首先调用resolveInstanceMethod方法(如果是类方法的话,会调用resolveClassMethod)让你为这个方法增加实现。Tips:在类中添加相应函数,然后在resolveInstanceMethod方法中判断传入的sel名称是否跟函数名相等,如果有的话,为class增加方法
class_addMethod(self, sel, (IMP)method, "v@😊; 然后return YES;
如果没有使用上述方案增加Method方法,那么会调用forwardingTargetForSelector,这个方法会返回需要转发消息的对象,直接在方法中指定转发对象即可。
如果也没有在上边方案中转发给别的对象,那么会使用第三种方案:
这里有两个方法:methodsignatureForSelector 用来生成方法签名,forwoardInvocation,其参数NSInvocation 用来调用签名。而class unrecognized selector send to instance的原因就是在这里有两个方法:methodsignatureForSelector方法中未找到实现方法,所以返回了空的方法签名,最终导致NSInvocation无法调用报错,所以我们可以自己新建方法签名,并在forworadInvocation中用要转发的那个对象调用对应的方法签名,就实现了消息转发。在methodsignatureForSelector中判断sel名称是否是要转发的消息,如果是的话,那么return [NSMethodSignature signatureWithObjecTypes:"v@:"]; 在forwardInvocation中获取到方法,然后新建转发对象,最后调用。
SEL selector = [invocation selector];
ReReceiverObject reObject = [ReReceiverObject alloc] init];
if ([reObject responsToSelector:selector]) {[invocation invokeWithTarget:reObject]};
Tips:关于"v@:" 每个方法都会默认隐藏两个参数,self,_cmd, self代表方法调用者,_cmd代表方法的SEL,签名类型就是用来描述这个方法的返回值、参数的,v代表返回值为void,@表示self,:表示_cmd。
自上而下逐级判断,如果中间实现了,那么后边的就不会执行,如果都没有实现的话,那么程序就会报crash。
3、属性关键字
assign 用于修饰基本数据类型和结构体,如果修饰对象的话,当对象销毁的时候,属性值不会自动设置为nil,可能造成野指针。
retain 强引用类型,在ARC下相当于strong。
strong 强引用类型,不能用于修饰block,理由同上。虽然只会有警告,因为block本身和对象一样能retain、release。在其创建的时候,它的内存被分配在栈上,栈的特点就是创建的对象可能随时被销毁,一旦被销毁后再次调用的时候就会造成崩溃,对block进行copy后,会将其内存放入堆区。block变量默认声明为栈变量其作用域属于其创建时候的作用域,为了在外部使用,所以要将其copy到堆。总之,为了声明和实现一致,所以使用copy来修饰block。
weak 使用这个关键字修饰的对象,其引用计数不会+1,有点类似与__unsafe_unretain,不过在对象引用计数为0的时候,weak会将引用对象置为nil , __unsafe_unretain不会,会造成野指针。通常用来修饰不属于强引用的对象的时候使用weak修饰,典型的例子就是代理。
关于如何置为nil,runtime维护有一个Weak哈希表,key是所指对象的地址,value是weak指针的地址数组(该地址保存的是所指对象的地址)。在初始化的时候,runtime会调用objc_iniitWeak函数,初始化一个新的weak指针指向对象的地址。添加引用的时候,objc_initWeak函数会调用objc_storeWeak函数,用来更新指针指向,创建对应的弱引用表。释放的时候,调用clearDellocating函数,首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设置为nil,然后清除表中该条数据,最后清理对象的记录。
4、KVO的底层实现
当观察一个对象的时候,runtime会自动创建继承该对象的类的子类,并重写观察对象的setter方法,该方法会在调用原setter方法的时候通知所有的观察对象该值的修改。最后将该对象的isa指针指向新创建的子类,对象就变成了该子类的实例。
手动出发KVO,手动实现willChangeValueForKey、didChangeValueForKey即可。
Swift中的KVO,继承NSObject的类,或者直接willSet 、didSet实现。
OC知识点
2021-08-24