IOS Runtime
2017-03-17 10:50
106 查看
/* Runtime oc的运行时,一套c语言的API,能动态获取编译之后的对象,属性,成员,方法等; oc --> c 之后的结构 一. 类 与 对象 oc类由Class类型来表示,实际上是一个指向struct objc_class结构体的指针。定义如下: struct objc_class { Class isa, //指向类型的指针,也是个struct objc_class指针 Class super_class, //指向父类的指针,也是个struct objc_class指针 const char * name, //对象的类名 long version, //版本号,一个hash值,可以用来区分相同类的变更 long instance_size, //对象的内存大小 struct objc_cache * cache, //method的缓存 //三大核心: 对象的成员变量,方法,遵守的协议 struct objc_ivar_list * ivars, struct objc_method_list ** methodLists, struct objc_protocol_list * protocols } (1) isa 指向类型的指针,也是个struct objc_class指针;(指向"自身的类型") 需要注意的是,在oc中,所有的类自身也是一个对象 (2) super_class 指向该类的父类 (3) name 这个类的名称 (4) version 利用这个字段来提供类的版本信息。(对于对象的序列化有用) (5) instance_size 该类的对象的所占内存大小 (6) info (7) cache (8) *ivars 该类的成员变量链表 (9) **methodLists 方法定义的链表 (10) *protocols 协议链表 1.1 isa: 需要注意的是在oc中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass元类. 1.2 super_class: 指向该类的父类,如果该类已经是最顶层的根类(NSObject,NSProxy),则superClass为nil. 1.3 version: 类的版本信息,可以用作对象的序列化 1.4 name: 该类的类名 1.5 cache: cache用来缓存调用过的方法。该字段指向一个objc_cache结构体的指针, struct objc_cache { unsigned int mask; unsigned int occupied; Method buckets[1]; } cache用来缓存最近使用的方法。当一个接受者接受到一个消息时,它会根据isa指针去 查找能够响应这个消息的对象。如果每次消息来时,我们都便利一次MethodLists中一遍, 性能势必很差。这是,cache就派上用场,在我们每调用过一次方法后,这个方法就会被缓存 到cache列表中,下次调用的时候runtime优先去cache中查找; 2.1 objc_object 与 id objc_object是表示一个类的实例的结构体,它的定义(objc/objc.h) typedef struct objc_object * id; struct objc_object { Class isa; //Class 是 struct objc_class; } 可以看出,这个结构体只有一个字体,即指向其类的isa指针 ("在赋值的时候,就指向了对象的isa指针")。 这样,当我们向一个oc对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。 Runtime库会在类的方法列表及父类方法列表中去寻找与消息对应的selector指向的方法。 2.2 metaClass元类 所有的类自身也是一个对象,我们可以向这个对象发送消息。(即调用类方法) NSArray * array = [NSArray array]; 在这个例子中,+array消息发送给了NSArray类,而这个NSArray也是一个对象。既然是对象,那么它也是一个objc_object指针,它包含一个指向其类的一个isa指针. 那么问题来了,类对象的isa指针指向了什么? 为了调用+array方法,这个类的isa指针必须指向一个包含这些类方法的一个struct objc_class结构体。这就引出了meta-class的概念: meta-class是一个类对象的类。 当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的"类对象中"的方法列表中查找方法;而向一个类(类对象)发送消息时,会在这个类的meta-class的methodLists中查找.meta-class之所以重要,是因为它存储着一个类的所有类方法。 ( 重点: 类和元类都是一种特殊的对象 1. 一个类 存储了对象中所有实例方法; 对象的isa 指向 类对象 2. 一个元类存储了对象中所有的类方法; 类对象的isa 指向 元类对象 ) 通过 对象 --> 类对象 --> 元类对象 (1) 获取对象的类, 以及所有成员方法 Class currentClass = [self class]; NSUInteger outcount = 0; Method * methods = class_copyMethodList(currentClass, &outcount); for( NSInteger i=0; i<outcount; i++ ){ Method method = methods[i]; const char * methodName = sel_getName(method_getName(method)); NSLog(@"对象实例方法: %s", methodName); } (2) 获取对象的元类,以及所有的类方法 Class currentClass = [self class]; //根据类名获取元类 Class classObj = objc_getMetaClass(class_getName(currentClass)); NSUInteger outcount = 0; Method * methods = class_copyMethodList(classObj, &outcount); for( NSInteger i=0; i<outcount; i++ ){ Method method = methods[i]; const char * methodName = sel_getName(method_getName(method)); NSLog(@"方法名: %s", methodName); } 3 API 类与对象操作函数 runtime提供了大量的函数来操作类与对象。 (1) 类的操作方法 大部分是以class_为前缀; (2) 对象的操作方法 大部分是以objc_,object_为前缀; 3.1 类名 name //获取类的类名 const char * class_getName( Class cls ); 3.2 父类和元类 super_class meta_class //获取类的父类 Class class_getSuperclass( Class cls ); //获取给定class的元类 Class metaCls = objc_getMetaClass(class_getName([self class])); ************* code ***************************** Class cls = object_getClass( self ); cls = object_getClass( cls ); NSLog(@"是否是元类: %zd", class_isMetaClass(cls)); //是 Class cls1 = [self class]; cls1 = [cls1 class]; NSLog(@"是否是元类: %zd", class_isMetaClass(cls1)); //不是 ****************************************** //判断给定的Class是否是一个元类 BOOL class_isMetaClass( Class cls ); 3.3 实例变量的大小 3.3.1 获取实例大小 size_t class_getInstanceSize( Class cls ); 3.3 版本version 3.3.1 获取版本号 int class_getVersion( Class cls ); 3.3.2 设置版本号 void class_setVersion( Class cls, int version ); 3.4 对象中的成员变量的相关API 3.4.1 获取类中指定名称的成员变量 Ivar class_getInstanceVariable( Class cls, const char * name ); 3.4.2 获取整个成员变量列表 Ivar * class_copyIvarList( Class cls, unsigned int * outCount ); 3.4.3 动态添加成员变量 BOOL class_addIvar( Class cls, const char * name, size_t size size, uint8_t aligment, const char * types); 注意: oc不支持往已存在的类中添加实例变量,因此不管是系统库提供的类,还是我 们自定义的类, 都无法动态添加成员变量;( 当我们通过运行时来创建一个类 的话,就可以使用class_addIvar添加成员了,不过该方法只能在 objc_allocateClassPair函数与objc_registerClassPair之间调用。) class_copyIvarList函数返回的 Ivar数组,并不包含在父类中声明的成员变量; 3.5 property 相关API 3.5.1 获取指定的Property objc_property_t class_getProperty( Class cls, const char * name ); 3.5.2 获取Property的列表 objc_property_t * class_copyPropertyList( Class cls, unsigned int * outCount ); 3.5.3 为类添加Property BOOL class_addProperty( Class cls, const char * name, const objc_property_attribute * attributes, unsigned int attributeCount); 3.5.4 替换类的Property void class_replaceProperty( Class cls, const char * name, const objc_property_attribute_t * attributes, unsigned int attributeCount); 3.6 method 相关API 3.6.1 获取实例方法 Method class_getInstanceMethod( Class cls, SEL name ); 3.6.2 获取类方法 Method class_getClassMethod( Class cls, SEL name ); 3.6.3 获取类对象中所有的方法 ( 类对象中存储的是 所有成员方法 ) Method class_copyMethodList( Class cls, SEL name ); 3.6.4 获取元类对象中所有的方法 ( 元类对象中存储的是 所有类方法 ) Method class_copyMethodList( Class metaCls, SEL name ); 3.6.5 添加方法 BOOL class_method( Class cls, SEL name, IMP imp, const char * types ); 3.6.6 替换指定方法的实现体 即让selector指向另外的IMP Method class_replaceMethod( Class cls, SEL name, IMP imp, cosnt char * types ); 3.6.7 返回方法的具体实现 IMP class_getMethodImplementation( Class cls, SEL name ); IMP class_getMethodImplementation_stret( Class cls, SEL name ); 3.6.8 类实例是否相应指定的selector BOOL class_respondsToSelector( Class cls, SEL sel ); 注意: (1) 一个oc方法最终转化为c函数,在消息动态绑定过程中,会将对象本身和方法名作为 两个参数,所以它 对应的c函数至少有两个参数self和cmd。 所以我们实现的函数 (IMP)至少需要两个参数: void myMethodIMP( id self, SEL _cmd ){ ... } 与成员变量不同的是,不管这个类是否存在,我们都可以为类动态添加方法; (2) class_method的实现 会覆盖"父类的方法实现",但不会取代本类中已存在的实 现,如果本类中包含一个同名的实现,则函数会返回NO。 如果要修改本类存在的实现, 使用class_setImplementaion。 (3) 参数types, 是一个描述传递给方法的参数类型的字符数组,这涉及到类型编码; (4) class_getInstanceMethod, class_getClassMethod函数回去搜索父类中 的方法实现,但是class_copyMethodList( object_getClass(cls),&outCount )则不会如此, outCount参数返回MethodList的长度,用完之后需要free(); (5) class_replaceMethod函数,该函数的行为可以分为两种: 5.1) 如果类中不存在name指定的方法,则类似于class_addMethod函数一样 会添加方法; 5.2) 如果类中已存在name指定的方法,则类似于class_setImplementaion 一样将原方法替换; (6) class_respondsToSelector函数, 查看对象是否实现了该方法; 不过我们通常用NSObject类的respondsToSelector:或 instanceRespondToSelector:方法达到同样效果。 3.7 protocol相关API 3.7.1 返回类实现的协议列表 Protocol * class_copyProtocolList( Class cls, unsigned int * outCount ); 3.7.2 返回类是否实现指定的协议 (confrom 遵守) BOOL class_conformsToProtocol( Class cls, unsigned int * outcount ); 3.7.3 添加协议 BOOL class_addProtocol( Class cls, Protocol * protocol ); 注意: class_conformsToProtocol函数 可以使用 NSObject类的 conformsToProtocol:方法来替代。 class_copyProtocolList函数返回一个数组,在使用后我们需要释放free()。 ************************************************ Class cls = [self class]; //类名 NSString * clsName = NSStringFromClass(cls); NSLog(@"%@", clsName); //获取元类 Class metaCls = objc_getMetaClass(class_getName([self class])); NSLog(@"是否是元类: %zd",class_isMetaClass(metaCls)); //获取父类 Class superClass = class_getSuperclass(cls); NSLog(@"父类: %@", NSStringFromClass(superClass)); //变量大小 NSLog(@"instance size: %zu", class_getInstanceSize(cls)); //成员变量 unsigned int outcount = 0; Ivar * ivars = class_copyIvarList(cls, &outcount); for( int i=0; i<outcount; i++ ){ Ivar ivar = ivars[i]; NSLog(@"instance variable's name:\n %s at index: %d", ivar_getName(ivar), i); } free(ivars); //属性操作 objc_property_t * properties = class_copyPropertyList(cls, &outcount); for ( int i=0 ; i<outcount; i++) { objc_property_t property = properties[i]; const char * name = property_getName(property); NSLog(@"属性名: %@", [NSString stringWithCString:name encoding:NSUTF8StringEncoding]); } free(properties); //方法操作 Method * methods = class_copyMethodList(cls, &outcount); for( int i=0; i<outcount; i++ ){ Method method = methods[i]; const char * name = sel_getName(method_getName(method)); NSLog(@"方法名: %@", [NSString stringWithCString:name encoding:NSUTF8StringEncoding]); } free(methods); ******************************************* 4. 动态创建类 4.1 创建一个新类和元类 Class objc_allocateClassPair( Class superClass, const char * name, size_t extraBytes); objc_allocateClassPair函数:如果我们要创建一个根类,则superClass指 定为nil。 extraBytes通常指定为0。 为了创建一个新类,需要调用objc_allocateClassPair。然后调用 class_addMethod, class_addIvar等函数来为新创建的类添加方法,实例变量和属性等.完成这些之后,我们需要调用objc_registerClasspair函数来注册类,之后这个新类就可以在程序中使用了。 ( 注意: 实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上 ) ; 4.2 销毁一个类以及相关联的类 void objc_disposeClassPair( Class cls ); 4.3 在应用中注册由objc_allocateClassPair创建的类 void objc_registerClassPair( Class cls ); ********************************************* Class cls = objc_allocateClassPair([NSObject class], "myClass", 0); NSLog(@"%s", @encode(NSString *)); class_addMethod(cls, @selector(hehe:), (IMP)hehe, "v@@"); //IMP是c函数,参数有self和_cmd //ios中的成员变量只能在allocate~register之间添加,其他地方无效; class_replaceMethod(cls, @selector(hehe:), (IMP)xixi, "v@@"); objc_property_attribute_t type = {"T","\"NSString\""}; objc_property_attribute_t nona = {"N", ""}; objc_property_attribute_t strong = {"&",""}; objc_property_attribute_t name = {"V","temp2"}; objc_property_attribute_t attributes[] = {type, nona,strong,name}; BOOL result = class_addProperty(cls, "temp2", attributes, 4); NSLog(@"属性是否添加成功: %zd", result); objc_registerClassPair(cls); id instance = [[cls alloc] init]; hehe(instance, @selector(hehe:), @"222222"); [instance performSelector:@selector(hehe:) withObject:@"1111"]; [instance setValue:@"123123" forKey:@"_temp"]; //给对象的属性成员赋值 //获取到的Ivar,其实是原来对象值得一份拷贝; NSString * str1 = (NSString *)object_getIvar(instance, class_getInstanceVariable(cls, "_temp")); NSLog(@"%@", str1); ******************************************************** 5. 动态创建对象: 5.1 创建类实例 id class_createInstance( Class cls, size_t extraBytes ); 5.2 在指定位置创建类实例 id objc_constructInstance( Class cls, void * bytes ); 5.3 销毁类实例 void * objc_destructInstance( id obj ); 5.4 返回指定对象的一份拷贝 id object_copy( id obj, size_t size ); 5.5 释放指定对象占用的内存 id object_dispose( id obj ); 6. 针对对象实例, 对象的类进行操作的函数API 6.1 根据字符串获取对象的成员变量Ivar Ivar ivar = class_getInstanceVariable(cls, @"_ivarTemp") id object_getIvar(<#id obj#>, <#Ivar ivar#>) 6.2 获取对象实例变量的值 Ivar object_setInstanceVariable( id obj, const chat * name, void * value); 6.3 返回指向给定对象分配的任何额外字节的指针 void * object_getIndexedIvars( id obj ); 6.4 设置对象中实例变量的值 void object_setIvar( id obj, Ivar ivar, id value ); Ivar ivar = class_getInstanceVariable(cls, "_instance2"); const char * name = ivar_getName(ivar); NSLog(@"%s", name); id var = object_getIvar(self, ivar); NSLog(@"%@", var); object_setIvar(self, ivar, @"22222222"); 对对象的类进行操作的函数API 6.5 返回给定对象的类名 const char * object_getClassName( id obj ); 6.6 返回对象的类 ( 可以通过这个API来获取元类 ) Class object_getClass( id obj ); 6.7 设置对象的类 Class object_setClass( id obj, Class cls ); 获取类定义 6.8 获取已注册的类定义的列表 int objc_getClassList( Class * buffer, int bufferCount ); 6.9 创建并返回一个指向所有已注册类的指针列表 Class * objc_copyClassList( unsigned int * outCount ); 6.10 返回指定类的类定义 Class objc_lookUpClass( const char * name ); Class objc_getClass( const char * name ); Class objc_getRequiredClass( const char * name ); 6.11 返回指定类的元类 Class objc_getMetaClass( const char * name ); int numClasses = 0; Class * classes = NULL; numClasses = objc_getClassList(NULL, 0); if ( numClasses > 0 ) { classes = (Class *)malloc(sizeof( Class )*numClasses); numClasses = objc_getClassList(classes, numClasses); NSLog(@"number of Classes: %zd", numClasses); for( int i=0; i<numClasses; i++ ){ Class cls = classes[i]; NSLog(@"class name: %s", class_getName(cls)); } free(classes); } 七 编码类型 7.1 编码类型,可以通过 NSLog( @"%c", @encode( Type ) ) 来获取; c --> char i --> int s --> short l --> long C --> unsigned char I --> unsigned int S --> unsigned short L --> unsigned long f --> float d --> double B --> bool v --> void * --> char * @ --> object 对象 # --> Class类型 : --> selector(SEL类型) [array type]--> array {name=type} --> 结构体 ^type --> 指针类型 ? --> 未知类型 八 成员变量和成员属性 8.1 Ivar成员变量类型 Ivar是表示实例变量的类型,其实际是一个指向objc_ivar结构体的指针,定义如下: typedef struct objc_ivar * Ivar; struct objc_ivar { char * ivar_name //成员变量名字 char * ivar_type //变量类型 int ivar_offset //基地址偏移字节 } 8.2 objc_property_t属性类型 objc_property_t是表示oc声明的属性的类型,其实际是指向objc_property结构体的指针, 其定义如下: typedef struct objc_property * objc_property_t; typedef struct { const char * name; //属性名字 const char * value; //属性值 } 8.3 关联对象Associated Object 由于成员变量只能在动态声明类时的allocate~register之间添加,那么在分类中添加成员变量该如何解决? 关联对象Associated Object就是个非常不错的解决方案; 关联对象类似于成员变量,不过是在运行时添加的。我们可以把关联对象Associated Object想象成 一个oc对象(如字典),这个对象通过给定的key连接到类的一个实例上。不过由于是c接口,所以key是一个void指针(const void *)。我们还需要指定一个内存管理策略,以告诉Runtime如何管理这个对象的内存。值如下: OBJC_ASSOCIATION_ASSIGN OBJC_ASSOCIATION_RETAIN_NONATOMIC OBJC_ASSOCIATION_RETAIN OBJC_ASSOCIATION_COPY 当宿主对象被释放时,会根据指定的内存管理策略来处理关联对象.如果指定的策略是assign,则宿主释放时,对象不会被释放;而如果指定的是retain或copy,则宿主释放时,关联对象会被释放。 const char c; objc_setAssociatedObject(self, &c, @"123123", OBJC_ASSOCIATION_RETAIN); NSString * result = objc_getAssociatedObject(self, &c); NSLog(@"%@", result); 九 成员变量Ivar, 属性Property的操作函数API 9.1 成员变量Ivar 相关的API 9.1.1 获取成员变量的名称 const char * ivar_getName( ivar v ); 9.1.2 设置成员变量的值 object_setIvar(<#id obj#>, <#Ivar ivar#>, <#id value#>) 9.1.3 获取成员变量的类型 const char * ivar_getTypeEncoding( Ivar v ); 9.1.4 获取成员变量的偏移量 ptrdiff_t ivar_getOffset( Ivar v ); 9.2 关联对象Objc_associated Object相关API 9.2.1 设置关联对象 void objc_setAssociatedObject( id object, //被绑定的对象 const void * key, //key void * 类型 id value, //要绑定的对象 objc_associationPolicy policy); 9.2.2 获取关联对象 id objc_getAssociatedObject( id object, const void * key); 9.2.3 移除关联对象 void objc_removeAssociatedObjects( id object ); 9.3 属性property相关操作API 9.3.1 获取属性名 const char * property_getName( objc_property_t property ); 9.3.2 获取属性描述字符 const char * property_getAttributes( objc_property_t property); 9.3.3 获取属性中指定的特性 char * property_copyAttributeValue( objc_property_t property, const char * attributeName); 9.3.4 获取属性的特性列表 objc_property_attribute_t * property_copyAttributeList( objc_property_t property, unsigned int * outCount); 其中property_copyAttributeValue函数,返回的char * 使用完之后需要free(); 其中property_copyAttributeList函数,返回值在使用完后需要调用free(); 十 方法与消息 10.1 基础数据类型 10.1.1 选择器SEL ( 一个int类型的hash值,具有唯一性,(在同一个类中不存在两个相同的SEL ).与IMP形成映射 ) SEL叫做选择器,是表示一个方法的selector的指针,一般通过 @selector(functionName)计算得出; 定义如下: typedef struct objc_selector * SEL; 获取SEL的方式: 1) sel_registerName(<#const char *str#>) 2) @selector( functionName ) 3) NSSelectorFromString( ) 4) SEL method_getName( Method m ); 注意,并不能从IMP获得 SEL; 10.1.2 函数体( c级别的函数指针 ) IMP IMP实际是一个函数指针,指向方法实现的首地址.其定义如下: id ( * IMP )( receiver , SEL, ... ); 每个oc的函数体,最终会转成以上实现形式; 第一个参数 是指向self的指针。(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针); 第二个参数 是方法选择器selector; SEL就是为了查找“方法的最终实现的IMP”.由于每个方法对应唯一的SEL,因此我们可以通过SEL方便快捷地获得它所对应的IMP,查找过程将在下面讨论。取得IMP后,我们就获得了执行这个方法代码的入口点(函数指针直接调用函数)。此时,我们就可以像调用普通c语言函数一样使用这个函数指针了. 另外,通过取得IMP,我们就可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现,这样就省去了Runtime的消息发送和查找过程,效率会高效些; void ( * aa )( id self, SEL _cmd, NSString * str ); aa = class_getMethodImplementation([self class], @selector(hehe:)); aa( self, @selector(hehe:) , @"牛牛牛"); 10.1.3 SEL与IMP的绑定 Method Method用于表示类定义中的方法,将SEL与IMP绑定在一起;定义如下: typedef struct objc_method * Method; struct objc_method { //方法名,其实是个hash值,不过可以通过sel_getName()获取名字 SEL method_name; char * method_types; IMP method_imp; //函数指针 } 可以看到该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间做了一个映射。通过SEL,找到IMP, 然后IMP调用函数代码; 10.2 Method相关操作API 10.2.1 获取方法名 ( 获取SEL ) SEL method_getName( Method m ); 10.2.2 返回方法的实现( 获取IMP ) IMP method_getImplementation( Method ); 10.2.3 获取描述方法参数和返回值类型的字符串 const char * method_getTypeEncoding( Method m ); 10.2.4 获取方法的"返回值"的类型的字符串 char * method_copyArgumentType( Method m, unsigned int index ); 10.2.5 返回方法的"参数"的个数 unsigned int method_getNumberOfArguments( Method m ); 10.2.6 通过"引用变量"返回方法指定位置参数的类型字符串 void method_getArgumentType( Method m, unsigned int index, char * dst, size_t dst_len); 10.2.7 返回指定方法的方法描述结构体 struct objc_method_description * method_getDescription( Method m ); 10.2.8 设置方法的实现 ( 将原来Method中的SEL指向新的 Method的IMP ) IMP method_setImplementation( Method m, IMP imp ); 10.2.9 交换两个方法 ( 将两个Method的SEL指向的IMP,发生对换 ) void method_exchangeImplementations( Method m1, Method m2 ); 注意: (1) method_getName函数,返回的是一个SEL,是一个hash值。如果想获取方法的名字,使用 sel_getName( method_getName(method)); (2) method_getReturnType函数,类型字符串会被拷贝到dst中; (3) method_setImplementation函数,注意该函数的返回值是方法之前的实现. 10.3 SEL方法选择器的操作API 10.3.1 获取指定选择器的方法名 const char * sel_getName( SEL sel ); 10.3.2 在oc runtime系统中注册一个方法,将方法名映射到一个SEL,并返回这个SEL SEL sel_registerName( const char * str ); 10.3.3 在oc runtime系统中注册一个方法 SEL sel_getUid( const char * str ); 10.3.4 比较两个选择器 BOOL sel_isEqual( SEL lhs, SEL rhs ); 10.4 IMP操作API ( 不一定用IMP去接受,其实就是个函数指针类型,可以自定义类型指针去接受返回值 ) 获取IMP的API 1) IMP class_getMethodImplementation( Class, SEL ); //runtime 2) IMP methodForSelector:(SEL) selector; //NSObject 十一 方法调用流程 ( oc的消息发送 ) 11.1 在oc中,消息直到运行时才绑定到方法实现上。编译器会将消息表达式[receiver message]转化为一个消息 函数的调用,即objc_msgSend。这个函数将消息接受者和方法名作为其基础参数。如下所示: objc_msgSend( receiver, selector ); 如果消息中还有其他参数,则该方法的形式如下所示: objc_msgSend( receiver, selector, arg1, arg2 ); 这个函数完成了动态绑定的所有事情;也就是说所有的oc方法调用的实际形式是上面的这种形式; 1) objc_msgSend()开始调用之后,通过receriver,selector可以找到Method,然后找到IMP。 2) 找到IMP之后,将receiver,SEL,以及其他参数都传给IMP。 3) 它将IMP的返回值作为自己的返回值。 11.2 消息发送的关键在于我们之前讨论的objc_class,这个结构体有两个字段是我们在分发消息是需要关注的: 1) 指向父类的指针; 2) 一个类的方法分发表,即methodLists; ( Method集合 ) 1. 当消息发送给一个对象时,objc_msgSend通过对象的isa指针取得类的结构体,然后在methodLists方法分发表里面查找方法的selector。 2. 如果没有找到selector, 则通过objc_msgSend结构体中指向父类的指针找到其父类,并在父类的methodLists方法 分发表里面查找方法的selector。依次,一直到根据类的继承体系到达NSObject类. 3. 一旦定位到selector,就找到了该方法的IMP, 并传入相应的参数来执行方法的具体实现。 4. 如果最后没有定位到selector,则走消息转发流程。( 消息转发,即将本receiver的消息转到其他的receiver,甚至是不再调用该方法,而是调用其他方法)。 11.3 隐藏参数: 1) 消息接受对象 receiver 2) 方法的SEL selector 这两个参数为方法的实现提供了调用者的消息,之所以说是隐藏的,是因为他们在定义方法以及调用oc方法是都不是以参数形式 出现的。它们是在编译器被插入实现代码的。 虽然这些参数没有显示生命,但是可以在运行时引用它们。我们可以使用 "id self, SEL _cmd"来引用. id target = getTheReceiver(); SEL method = getTheMethod(); if( target==self || method==_cmd ){ return nil; } return [target performSelector:method]; 11.4 在遇到在循环中反复调用同意方法的情形,可以采用跳过发送消息的步骤,直接通过IMP去调用该函数; 十二 消息转发 ( 当发送消息中的 selector没有被匹配到时的处理 ) 12.1 当一个对象能接受一个消息时,就会走正常的方法调用流程。但是如果一个对象无法接受指定消息时,又会发送什么? 默认情形下,如果是以[object message]的方式调用方法,如果object无法响应message消息时,编译器会报错。如果是以perform..形式来调用,则需要等到运行时才能确定object是否收到message消息。如果不能,则程序崩溃。通常,当我们不能确定一个对象是否能接受某个消息时,会先用respondsToSelector:来判断: if( [self respondsToSelector:@selector(method)] ){ [self performSelector:@selector(method)]; } 当一个对象无法接受某一消息时,就会启动所谓的"消息转发(message forwarding)"机制。通过这个机制,我们可以告诉对象如何处理未知的消息。默认情形下,对象接受未知的消息,会导致程序崩溃; 12.2 消息转发机制的三个步骤: 1) 动态方法解析 2) 备用接收者 3) 完整转发 12.2.1 动态方法解析(+resolveInstanceMethod:/ +resolveClassMethod:) 对象在接受到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法) 或者+resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个"处理方法",不过该 使用该方法的前提是我们已经实现了该"处理方法",只需要在运行时通过class_addMethod函数动态添加到 类里面就可以了。 因为[self class]中的methodList中没有匹配到的selector,系统则将该消息进行转发; 在动态方法解析这一步的作用是; 如果做了对应的处理,那么为该selector绑定一个"新的IMP",则系统直接调用该IMP,然后完成此次的"消息发送"; //为该selector新绑定一个IMP + ( BOOL ) resolveInstanceMethod:(SEL)sel { NSString * selectorString = NSStringFromSelector(sel); if ( [selectorString isEqualToString:@"hehe"] ) { class_addMethod([self class], sel, (IMP)wuwu, "@:@i"); } return [super resolveInstanceMethod:sel]; } void wuwu( id self, SEL _cmd, NSString * str){ NSLog(@"string: %@",str ); } 12.2.2 备用接收者 forwardingTargetForSelector: 如果 resolveInstanceMethod:无法处理消息,则Runtime会继续调用一下方法: - (id)forwardingTargetForSelector:(SEL)aSelector 如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象作为消息的新接手者,且消息会被分发到这个对象.当然备用接收者对象不能是self自身,否则就是出现无限循环。当然,如果我们没有指定响应处理的对象来处理aSelector,则应该调用父类的实现来返回结果。 - ( id ) forwardingTargetForSelector:(SEL)aSelector { NSLog(@"forwardingTargetForSelector"); NSString * selectorString = NSStringFromSelector(aSelector); //将消息转化给_helper来处理 if( [selectorString isEqualToString:@"hehe:"] ){ return [UIApplication sharedApplication].delegate; } return [super forwardingTargetForSelector:aSelector]; } 当进行消息转发给备用接收者时,系统会将receriver该为备用接收者,selector不变。所以备用接收者必须要有一个同名的方法。 12.2.3 完整消息转发 (即receiver和selector都可以换成其他的) forwardInvocation: 如果备用接收者都无法处理未知消息,则唯一能做的就是启动完整的消息转发机制了。此时会调用 - (void)forwardInvocation:(NSInvocation *)anInvocation 运行时系统会在这一步给消息接收者最后一次机会将消息转发给其他对象。系统会创建一个表示消息的NSInvocation对象,把与尚未处理的消息有关的全部细节都封装在anInvocation中,包括selector,target和参数。我们可以在forwardInvocation方法中 选择将消息转发给其他对象。 forwardInvocation:方法的实现由两个任务: 1. 定位可以响应封装在anInvocation中的消息的对象。这个对象不需要能处理所有未知消息。 2. 使用anInvocation作为参数,将消息发送到选中的对象。anInvocation将会保留调用结果,运行时系统会提取这一结果并将其发送到消息的原始发送者。 另外,还有一个重要问题,我们必须重写一下方法; - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 消息转发机制使用从这个方法中"返回的NSMethodSignature" 获取消息的信息来创建NSInvocation对象。因此我们必须要重写 这个方法,为给定的selector提供一个合适的方法签名。 //这个方法是获得指定方法的签名 - ( NSMethodSignature * ) methodSignatureForSelector:(SEL)aSelector { NSMethodSignature * signature = [super methodSignatureForSelector:aSelector]; signature = [self.rootView methodSignatureForSelector:@selector(wuwu:)]; return signature; } //这个方法可以更改selector,并且调用新的receiver,以及新的selector; //也就是说完全转发是能够将receiver和selector都变成另外一个新的; - ( void ) forwardInvocation:(NSInvocation *)anInvocation { anInvocation.selector = @selector(wuwu:); NSLog(@"%@", NSStringFromSelector([anInvocation selector])); [anInvocation invokeWithTarget:self.rootView]; } 十三 消息转发在实战中作用: 如果系统进行了消息转发,而没有处理,那么就会crash;且有个重要的特征: Unrecognized selector send to instance *******; 其实由于[receiver selector]方法调用,会提示selector没有实现;所以相对而言,出现消息转发情形还是挺少的;不过在使用高版本API,且适配低版本时,还是比较好用的; 而且由于crash是很严重的bug,所以在上线的时候,可以用消息转发预防 Unrecognized selector问题出现; 十四 Method Swizzling 14.1 相关的方法: 1) 为SEL添加一个新的IMP,如果此时SEL绑定了一个本对象实现的方法,则无效;如果是父类实现的,那么可以替换; BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) 2) 为SEL替换一个新的IMP,如果SEL原来有绑定的IMP,也会被替换掉 IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) 3) 将两个Method的SEL指向的IMP进行交换,而且是永久性的,而且能影响到子类; void method_exchangeImplementations(Method m1, Method m2) 14.2 Swizzling应该总在+load中执行 +load会在类初始加载时调用, +initialize会在第一次调用类的类方法或实例方法之前调用;由于method swizzling会影响到类的全局状态,因此要尽量避免在并发处理中出现竞争关系的情形;+load能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。相比之下,+initialize在其执行时不提供这种保证----事实上,如果在应用中没为这个类发送消息,则它可能永远不会被调用; 14.3 Swizzling应该总在dispatch_once中执行 swizzling会改变全局状态,所以我们在使用时必须得谨慎小心。原子性就是这样一种措施,它保证代码只执行一次,不管有多少个线程。GCD的dispatch_once可以确保这种行为,我们应该将其视为method swizzling的最佳实践。 + ( void ) load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class cls = [self class]; SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(xxx_viewWillAppear:); Method originalMethod = class_getInstanceMethod(cls, originalSelector); Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector); BOOL didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), //返回方法的参数类型编码 method_getTypeEncoding(swizzledMethod)); if( didAddMethod ){ class_replaceMethod(cls, swizzledSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); }else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } 十五 协议与分类 15.1 Category分类 Category是表示一个指向分类的结构体的指针,其定义如下: typedef struct objc_category * Category; struct objc_category { char * category_name; //分类名 char * class_name; //分类所属的类名 struct objc_method_list * instance_methods;//实例方法列表 struct objc_method_list * class_methods;//类方法列表 struct objc_protocol_list * protocols //分类所实现的协议列表 } 这个结构体中不能包含成员变量; 15.2 Protocol 协议 typedef struct objc_object Protocol; 15.3 关于Protocol操作的API 15.3.1 根据字符串返回指定的协议 Protocol * objc_getProtocol( const char * name ); 15.3.2 获取运行时所知道的所有协议的数组 Protocol ** objc_copyProtocolList( unsigned int * outCount ); 15.3.3 创建新的协议实例 Protocol * objc_allocateProtocol( const char * name ); 15.3.4 在运行时中注册新创建的协议 void objc_registerProtocol( Protocol * protocol ); 15.3.5 为协议添加新方法 void protocol_addMethodDescription( Protocol * protocol, SEL name, const char * types, BOOL isRquiredMethod, BOOL isInstanceMethod); 15.3.6 添加一个已注册的协议到协议中 void protocol_addProtocol ( Protocol *proto, Protocol *addition ); 15.3.7 为协议添加属性 void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty ); 15.3.8 返回协议名 const char * protocol_getName ( Protocol *p ); 15.3.9 测试两个协议是否相等 BOOL protocol_isEqual ( Protocol *proto, Protocol *other ); 15.3.10 获取协议中指定条件的方法的方法描述数组 struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, unsigned int *outCount ); 15.3.11 获取协议中指定方法的方法描述 struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod ); 15.3.12 获取协议中的属性列表 objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount ); 15.3.13 获取协议的指定属性 objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty ); 15.3.14 获取协议采用的协议 Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount ); 15.3.15 查看协议是否采用了另一个协议 BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other ); 15.4 分类无法添加成员变量问题 AssiciatedObject 1) void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) 2) id objc_getAssociatedObject(id object, const void *key) 通过AssociatedObject解决Category无法携带变量的问题; */
相关文章推荐
- ArcGIS Runtime SDK for iOS开发系列教程(3)——Objective-C语法基础
- IOS 应用运行环境理解: The Application Runtime Environment
- 【移动产品】ArcGIS Runtime SDK for iOS v2.2.1发布
- ArcGIS Runtime SDK for iOS简介
- ARCGIS RUNTIME FOR IOS总结(四)
- ARCGIS RUNTIME FOR IOS总结(一)
- 【移动产品】ArcGIS Runtime SDK for iOS 2.2发布
- [iOS] ios的runtime
- [iOS] ios的runtime
- Runtime专题:详解IOS开发应用之并发Dispatch Queues
- ArcGIS Runtime SDK for iOS开发系列教程(5)——要素信息的绘制
- 关于ArcGIS Runtime SDK for iOS中AGSLayerDefinition使用日期类型字段过滤的问题
- 【技术直通车】ArcGIS Runtime SDK for iOS 升级介绍
- ArcGIS Runtime SDK for iOS开发介绍
- ArcGIS Runtime SDK for iOS开发系列教程(1)——开发前准备
- ios runtime environment
- ArcGIS Runtime SDK for iOS开发系列教程(4)——如何让你的iOS应用具有GIS能力
- 【移动产品】ArcGIS Runtime SDK for iOS的新版本10.1.1发布
- ARCGIS RUNTIME FOR IOS总结(三)
- ARCGIS RUNTIME FOR IOS总结(五)