OC运行时及Swift结构体与值类型
OC中的运行时
-
OC本身是一种强类型语言,但其运行时功能让它又有了动态语言的特点。OC中对象的类型和对象所执行的方法都是在运行时阶段进行查找并确认的,这种机制被称为动态绑定。
-
在很多语言,比如 C ,调用一个方法其实就是跳到内存中的某一点并开始执行一段代码。没有任何动态的特性,因为这在编译时就决定好了。而在 Objective-C 中,[object foo] 语法并不会立即执行 foo 这个方法的代码。它是在运行时给 object 发送一条叫 foo 的消息。这个消息,也许会由 object 来处理,也许会被转发给另一个对象,或者不予理睬假装没收到这个消息。多条不同的消息也可以对应同一个方法实现。这些都是在程序运行的时候决定的。
-
Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。
-
OC是C语言的超集,所以OC中面向对象的功能在底层也是使用C语言来实现,是通过引入特有的运行时库,形成一门支持面向对象特性的语言。即一个动态链接库——libobjc.A.dylib。这个动态库里面提供了Objective-C语言所需的各种动态特性。
-
想要支持运行时多态,代码中一定包含有运行时所需的判断数据。Java的运行时信息都在.class文件中按规范格式保留下来;编译后的C++代码也一样保有RTTI,而Objective-C代码中的运行时信息都保留在编译过后的目标文件中(.o)。
-
在OC中,由于动态绑定机制使得程序直到运行时才能清楚那个方法需要被执行,甚至通过使用底层的运行时函数,就可以更改调用的方法或改变方法内部的功能实现,这些特性使得OC成为一门真正的动态语言。
-
objc_msgSend是一个可变参数函数,其中第一个参数代表消息的接收者,第二个参数代表消息的选择器,后续参数表示消息发送时附带的参数。编译器在编译期间就会将发送消息的代码转换为objc_msgSend函数。
-
消息传递
- 在编译时你写的 Objective-C 函数调用的语法都会被翻译成一个 C 的函数调用 - objc_msgSend() 。比如,下面两行代码就是等价的:
[array insertObject:foo atIndex:5]; objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);
- 消息传递的关键藏于 objc_object 中的 isa 指针和 objc_class 中的class dispatch table。objc_object,objc_class以及Ojbc_method。在 Objective-C 中,类、对象和方法都是一个 C 的结构体,从 objc/objc.h 头文件中,我们可以找到他们的定义:
struct objc_object { Class isa OBJC_ISA_AVAILABILITY; }; struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class; const char *name; long version; long info; long instance_size; struct objc_ivar_list *ivars; **struct objc_method_list **methodLists**; **struct objc_cache *cache**; struct objc_protocol_list *protocols; #endif }; struct objc_method_list { struct objc_method_list *obsolete; int method_count; #ifdef __LP64__ int space; #endif /* variable length structure */ struct objc_method method_list[1]; }; struct objc_method { SEL method_name; char *method_types; /* a string representing argument/return types */ IMP method_imp; };
- 发送一条消息也就 objc_msgSend 做了什么事。举 objc_msgSend(obj, foo) 这个例子来说:
- 首先,通过 obj 的 isa 指针找到它的 class ;
- 在 class 的 method list 找 foo ;
- 如果 class 中没到 foo,继续往它的 superclass 中找 ;
- 一旦找到 foo 这个函数,就去执行它的实现IMP .
- Message Forward 消息转发:
- 当消息派发流程最终在对象的类和父类中都没有找到对应选择器的方法时,就会开启消息转发流程。
- 首先,第一步会先调用消息接收者所在类的
resolveInstanceMethod:
方法,该方法返回一个BOOL值,表示是否动态添加一个方法来响应当前消息选择器。如果发送的消息是一个类方法,则会调用另一个类似的方法resolveClassMethod:
。 - 消息接收者所在类重写了
resolveInstanceMethod:
方法并返回YES,也就意味着想要动态添加一个方法来响应当前的消息选择器,可以在重写的方法内使用class_addMethod
函数来为当前类添加方法。 - 如果上面过程中,并没有新方法能响应消息选择器,则会进入消息转发流程的第二步。在第二步中系统会调用当前消息接收者所在类的
forwardingTargetForSelector:
方法,用以询问能否将该条消息发送给其他接收者来处理,方法的返回值就代表这个新的接收者,如果不允许将消息转发给其他接收者则返回nil。 - 如果
forwardingTargetForSelector:
方法的返回值为nil,那么消息转发机制还要继续进行最后一步。在这一步中,系统会将尚未处理的消息包装成一个NSInvocation对象,其内部包含与该消息相关的所有信息,比如消息的选择器、目标接收者、参数等。之后系统会调用消息接收者所在类的forwardInvocation:
方法,并将生成的NSInvocation对象作为参数传入。
- Method Swizzing 方法调配
- OC中方法和消息选择器之间的关系,在运行时头文件中,我们可以找到方法的底层结构定义;
struct objc_method { SEL method_name; char *method_types; IMP method_imp; }
-
可以看出,每一个方法内部都包含三个成员,第一个是选择器代表方法的名字,第二个是方法的类型,其值是一个C语言字符串,可以参考前文讲过类型编码,最后一个是C语言中的函数指针,用以指向方法具体执行的函数。我们可以把方法的内部结构理解为每一个SEL选择器(可以当做是方法名)对应一个具体的IMP函数(可以当做是方法的实现),这也是SEL被称为选择器的原因。这样我们就可以更加清楚地理解消息派发时,系统是如何根据消息选择器来查找对应的方法并跳转到方法的具体实现的了。
-
因为消息选择器和方法实现之间的一对一关系后,我们接下来开始介绍方法调配技术,它其实就是利用运行时提供的函数来动态修改选择器和方法实现之间的对应关系的一种技术。利用这种技术,我们可以在运行时为某个类添加选择器或更改选择器所对应的方法实现
以上参考: 理解OC运行时 Objective-C的对象模型与运行时 Objective-C Runtime
Swift中的结构体及值类型
- 相同点
- Swift中的类和结构体非常类似,都具有定义和使用属性、方法、下标和构造器、扩展、符号协议等面向对象特性。
- 不同点
- 但是结构体不具有继承性、运行时强制类型转换、使用析构器和使用引用计数的多次引用等能力。
- Swift中只有类是引用类型,其他类型全部是值类型(整型、浮点型、布尔型、字符串、元组、集合、枚举以及函数类型、闭包类型)。
- 值类型的优势与使用场景
- 默认情况下,值类型被强制使用或者至少说被鼓励使用在属性上,来使得工作更清晰。
- 值类型是稳定的,值类型不具有行为,它是非常稳定的。虽然其中的一些方法可能会使值类型本身发生改变,但是控制流却还是严格地受控于该实例的唯一所有者。
- 一个典型的值类型对任何外部组件的行为都没有隐式的依赖。它是孤立的。
- 值类型是可交换的,因为每次将值类型赋给一个新变量的时候,该值类型都是被复制的,所以,所有的这些副本都是可交换的。
- 值层 (value layer) 实际上没有状态;它仅仅用来表示和变换数据。
- 对象可以和其他对象通信,但是通常它们发送的是值,而不是引用,除非它们确实想要和外部不可或缺的层创建一个持久的连接。
以上参考了:
结构体和值类型
Swift - 类和结构体
Swift 编程思想,第三部分:结构体和类