Runtime笔记(官方Doc翻译+原创)

Runtime笔记(官方Doc翻译+原创) 发表于 2016-12-26 | 分类于 iOS | | 阅读次数 345

https://immanito.github.io/2016/12/26/Runtime笔记(官方Doc翻译+原创)/

runtime系统先上runtime指南官方地址: Objective-C Runtime Programming Guide Objective-C是一门动态语言,它将静态语言在编译和链接时期做的事放在运行时处理Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译代码,这个运行时系统即Objc Runtime,运行时系统作为OC语言的操作系统。尤其是它在运行时动态的加载类,并且向其他对象转发消息。它同时提供在你的程序运行时如何找到对象的信息。

Objc Runtime是一个用C语言和汇编语言编写的库Objc Runtime有两个版本:Legacy 和Modern 两个版本在Legacy版本中,如果你在类中改变了实例变量的布局,你必须重新编译继承它的类。在Modern版本中,如果你在类中改变了实例变量的布局,你不需要重新编译继承它的类。iPhone应用和在OS X v10.5上运行的64位程序以及以后的版本使用Modern版本。其他程序(OS X 32位程序)使用Legacy版本。与运行时系统的交互OC程序在3个层次上与运行时系统进行交互:通过OC源代码通过Foundation框架下NSObject类中定义的方法通过直接调用运行时函数OC源码大部分时候,运行时系统在后台自动工作,你需要做的仅仅是编写并运行OC源码。当你编译包含OC类和方法的代码时,编译器为了实现语言的动态特性创建数据结构和函数调用。数据结构捕获类、类别定义和协议声明中的信息;它们包括OC语言中定义类时涉及到的类和协议对象,协议,以及SEL,实例变量模板,和其他从源码中提取的信息。主要的运行时函数是发送消息,它由源代码的消息表达式调用。NSObject方法大多数Cocoa中的对象都是NSObject类的子类,所以大多数对象都继承它定义的方法。(一个值得注意的例外是NSProxy类。)它的方法因此建立了每个实例和每个类对象固有的行为。然而,在少数情况下,NSObject类仅仅为了表明一些事情应该这么做而定义一个模板,它本身不提供任何必需的源码。例如,NSObject类定义了一个实例方法description,这个方法返回一个描述该类内容的字符串。这主要用于调试——GDB打印对象 命令打印从这个方法返回的字符串,NSObject类的这个方法的实现不知道包含有什么类,所以它返回包含对象名字和地址的字符串。NSObject的子类可以实现这个方法来返回更多详细信息。例如:Foundation框架下的NSArray类返回它包含的所有对象的描述。一些NSObject的方法简单地查询运行时系统的信息。这些方法允许对象执行自省(自我认知),这些举例列出的方法都是类方法,其要求对象识别他们的类;isKindOfClass:和isMemberOfClass:它测试对象在继承层次结构中的位置;repondsToSelector:它表示一个对象能接受一个特定的消息,conformsToProtocol它表示一个对象是否实现了定义在特定协议中的方法;methodForSelector:提供了方法的实现地址。像这些方法给一个对象认识自身的能力。运行时函数运行时系统是一个在位于/usr/include/objc的头文件中包含了一系列函数和数据结构的动态共享库,许多函数与允许你使用C语言硬编码去复写在你写OC代码时编译器做的事情。其他形式的基础功能导入时通过NSObject类的方法。这些函数使得开发其他面向运行时系统和增强开发环境的工具成为可能;它们不需要OC语言环境。然而,一部分运行时函数可能会不定期的被用来写OC程序。所有的这些函数都被记录在::Objective-C Runtime Reference::。(在API文档中查看)消息机制本节描述消息表达式是如何转为objc_msgSend的函数调用和如何通过方法名来引用一个方法。然后讲解如何利用objc_msgSend和如何绕过动态绑定。objc_msgSend函数在OC中,直到运行时之前,消息不会被绑定到方法的实现。编译器会转换一个消息表达式:[receiver message]转变为一个消息函数的调用——objc_msgSend。函数带有接受者和方法提及的名字,作为方法选择器的两个主要参数:objc_msgSend(receiver, selector)任何传入消息的参数都被交给objc_msgSend:objc_msgSend(receiver, selector, arg1, arg2, ...);消息函数做了所有动态绑定所必需的事情:它首先找到选择器所引用的方法实现。由于相同的方法可以被单独的类不同地实现,它找到的确切的方法实现取决于接受者所属的类。然后它通过接收对象以及该方法所指定的所有参数来调用方法实现。最后它通过调用方法实现的返回值来作为自己的返回值。注意:编译器生成的调用消息函数,你绝对不能在你的代码中直接调用。消息的关键在于编译器为每个类和对象构建的结构,每个类结构包含两个必要的元素:一个指向父类的指针一个类的调度表。这个表包含关联了方法选择器与它们标记的类特定方法的地址。setOrigin:方法的选择器与setOrigin:的地址关联起来,等等。当一个新的对象被创建的时候,内存会为它开辟一部分空间,它的实例变量同时被初始化。对象的所有变量中的第一个是一个指向它的类结构的指针,这个指针叫做isa指针,提供给对象访问它的类,通过类,到达所有该类继承的类。注意:严格说虽然不是语言的一部分,isa指针对于工作在OC运行时系统中的对象来说是必须的。一个对象需要与结构体struct objc_object在objc/objc.h中定义中定义的任何字段“等同”,但是,除非有需要你很少创建你自己的根对象,从NSObject或者NSProxy集成而来的类对象都自动拥有isa变量。当一条消息发送给一个对象的时候,消息函数跟随对象的isa指针到它的类结构,在调度表中查找方法的选择器,如果他不能在那找到选择器,objc_msgSend跟随指针来到父类并且试着在调度表中找到选择器,连续的失败会使objc_msgSend沿着继承机构向上寻找直到它到达NSObject类,一旦它发现选择器,该函数调用表中输入的方法并将其传递给接收对象的数据结构。这里着重说明一个知识点:在一个类中调用[super class]很多初学者会认为输出父类的名字,但是结果却还是与[self class]相同的输出,而又不理解,其实self是类的隐藏参数,指向当前调用方法的类,另一个隐藏参数是_cmd前面已经介绍过,代表当前方法的selector,这里只关注这个self,而super并不是一个隐藏参数,它是一个“编译器指示符”,和self指向相同的消息接受者,[self class]和[super class],接收class消息的都是指向当前类的指针,而不是想当然的super指向的父类,不同之处在于super只是告诉编译器,调用方法是要去调用父类的class方法而不是本类的,其实self和super最后调用的都是NSObject定义的方法,输出本类的类名,所以才会出现上面那种结果。这就是方法实现在运行时才会确定的实现方式,或者用面向对象编程的行话,那些方法与消息动态绑定。为了加快消息的传递过程,运行时系统缓存它们用到的选择器和方法的地址,每一个类都有一个单独的缓存,它能包含继承方法,就像它们自己定义的一样。在搜索调度表之前,发送消息例行在它的接收对象的类的缓存中查找(理论上,一个方法被使用一次可能会被再次使用),如果方法选择器在缓存中,消息传递仅仅比函数调用略微慢一点点。一旦一个程序已经运行的足够长的时间来“预热”它的缓存,几乎所有的消息都会找到缓存方法,在程序运行过程中,缓存动态增加来容纳新消息。使用隐藏参数当objc_msgSend找到了方法实现的程序段,它调用这段程序并传递消息中的所有参数,它也会传递给这段程序两个隐藏的参数:接收对象方法的选择器这些参数给它的方法实现提供显示的关于两半调用它的消息表达式的信息。他们被称为是隐藏的是因为它们并不会被声明在它们定义的源码中,当源码被编译的时候,这两个参数会被插入到实现中。尽管这些参数并不是显示声明的,源码仍然能够引用到它们(就像它能够引用到接受对象的实例变量一样)。一个引用接收对象为——self,引用他自己的方法选择器为——_cmd。在下面的例子中,_cmd引用strange的方法选择器,引用self作为接收strange消息的对象。- strange { id target = getTheReceiver(); SEL method = getTheMethod(); if ( target == self || method == _cmd ) return nil; return [target performSelector:method]; } self在两个参数中更有用一些,事实上,这是接收的对象的实例变量对于方法定义变得可用的方式。得到一个方法地址绕过动态绑定的唯一方式就是得到方法的地址并像调用函数一样调用它。这在极少数场合是合适的,当一个特定的方法被连续调用多次的时候而且你想避免方法每次被执行发送消息的开销。一个定义在NSObject类中的方法,methodForSelector:你可以要求一个指向实现一个方法的过程的指针,然后用指针调用这个过程,methodForSelector:返回的指针必须仔细转换到恰当的函数类型。返回值和参数类型都应该包含在转换中。下面的例子展示了setFilled:方法的实现过程如何被调用:void (*setter)(id, SEL, BOOL);int i;setter = (void (*)(id, SEL, BOOL))[targetmethodForSelector:@selector(setFilled:)];for ( i = 0 ; i < 1000 ; i++ )setter(targetList[i], @selector(setFilled:), YES);第一次被传过去的两个参数是接收对象self和方法选择器_cmd。这些参数在方法语法中被隐藏,但是当方法被当做函数调用的时候必须显示的传递。使用methodForSelector:来规避动态绑定节约了大量发送消息所需的时间,要使节省变得有意义,必须当一个特定的消息重复很多次的时候,就像上面for循环展示的那样。动态方法解析这节描述了如何动态地提供一个方法。动态方法解析有时你会想要动态地提供一个方法的实现,例如,OC声明属性特性包含@dynamic关键字@dynamic propertyName;告诉编译器与属性关联的方法会被动态地提供。你可以实现方法resolveInstanceMethod:和方法resolveClassMethod:来分别地动态提供一个给定selector,实例和类名的方法实现。一个OC方法的根本就是带有self和_cmd两个参数的C函数,你可以用函数class_addMethod来添加一个函数到类中去作为方法。因此给出以下函数:void dynamicMethodIMP(id self, SEL _cmd) {// implementation ….}你可以使用方法resolveInstanceMethod:动态添加它的实现到类中作为方法(resolveThisMethodDynamically):@implementation MyClass+ (BOOL)resolveInstanceMethod:(SEL)aSEL { if (aSEL == @selector(resolveThisMethodDynamically)) { class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:"); return YES; } return [super resolveInstanceMethod:aSEL]; } @end 官方的runtime编程指南到这里就没在继续详细讲解了,你可以喷我,但我还是想说,新手根本看不懂所以还要再增加篇幅从头讲。每个方法都有一个SEL(selector)和一个IML(implement),SEL可以随便写,但是不一定有对应的IML,如果消息函数沿着继承层次结构找到了顶端还是找不到对应的方法实现,就会抛出异常而crash。上文提到“消息函数做了所有动态绑定所必需的事情:它首先找到选择器所引用的方法实现。”但是如果一直没有找到,就会开始尝试动态解析,消息转发,标准消息转发:其实这就是通过SEL查找IML,这个过程也可以用下图表示:resolveInstanceMethod函数函数原型是:+ (BOOL)resolveInstanceMethod:(SEL)name;在运行时(runtime),SEL没有找到对应的IML就会先执行这个函数,这个函数是给类利用class_addMethod添加方法的机会。如果实现了添加方法的代码则返回YES,如果没有实现则返回NO。新建一个工程在.m文件添加如下代码:#import "ViewController.h" @interface ViewController() @end @implement ViewController - (void)viewDidLoad{ [super viewDidLoad]; [self performSelector:@selector(doSomething:)]; } - (void)didReceiveMemoryWarning{ [super didReveiceMemoryWarning]; } 结果就是程序crash控制台报错:terminating with uncaught exceptionof type NSException因为程序没有找到doSomething:这个方法,下面我们实现:+ (BOOL)resolveInstanceMethod:(SEL)sel;并且判断若果sel是doSomething:那就说出add method here#import "ViewController.h" @interface ViewController() @end @implement ViewController - (void)viewDidLoad{ [super viewDidLoad]; [self performSelector:@selector(doSomething)]; } + (BOOL)resolveInstanceMethod:(SEL)sel{ if(sel == @selector(doSomething)){ NSLog(@"add method here!"); return YES; } return NO; } - (void)didReceiveMemoryWarning{ [super didReveiceMemoryWarning]; } 运行查看控制台发现程序虽然崩溃了,但是控制台输出的第一句话就是add method here! 说明确实进入了这个方法并且通过了判断。所以我们可以在if语句里做一下操作,使得这个方法的得到实现而不至于走到方法:- (void)doesNotRecognizeSelector:(SEL)aSelector;走到这个方法就会Crash,接下来我们继续更改#import "ViewController.h" @interface ViewController() @end @implement ViewController - (void)viewDidLoad{ [super viewDidLoad]; [self performSelector:@selector(doSomething:)]; } + (BOOL)resolveInstanceMethod:(SEL)sel{ if(sel == @selector(doSomething)){ NSLog(@"add method here!"); class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:"); return YES; } return [super resolveInstanceMethod:sel]; } void dynamicMethodIMP(id self, SEL _cmd){ NSLog(@"doSomthing SEL"); } - (void)didReceiveMemoryWarning{ [super didReveiceMemoryWarning]; } 定义了一个void dynamicMethodIMP(id self, SEL _cmd)这个函数,并且在+ (BOOL)resolveInstanceMethod:(SEL)sel方法中执行了class_addMethod方法,运行工程我们查看Log:add method here!doSomething SEL程序成功输入,这说明我们已经通过runtime成功向我们这个类中添加了一个方法,这里说几点注意事项:首先class_addMethod是定义在\中的方法,使用前要导入头文件,前几个查找IML的方法是定义在NSObject中的方法,所以无需导入头文件。我们再来看一下class_addMethod的方法定义class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const charchar *types)cls 方法所要添加到的类name 方法名字可以随意起imp 实现方法的函数types 定义该函数返回值类型和参数类型(依次按序输入)的字符串,注意这个参数不是NSString类型,而是const char *类型 所以不要用@“”,而要直接用””,我们上面的函数是 void dynamicMethod(id self, SEL _cmd) 返回值是void——(对应)v ; 第一个参数是self——(对应)@ 第二个参数是SEL——(对应):,所以连起来就是“v@:”就是此处该写入的参数。再举个例子:int newMethod(id self, SEL _cmd, NSString *str){return 100;}那么添加这个函数的方法就是class_addMethod([self class], SEL name, IMP imp, "i@:@");forwardingTargetForSelector函数如果在+ (BOOL)resolveInstanceMethod:(SEL)sel中没有找到或者添加方法,消息继续往下传递到-(id)forwardingTargetForSelector:(SEL)aSelector看看是不是有对象可以执行这个方法,我们再原有例子的基础上在新建一个类#import"SecondViewController.h" @interface SecondViewController @end @implementation SecondViewController - (void)viewDidLoad{ [super viewDidLoad]; } - (void)secondVCMethod{ NSLog(@"This is secondVC method"); } - (void)didReceiveMemoryWarning{ [super didReceiveMemoryWarning]; } 添加好后我们要在ViewController 中调用secondVCMethod,可是这个两个类并没有继承关系,正常是无法调用的在ViewController中#import "ViewController.h" @interface ViewController() @end @implement ViewController - (void)viewDidLoad{ [super viewDidLoad]; [self performSelector:@selector(secondVCMethod)]; } - (void)didReceiveMemoryWarning{ [super didReveiceMemoryWarning]; } 这样调用肯定会找不到方法而崩溃,下面我们是用forwardingTargetForSelector方法来转发一下消息,继续处理ViewConreoller类#import "ViewController.h" @interface ViewController() @end @implement ViewController - (void)viewDidLoad{ [super viewDidLoad]; [self performSelector:@selector(secondVCMethod)]; } + (BOOL)resolveInstanceMethod:(SEL)sel{ return [super resolveInstanceMethod:sel]; } - (id)forwadingTargetForSelector:(SEL)aSelector{ Class class = NSClassFromString(@"SecondViewController"); UIViewController * vc = class.new; if(aSelector == NSSelectorFromString(@"secondVCMethod")){ NSLog(@"secondVC do this"); return vc; } return nil; } - (void)didReceiveMemoryWarning{ [super didReveiceMemoryWarning]; } 我们会发现secondVCMethod方法执行了,程序并没有崩溃,原因在于当没有找到secondVCMethod这个方法的时候消息一直传递到方法- (id)forwadingTargetForSelector:(SEL)aSelector然后在里面创建了一个SecondViewController的对象,并判断如果这个需要转发的方法是secondViewController中的方法就返回secondViewController的对象,消息成功转发给secondViewController的对象,并执行。同时也相当于完成了一个多继承。动态加载一个OC程序可以在运行的时候绑定并连接新的类和分类。新的代码会被合并到程序中,与一开始就加载的代码没有区别。动态加载可以被用来做血多不同的事情,例如,在系统APP的许多模块都是动态绑定。在Cocoa环境下,动态绑定通常被用来自定义APP。其他则是用来写一些运行时加载的组件——就像Interface Builder加载定制的调色板和OS X系统应用加载自定义模块一样,可加载模块扩展了你的应用可以做什么,它们的贡献在于你提供框架,他人提供代码。虽然运行时函数在Mach-O文件中执行动态绑定(objc_loadModiles,在objc_load.h中定义),Cocoa的NSbundle类为动态绑定提供了一个显着更方便的接口——一种面向对象并与相关服务集成的接口。在Foundation框架查看NSBundle类的说明参考类的信息和它所使用的。通过 《OS X ABI Mach-O 文件格式参考》查看Mach-O文件的信息。消息转发向一个对象发送消息,对象没有处理消息,就会报错。然而,在报错之前,运行时系统给接收消息的对象两个选择去处理消息。转发如果你给一个对象发送消息,并且这个对象没有处理这个消息,在抛出一个错误之前,运行时系统会向对象发送一个消息:forwardInvocation:用NSInvocation作为它的唯一实参,通过NSInvocation对象封装了原始的消息和实参。你可以实现forwardInvocation:方法来给消息一个默认的响应,或者从其他方式避免错误。正如它的名字那样,这个方法被用来将消息转发给其他对象。查看转发的范围和意图,想象下列场景:假如,开始,你设计了一个对象可以响应一个叫做negotiate的消息,并且你想让它的响应包含一个其他类型的对象的响应。你可以通过将negotiate消息传递给你实现的negotiate方法的主体里的对象来轻松的实现。进一步采取这一步,然后假设你想让你的对象准确地响应另一个类中negotiate消息实现。一种实现方法是使你的类从其他类继承这个方法。然而,它或许是不可能实现的。这也许可以很好的解释,为什么你的类和实现了negotiate方法的类在继承层次的不同分支。即使你的类不能继承negotiate方法,你仍然可以通过实现一个将消息传递给其他类的实例的方法来借用它。- (id)negotiate{ if ( [someOtherObject respondsTo:@selector(negotiate)] ) return [someOtherObject negotiate]; return self;}这种方式可能有一点麻烦,尤其是你想使你的对象传递一定数量的消息给另一个对象。你需要实现并覆盖每一个你想要从其他类借用的方法。此外,当你写代码时,你可能想要转发全部的消息集,对于你不知道的句柄,这是不可能的,该消息集合可能取决于运行时的事件,并且它可能会改变,因为将来会有新的方法和类被实现。对于这个问题,forwardInvocation:消息提供了一个较少的特别指定的解决方法,并且这个方法是动态的而不是静态的。它像这样工作:当一个对象不能响应一个消息,因为它并没有方法可以匹配消息中的SEL,runtime系统通过发送一个forwardInvocation消息来告知对象。每一个对象都从NSObject类继承这个forwardInvocation方法。然而NSObject版本的方法只是简单地调用了doesNotRecognizeSelector:方法,通过覆写NSObject的版本并加入你自己的实现,你可以利用forwardInvocation消息提供的机会将消息转发给其他对象。为了转发一个消息,所有的forwardInvocation方法需要这样做:确定消息应该到哪里带着原始的实参一起发送它消息可以与invokeWithTarget:方法一起被发送:- (void)forwardInvocation:(NSInvocation *)anInvocation{ if ([someOtherObject respondsToSelector:[anInvocation selector]]) [anInvocation invokeWithTarget:someOtherObject]; else [super forwardInvocation:anInvocation];}转发的消息的返回值会被返回到原始的发送者。所有类型的返回值都可以被传递到发送者,包括id, structure,和双精度浮点数。一个forwardInvocation:方法可以作为不识别消息的分配中心,将它们打包分发给不同的接受者。或者它作为一个中转站,发送所有的消息到相同的目的地。它可以将一条消息转换为另一条消息,或者简单地吞没一些消息所以没有响应并且没有错误。forwardInvocation方法还可以将几条消息合并为一个单独的响应。forwardInvocation所做的就是提交给实现者。然而,它所提供的在一个转发链中关联对象的机会为程序设计开辟了更多可能。注意:forwardInvocation开始处理消息,除非他们不调用名义接收器中已经存在的消息。如果,例如你想让你的对象转发negotiate消息给另一个对象,它自己就不能够有negotiate方法。如果它有,消息绝对不会到达forwardInvocation:。想要了解更多关于转发和调用的信息,在Foundation框架中查看NSInvocation类说明。转发和多重继承转发模拟继承,被用来给OC程序添加一些多继承效果。一个通过转发来响应消息的对象呈现出借用或是继承了定义在另一个类中方法实现的效果。在这个插图中,一个Warrior的实例对象转发了一个negotiate消息给一个Diplomat类的实例。Warrior对于negotiate消息会呈现出和Diplomat一样的响应。它似乎对negotiate消息做出响应,并对所有实际目的做出回应(虽然事实上是一个Diplomat在工作)。转发消息的对象从而 从继承树上的两个分支继承了方法——它自己的分支和响应消息的对象的分支。在上面的例子中,他看上去好像Warrior类继承了Diplomat类以及它自己的父类。代理对象转发不仅仅模拟多继承,它也使得可以开发代表或覆盖更多实体类型的轻量级对象,The surrogate stands in for the other object and funnels messages to it.(谁来翻译一下)在The Objective-C Programing Language中讨论的远程通知就是一个代理,代理负责管理对一个远程接受者转发消息的细节,确保参数值在整个链接上被复制和检索,等等。但是它并不试图去做其他更多的事情,它不复制远程对象的功能,只是给远程对象一个它可以在其他应用接受消息的本地地址。其他类型的代理对象同样可以。假设,例如你有一个对象操作许多数据——也许它创建一个复杂的图片或者读取磁盘上一个文件的内容。设置这个对象可能非常耗时,所以你宁愿懒惰地使用它——当它真正被需要或者当系统资源暂时空闲的时候。同一时间,你需要至少一个此对象的占位符来保证其他对象在该应用中正常工作。在这种情况下,你开始可能创建了一个不成熟的对象,但是你可以为它创建一个轻量级的代理。这个对象可能会自己做一些事情,例如询问关于数据的问题,但是大多数时候它仅仅是为更大的对象占据一个位置,当时间到了,给它转发消息。当代理的forwardInvocation方法首先接收到去往其他对象的消息,它会确保对象存在并且如果没有被创建的话它会创建对象。较大对象的所有消息都会通过代理,所以,至于关于程序的其他部分,代理和较大的对象是相同的。转发和继承虽然转发模拟继承,但是NSObject类绝对不会混淆两者。像方法respondsToSelector:和isKindOfClass仅仅查看继承层次结构,并不在转发链上。如果,例如一个Warrios对象被询问是否响应negotiate消息if ([aWarrior respondsToSelector:@selector(negotiate)])答案是NO,即使它能不报错误地接收negotiate消息并响应他们,在某种意义上,通过转发消息给一个Diolomat对象。在许多例子中,答案是NO。但它可能不是。如果你使用转发设置一个代理对象或者扩展一个类的能力,转发机制大概应该像继承一样透明。如果你想让你的对象表现出它们好像真正的继承了他们转发消息的目标的行为,你需要重新实现respondsToSelector:方法和isKindOfClass方法来包含你的转发规则。- (BOOL)respondsToSelector:(SEL)aSelector{ if([super respondsToSelector:aSelector]) return YES; else{ /* Here, test whether the aSelector message can* * be forwarded to another object and whether that * * object can respond to it. Return YES if it can. * } return NO;}除了respondsToSelector:和isKindOfClass之外,instancesRespondToSelector:方法也能反映转发规则。如果协议被使用,conformsToProtocol:方法同样被添加到名单。同样的,如果一些对象转发任何它们接收到的远程消息,它应该有一个methodSignatureForSelector方法可以返回最终响应转发消息的方法的准确描述;例如,如果一个对象可以转发消息给它的代理,你如下应该实现methodSignatureForSelector:- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector{ NSMethodSignature *signature = [super methodSignatureForSelector:selector]; if(!signature){ signature = [surrogate methodSignatureForSelector:selector]; } return signature;}你可能考虑将转发规则放在私有代码中,并拥有他们,包括forwardInvocation,调用它。注意:这是一种高级技术,仅仅适合没有其他解决办法的时候使用。它并不是意在代替继承来使用。如果你一定要使用这种技术,确保你完全了解了执行转发的类和要被转发到达的类的行为。上述提及的方法都被编写在Foundation框架下的NSObject类的说明中。获得有关invokeWithTarget的信息请查看Foundation框架下的 NSInvocation类说明。类型编码为了辅助运行时系统,编译器为字符串中的每个方法编码返回值和参数类型,并将字符串与方法选择器相关联。它使用的编码模式同样适用于其他上下文,因此公开了可获得的编译器指令@encode。当给一个类型说明,@encode返回该类型的编码字符串。这些类型可以是基础类型,例如整形,指针,一个结构体或者联合体,或者一个类名——任何类型都可以,事实上,他可以用作C语言sizeof运算符的参数。char *buf1 = @encode(int **);char *buf2 = @encode(struct key);char *buf3 = @encode(Rectangle);下面的表格列出了类型编码。注意它们当中许多与你使用编码一个对象归档或者分发的代码重叠。然后这里列出的代码你不能用来编写一个编码器,这些代码当你编写一个并不是由@encode生成的编码器时你可能想要使用。Code Meaningc A chari An intl A long is treated as a 32-bit quantity on 64-bit programs.q A long longc An unsigned charI An unsigned intS An unsigned shortL An unsigned shortQ An unsigned long longf An unsigned long longd A doubleB A C++ bool or a C99 _Boolv A void* A character string (char *)@ An object (whether statically typed or typed id)# A class object (Class): A method selector (SEL)[array type] An array{name=type…} A structure(name=type…) A unionbnum A bit field of num bits^type A pointer to type? An unknown type (among other things, this code is used for function pointers)重要:Objective-C不支持long double类型。@encode(long double)返回d,与double类型返回的一样。数组的类型代码在方括号中;数组中的元素数量紧随开括号指定,在数组类型之前。例如一个包含12个浮点数指针的数组将会被编码为:[12 ^f]结构体被指定在大括号中,联合体在圆括号中。结构体标志首先被列出,紧跟一个等号标识符和用序列表示的结构体内容的代码。例如,结构体:typedef struct example {id anObject;char *aString;int anInt;} Example会被编码为:{example=@*i}相同的编码结果导致定义的类型名字或者结构体标识是否传递给@encode。结构体指针的编码携带与结构体字段相同的信息量:^{example=@*i}然而另一个间接的级别去除了内部类型规范:^^{example}对待对象会像对待结构体那样。例如,将NSObject类名传入@encode()产生这样的编码:{NSObject=# }NSObject类只声明一个实例变量isa,类型是Class。注意,尽管@encode指令不返回它们,当它们需要在协议中声明方法时,runtime系统使用额外的编码列表中的类型限定符。声明属性当编译器遇到属性声明,它生成与封闭类,类别或者协议相关联的描述性元数据。你可以使用支持在类或协议中通过名字查看属性的函数来访问这些元数据,获得属性的@encode字符编码类型,并将属性的属性列表复制一份C字符串数组。每个类和协议都有一个声明的属性列表。属性类型和函数Property结构定义了属性描述符的不透明句柄。typedef struct objc_property *Property你可以使用函数class_copyPropertyList和protocol_copyPropertyList来分别地检索一个与类和协议相关的属性数组:objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)例如给出如下类声明:@interface Lender : NSObject{float alone;}@property float alone;@end你可以这样获得属性列表:id LenderClass = objc_getClass(“Lender”);unsigned int outCount;objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);你可以使用property_getName函数来发现一个属性名字:const char *property_getName(objc_property_t property)你可以使用函数class_getProperty和class_getProperty来获得类和协议中给定名称的属性的引用:objc_property_t class_getProperty(Class cls, const char *name)objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)你可以使用 property_getAttributes函数获得一个属性的名字和@encode编码的类型字符串。对于编码类型字符串的详细内容,查看Type Encoding;对于这个字符串的详细内容,查看Property Type String 和Property Attribute Description Examples。(都在本文中)const char *property_getAttributes(objc_property_t property)同时使用这些函数,使用下面的代码你可以打印出所有与类关联的属性:id LenderClass = objc_getClass(“Lender”);unsigned int outCount, i;objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);for (i = 0; i < outCount; i++) {objc_property_t property = properties[i];fprintf(stdout, “%s %s\n”, property_getName(property), property_getAttributes(property));}属性类型字符串你可以使用property_getAttributes函数查看属性的@encode编码名称和属性中的其他信息。该字符串以字母T开始,紧跟着@encode类型和一个逗号,以字母V结束后跟实例变量名称。在它们中间,属性被指定如下描述符,以逗号分隔。Code MeaningR The property is read-only (readonly).C The property is a copy of the value last assigned (copy).& The property is a reference to the value last assigned (retain).N The property is non-atomic (nonatomic).G\< name > The property defines a custom getter selector name. The name follows the G (for example, GcustomGetter,).S\< name > The property defines a custom setter selector name. The name follows the S (for example, ScustomSetter:,).D The property is dynamic (@dynamic).W The property is a weak reference (__weak).P The property is eligible for garbage collection.t\< encoding > Specifies the type using old-style encoding.# runtime

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值