C/C++ 属于静态的语言;而ObjC属于动态语言。
什么是静态语言?
就是在编译器编译后就调用函数地址,代码结构就固定了,无法在运行的时候改变
什么是动态语言?
该语言将很多静态语言在编译和链接时期做的事放到了运行时来处理,比如: 在运行的时候才知道方法的具体实现在什么地方,程序在运行时可以改变其结构,
什么原因造成了oc 是动态语言?
答案:就是运行时
什么又是运行时?
Objective-C Runtime是一个运行时库, 包含两大组件——编译器和Runtime API ,他们是汇编和c语言编写的, 运行时为c语言提供了面向对象的功能,进而创造了OC 这门语言[ OC运行时本质上就是给OC面向对象编程提供了可能 ] , 也就是说运行时需要提供加载对象, 方法分发/转发等功能
一: runtime编译器
作用: 把任何 Objective-C 的代码编译为 Runtime 的 C 代码。这个过程中,会把 Objective-C 的数据结构编译为 Runtime 的 C 的数据结构。把 Objective-C 的消息传递编译为 Runtime 的 C 函数调用。
下面看一下Objective_c中各种元素在Runtime 中对应的是什么类型【数据结构】?
1: 对象
Runtime 的核心数据结构就是对象,对象在 Runtime 中是一个名为 objc_object 的结构体。也就是说Objective-C 中的任何对象都是用 objc_object 来表示的。
struct objc_object {
private:
isa_t isa;
public:
// ...
}
objc_object 中最关键的部分是一个 isa_t 类型的 isa 变量。isa_t 类型使用了 Tagged Pointer 技术来减小内存空间的占用。isa 指针主要保存了对象关联的类的信息。
id 的定义:
typedef struct objc_object *id;
id 是一个指向 objc_object 类型的指针,因此 id 可以代表任何对象。
2: 类
类在runtime 中表示为objc_class 类型
struct objc_class {
Class isa;
}
其中: 类的 isa 指针指向它的元类。
Class 的定义:
typedef struct objc_class *Class;
也就是说类和他的元类都是同一种类型
3: 元类
元类中保存了这个类的类方法的地址。元类的 isa 指针指向元类对应的类的父类的元类。
提个问题,类方法是通过元类找到, 一般对象方法是根据啥找到? 后续解答
4: 方法
方法在 Runtime 中用 Method 类型来表示,从 Runtime 的源码可以看到,Method 类型是一个指向 method_t 结构体类型的指针。
typedef struct method_t *Method;
struct method_t {
SEL name; // 称为方法子,也就是封装了方法名的数据结构objc_selector
const char *types; //types – 表示该方法参数的类型 比如
IMP imp; // IMP 是一个函数指针,也就是函数实现地址
// ...
};
其中:
typedef struct objc_selector *SEL;
typedef id (*IMP)(id, SEL, …);
IMP 是一个函数指针,这个函数指针包含一个接收消息的对象id(self 指针), 调用方法的选标 SEL (方法名),以及不定个数的方法参数,并返回一个id,我们可以通过NSObject 类中的methodForSelector:方法就是这样一个获取指向方法实现IMP 的指针,
例如:
void (*setter)(id, SEL, BOOL);
setter = (void(*)(id, SEL, BOOL))[target
methodForSelector:@selector(setFilled:)];
函数调用:
setter(targetList[i], @selector(setFilled:), YES);
注意,methodForSelector:是Cocoa运行时系统的提供的功能,而不是Objective-C语言本身的功能
一般我们源码中不使用IMP,SEL ,但是在源码中使用也是可以的,比如:
id target = getTheReceiver();
SEL method = getTheMethod();
[target performSelector:method];
5: 实例变量
typedef struct ivar_t *Ivar;
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
// ...
};
6 属性
typedef struct property_t *objc_property_t;
struct property_t {
const char *name;
const char *attributes;
};
7:协议
struct protocol_t : objc_object {
const char *mangledName;
struct protocol_list_t *protocols;
method_list_t *instanceMethods;
method_list_t *classMethods;
method_list_t *optionalInstanceMethods;
method_list_t *optionalClassMethods;
property_list_t *instanceProperties;
// ...
};
8:Objective-C 方法和隐含参数
对于每一个 Objective-C 的方法,例如:
- (void)printCount:(NSInteger)count {
// ...
}
编译器都会将其编译成一个 C 函数,上面的方法会被编译成:
void foo(id self, SEL _cmd, int count) {
// ...
}
这个 C 函数的第一个和第二个参数就是隐含参数,在 Objective-C 的方法体中,也是可以直接使用的。编译为 C 函数后,需要在函数声明中明确的声明。
9: 消息传递
[receiver message];
编译器会把它编译成对 C 函数 objc_msgSend 的调用。
objc_msgSend(receiver, @selector(message));
上述objc_class是简单描述,下面具体描述一下:
1》 类 具体数据结构类型
struct objc_class {
struct objc_class * isa; /* 指向元类,元类里面存放这所有的类方法*/
struct objc_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; /*协议链表*/
};// 存放类的结构的对象 isa 也称为元类对象
二: Runtime API
API 主要有下面的类型:
-
objc_
-
class_
-
object_
-
method_
-
property_
-
protocol_
-
ivar_ ,sel_ ,imp_
1.objc_xxx 系列函数
函数名称 函数作用
objc_getClass 获取Class对象
objc_getProtocol 获取某个协议
objc_getMetaClass 获取MetaClass对象
objc_copyProtocolList 拷贝在运行时中注册过的协议列表
objc_allocateClassPair 分配空间,创建类(仅在 创建之后,注册之前 能够添加成员变量)
objc_registerClassPair 注册一个类(注册后方可使用该类创建对象)
objc_disposeClassPair 注销某个类
objc_allocateProtocol 开辟空间创建协议
objc_registerProtocol 注册一个协议
objc_constructInstance 构造一个实例对象(ARC下无效)
objc_destructInstance 析构一个实例对象(ARC下无效)
objc_setAssociatedObject 为实例对象关联对象
objc_getAssociatedObject 获取实例对象的关联对象
objc_removeAssociatedObjects 清空实例对象的所有关联对象
objc_msgSend 发送ObjC消息
objc_ 系列函数关注于宏观使用,如类与协议的空间分配,注册,注销等操作
2.class_xxx 系列函数
函数名称 函数作用
class_addIvar 为类添加实例变量
class_addProperty 为类添加属性
class_addMethod 为类添加方法
class_addProtocol 为类遵循协议
class_replaceMethod 替换类某方法的实现
class_getName 获取类名
class_isMetaClass 判断是否为元类
class_getSuperclass 获取某类的父类
class_setSuperclass 设置某类的父类
class_getProperty 获取某类的属性
class_getInstanceVariable 获取实例变量
class_getClassVariable 获取类变量
class_getInstanceMethod 获取实例方法
class_getClassMethod 获取类方法
class_getMethodImplementation 获取方法的实现
class_getInstanceSize 获取类的实例的大小
class_respondsToSelector 判断类是否实现某方法
class_conformsToProtocol 判断类是否遵循某协议
class_createInstance 创建类的实例
class_copyIvarList 拷贝类的实例变量列表
class_copyMethodList 拷贝类的方法列表
class_copyProtocolList 拷贝类遵循的协议列表
class_copyPropertyList 拷贝类的属性列表
class_系列函数关注于类的内部,如实例变量,属性,方法,协议等相关问题
3.object_xxx 系列函数
函数名称 函数作用
object_copy 对象copy(ARC无效)
object_dispose 对象释放(ARC无效)
object_getClassName 获取对象的类名
object_getClass 获取对象的Class
object_setClass 设置对象的Class
object_getIvar 获取对象中实例变量的值
object_setIvar 设置对象中实例变量的值
object_getInstanceVariable 获取对象中实例变量的值 (ARC中无效,使用object_getIvar)
object_setInstanceVariable 设置对象中实例变量的值 (ARC中无效,使用object_setIvar)
objcet_系列函数关注于对象的角度,如实例变量
4.method_xxx 系列函数
函数名称 函数作用
method_getName 获取方法名
method_getImplementation 获取方法的实现
method_getTypeEncoding 获取方法的类型编码
method_getNumberOfArguments 获取方法的参数个数
method_copyReturnType 拷贝方法的返回类型
method_getReturnType 获取方法的返回类型
method_copyArgumentType 拷贝方法的参数类型
method_getArgumentType 获取方法的参数类型
method_getDescription 获取方法的描述
method_setImplementation 设置方法的实现
method_exchangeImplementations 替换方法的实现
method_系列函数关注于方法内部,如果方法的参数及返回值类型和方法的实现
5.property_xxx 系列函数
函数名称 函数作用
property_getName 获取属性名
property_getAttributes 获取属性的特性列表
property_copyAttributeList 拷贝属性的特性列表
property_copyAttributeValue 拷贝属性中某特性的值
property_系类函数关注与属性*内部,如属性的特性等
6.protocol_xxx 系列函数
函数名称 函数作用
protocol_conformsToProtocol 判断一个协议是否遵循另一个协议
protocol_isEqual 判断两个协议是否一致
protocol_getName 获取协议名称
protocol_copyPropertyList 拷贝协议的属性列表
protocol_copyProtocolList 拷贝某协议所遵循的协议列表
protocol_copyMethodDescriptionList 拷贝协议的方法列表
protocol_addProtocol 为一个协议遵循另一协议
protocol_addProperty 为协议添加属性
protocol_getProperty 获取协议中的某个属性
protocol_addMethodDescription 为协议添加方法描述
protocol_getMethodDescription 获取协议中某方法的描述
7.ivar_xxx 系列函数
函数名称 函数作用
ivar_getName 获取Ivar名称
ivar_getTypeEncoding 获取类型编码
ivar_getOffset 获取偏移量
8.sel_xxx 系列函数
函数名称 函数作用
sel_getName 获取名称
sel_getUid 注册方法
sel_registerName 注册方法
sel_isEqual 判断方法是否相等
9.imp_xxx 系列函数
函数名称 函数作用
imp_implementationWithBlock 通过代码块创建IMP
imp_getBlock 获取函数指针中的代码块
imp_removeBlock 移除IMP中的代码块
还有一些方便的函数
我们可以通过NSObject的一些方法获取运行时信息或动态执行一些消息:
isKindOfClass 和 isMemberOfClass检查对象是否在指定的类继承体系中;
respondsToSelector 检查对象能否相应指定的消息;
conformsToProtocol 检查对象是否实现了指定协议类的方法;
methodForSelector 返回指定方法实现的地址。
performSelector:withObject 执行SEL 所指代的方法。
函数调用举例:
![](https://img.haomeiwen.com/i1974361/44f265367e3ce1a1.png)
![](https://img.haomeiwen.com/i1974361/0d03359373d84738.png)
![](https://img.haomeiwen.com/i1974361/f349efb5df88de39.png)
![](https://img.haomeiwen.com/i1974361/368409b15e75f166.png)
三: 代码编译实例
因为 Objective-C 的源代码都会被编译成 Runtime 代码来运行,我们一样可以通过直接编写 Runtime 代码的方式来编写程序。
举例:
我们有个类叫 ClassA:
@interface ClassA : NSObject
@property (nonatomic, assign) NSInteger count;
- (void)printCount;
@end
@implementation ClassA
-
(void)printCount {
NSLog(@"count = %@", @(self.count));
}
@end
然后来执行调用:
ClassA *a = [[ClassA alloc] init];
a.count = 100;
[a printCount];
下面来看看下面代码转成 Runtime 怎么写【自己写的】?
// 获取到 ClassA 的 Class 对象
Class ClassA = objc_getClass("ClassA");
// 发送 alloc 和 init 消息来创建和初始化实例对象
id a = objc_msgSend(ClassA, @selector(alloc));
a = objc_msgSend(a, @selector(init));
// 获取到属性 count 背后的实例变量
Ivar countIvar = class_getInstanceVariable(ClassA, "_count");
assert(countIvar);
// 通过实例对象首地址 + 实例变量的地址偏移量得到实例变量的指针地址,然后通过 * 取指针值操作符修改指针指向的地址的值。
CFTypeRef aRef = CFBridgingRetain(a);
int *countIvarPtr = (int *)((uint8_t *)aRef + ivar_getOffset(countIvar));
*countIvarPtr = 100;
CFBridgingRelease(aRef);
// 给 a 对象发送 printCount 消息,打印 count 属性的值
objc_msgSend(a, @selector(printCount));
再看看通过clang编译后输出的和我们写的有什么区别?
执行命令: clang -rewrite-objc main.m
结果如下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 一次性发送消息init和alloc 进而得到ClassA的对象
ClassA *a = ((ClassA *(*)(id, SEL))(void *)objc_msgSend)((id)((ClassA *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ClassA"), sel_registerName("alloc")), sel_registerName("init"));
// 执行setCount 方法进行赋值
((void (*)(id, SEL, NSInteger))(void *)objc_msgSend)((id)a, sel_registerName("setCount:"), (NSInteger)100);
// 执行printCount 方法
((void (*)(id, SEL))(void *)objc_msgSend)((id)a, sel_registerName("printCount"));
}
return 0;
}
网友评论