赞
踩
原理解说:https://www.jianshu.com/p/19f280afcb24
https://blog.csdn.net/jq2530469200/article/details/51886578
<span style="color:#333333">RunTime简称运行时。OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制。</span>
编译时: 即编译器对语言的编译阶段,编译时只是对语言进行最基本的检查报错,包括词法分析、语法分析等等,将程序代码翻译成计算机能够识别的语言(例如汇编等),编译通过并不意味着程序就可以成功运行。
运行时: 即程序通过了编译这一关之后编译好的代码被装载到内存中跑起来的阶段,这个时候会具体对类型进行检查,而不仅仅是对代码的简单扫描分析,此时若出错程序会崩溃。
可以说编译时是一个静态的阶段,类型错误很明显可以直接检查出来,可读性也好;而运行时则是动态的阶段,开始具体与运行环境结合起来。
OC语言的动态性主要体现在三个方面:动态类型(Dynamic typing)、动态绑定(Dynamic binding)和动态加载(Dynamic loading)。
动态类型指的是对象指针类型的动态性,具体是指使用id任意类型将对象的类型确定推迟到运行时,由赋给它的对象类型决定对象指针的类型。另外类型确定推迟到运行时之后,可以通过NSObject的isKindOfClass方法动态判断对象最后的类型(动态类型识别)。也就是说id修饰的对象为动态类型对象,其他在编译器指明类型的为静态类型对象,通常如果不需要涉及到多态的话还是要尽量使用静态类型(原因上面已经说到:错误可以在编译器提前查出,可读性好)。(编译的时候不会检查对象的类型,运行的时候才会检查对象的类型,)
动态绑定指的是方法确定的动态性,具体指的是利用OC的消息传递机制将要执行的方法的确定推迟到运行时,可以动态添加方法。也就是说,一个OC对象是否调用某个方法不是由编译器决定的,而是由运行时决定的;另外关于动态绑定的关键一点是基于消息传递机制的消息转发机制,主要处理应对一些接受者无法处理的消息,此时有机会将消息转发给其他接收者处理,具体见下面介绍。(编译的时候不会决定执行那个方法,运行的时候给对象发送消息,决定执行那个方法)
动态绑定是基于动态类型的,在运行时对象的类型确定后,那么对象的属性和方法也就确定了(包括类中原来的属性和方法以及运行时动态新加入的属性和方法),这也就是所谓的动态绑定了。动态绑定的核心就该是在运行时动态的为类添加属性和方法,以及方法的最后处理或转发,主要用到C语言语法,因为涉及到运行时,因此要引入运行时头文件:#include <objc/runtime.h>
。
消息传递机制:
在OC中方法的调用实际不是对象调用其方法,而是要理解成对象接受消息,方法的调用实际就是告诉对象要干什么,给对象(的指针)传送一个消息。
对于C语言,函数的调用在编译的时候会决定调用哪个函数。
对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
事实证明:
·在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
·在编译阶段,C语言调用未实现的函数就会报错。
///OC方法调用的本质,就是给对象发送消息。
在 Objective-C 中,执行 [object foo] 语句并不会立即执行 foo 这个方法的代码。它是在运行时给 object 发送一条叫 foo 的消息。这个消息,也许会由 object 来处理,也许会被转发给另一个对象,或者不予理睬假装没收到这个消息。多条不同的消息也可以对应同一个方法实现。这些都是在程序运行的时候决定的。
编译时你写的 Objective-C 函数调用的语法都会被翻译成一个 C 的函数调用 - objc_msgSend()。比如,下面两行代码就是等价的:
- [p eat]; // 本质:给对象发送消息
- objc_msgSend(p, @selector(eat));
// 本质:给对象发送消息
objc_msgSend(p, @selector(eat));
消息传递过程:
消息直到运行时才会与方法实现进行绑定。
这里要清楚一点,objc_msgSend
方法看清来好像返回了数据,其实objc_msgSend
从不返回数据,而是你的方法在运行时实现被调用后才会返回数据。
selector
是不是要忽略。比如 Mac OS X 开发,有了垃圾回收就不理会 retain,release 这些函数。selector
的 target 是不是 nil
,Objc 允许我们对一个 nil 对象执行任何方法不会 Crash,因为运行时会被忽略掉。IMP
,先从 cache 里查找,如果找到了就运行对应的函数去执行相应的代码。在消息的传递中,编译器会根据情况在 objc_msgSend
, objc_msgSend_stret
, objc_msgSendSuper
, objc_msgSendSuper_stret
这四个方法中选择一个调用。如果消息是传递给父类,那么会调用名字带有 Super 的函数,如果消息返回值是数据结构而不是简单值时,会调用名字带有 stret 的函数。
动态方法解析会在消息转发机制侵入前执行,动态方法解析器将会首先给予提供该方法选择器对应的
IMP
的机会。如果你想让该方法选择器被传送到转发机制,就让resolveInstanceMethod:
方法返回NO
。
消息转发机制执行前,Runtime 系统允许我们替换消息的接收者为其他对象。通过 - (id)forwardingTargetForSelector:(SEL)aSelector
方法。
- - (id)forwardingTargetForSelector:(SEL)aSelector
- {
- if(aSelector == @selector(mysteriousMethod:)){
- return alternateObject;
- }
- return [super forwardingTargetForSelector:aSelector];
- }
如果此方法返回 nil
或者 self
,则会计入消息转发机制(forwardInvocation:
),否则将向返回的对象重新发送消息。
当动态方法解析不做处理返回 NO
时,则会触发消息转发机制。这时 forwardInvocation:
方法会被执行,我们可以重写这个方法来自定义我们的转发逻辑:
- - (void)forwardInvocation:(NSInvocation *)anInvocation
- {
- if ([someOtherObject respondsToSelector:
- [anInvocation selector]])
- [anInvocation invokeWithTarget:someOtherObject];
- else
- [super forwardInvocation:anInvocation];
- }
唯一参数是个 NSInvocation
类型的对象,该对象封装了原始的消息和消息的参数。我们可以实现 forwardInvocation:
方法来对不能处理的消息做一些处理。也可以将消息转发给其他对象处理,而不抛出错误。
注意:参数
anInvocation 是从哪来的?
在forwardInvocation:
消息发送前,Runtime 系统会向对象发送methodSignatureForSelector:
消息,并取到返回的方法签名用于生成 NSInvocation 对象。所以重写forwardInvocation:
的同时也要重写methodSignatureForSelector:
方法,否则会抛异常。
当一个对象由于没有相应的方法实现而无法相应某消息时,运行时系统将通过 forwardInvocation:
消息通知该对象。每个对象都继承了 forwardInvocation:
方法。但是, NSObject
中的方法实现只是简单的调用了 doesNotRecognizeSelector:
。通过实现自己的 forwardInvocation:
方法,我们可以将消息转发给其他对象。
forwardInvocation:
方法就是一个不能识别消息的分发中心,将这些不能识别的消息转发给不同的接收对象,或者转发给同一个对象,再或者将消息翻译成另外的消息,亦或者简单的“吃掉”某些消息,因此没有响应也不会报错。这一切都取决于方法的具体实现。
注意:
forwardInvocation:
方法只有在消息接收对象中无法正常响应消息时才会被调用。所以,如果我们向往一个对象将一个消息转发给其他对象时,要确保这个对象不能有该消息的所对应的方法。否则,forwardInvocation:
将不可能被调用。
==========================
使用消息机制前提,必须导入#import <objc/message.h>
消息机制简单使用
Person *p = [[Person alloc] init]; // 调用对象方法
[p eat]; // 本质:给对象发送消息
objc_msgSend(p, @selector(eat));
- // 调用类方法的方式:两种
- // 第一种通过类名调用
- [Person eat]; // 第二种通过类对象调用
- [[Person class] eat];
-
- // 用类名调用类方法,底层会自动把类名转换成类对象调用
- // 本质:让类对象发送消息
- objc_msgSend([Person class], @selector(eat));
// 调用类方法的方式:两种
// 第一种通过类名调用
[Person eat]; // 第二种通过类对象调用
[[Person class] eat];
// 用类名调用类方法,底层会自动把类名转换成类对象调用
// 本质:让类对象发送消息
objc_msgSend([Person class], @selector(eat));
1.将某些OC代码转为运行时代码,探究底层,比如block的实现原理(上边已讲到);
2.拦截系统自带的方法调用(Swizzle 黑魔法),比如拦截imageNamed:、viewDidLoad、alloc;
3.实现分类也可以增加属性;
4.实现NSCoding的自动归档和自动解档;
5.实现字典和模型的自动转换。
=================动态添加成员变量======================================
- /**
- runtime动态添加属性
- */
- #import <Foundation/Foundation.h>
- #import <objc/message.h>
- NS_ASSUME_NONNULL_BEGIN
-
- @interface NSObject (Property)
- // @property分类: 只会生成get,set方法声明,不会生成方法实现,并且不会生成_name私有属性
- @property(nonatomic,copy) NSString *userName;
- @end
-
- NS_ASSUME_NONNULL_END
-
-
- /**
- runtime动态添加属性
- */
- #import "NSObject+Property.h"
- // 属性名称
- static const char * key = "name";//必须用char *,用NSString会报错
- @implementation NSObject (Property)
- // set方法
- - (void)setUserName:(NSString *)userName
- {
- // 让这个字符串与当前对象产生联系
- /*
- param1:object:给哪个对象添加属性
- param2:key:属性名称
- param3:value:属性值
- param4:policy:保存策略
- */
- objc_setAssociatedObject(self,key, userName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
-
- // get 方法
- - (NSString *)userName
- {
- return objc_getAssociatedObject(self, key);
- }
-
- @end
/*
1. 分类不可以扩充成员变量
2. 分类可以扩充属性,但是属性不可以保存值.因为分类中没有成员变量.我们需要重写setter和getter
3. 分类可以扩充方法
*/
//设置值
- (void)setLastUrl:(NSString *)lastUrl
{
/*
1. 需要关联的对象 self
2. 需要关联的对象的属性的键值(key)
3. 需要关联的对象的属性lasturl
4. 需要关联的对象的属性的修饰符(策略)
*/
objc_setAssociatedObject(self,"key", lastUrl,OBJC_ASSOCIATION_COPY_NONATOMIC);
}
//获取值
- (NSString *)lastUrl
{
/*
1. 需要关联的对象 self
2. 需要关联的对象的属性的键值(key)
*/
returnobjc_getAssociatedObject(self,"key");
}
=========runtime 实现方法交换:=================
- /**
- runtime实现交换方法
- */
-
- #import <Foundation/Foundation.h>
-
- NS_ASSUME_NONNULL_BEGIN
-
- @interface UIImage (RuntimeExchangeMethod)
- + (UIImage *)xmg_imageNamed:(NSString *)name;
- @end
-
- NS_ASSUME_NONNULL_END
-
-
- //
-
- #import "UIImage+RuntimeExchangeMethod.h"
- #import <objc/message.h>
- @implementation UIImage (RuntimeExchangeMethod)
- // 把类加载进内存的时候调用,只会调用一次
- + (void)load
- {
- // self -> UIImage
- // 获取imageNamed
- // 获取哪个类的方法
- // SEL:获取哪个方法
- Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
- // 获取xmg_imageNamed
- Method xmg_imageNamedMethod = class_getClassMethod(self, @selector(xmg_imageNamed:));
-
- // 交互方法:runtime
- method_exchangeImplementations(imageNamedMethod, xmg_imageNamedMethod);
- // 调用imageNamed => xmg_imageNamedMethod
- // 调用xmg_imageNamedMethod => imageNamed
- }
-
-
-
- // 在分类中,不要重写系统方法,一旦重写,把系统方法实现给干掉
-
- //+ (UIImage *)imageNamed:(NSString *)name
- //{
- // // super -> 父类NSObject
- //
- //}
-
- // 1.加载图片
- // 2.判断是否加载成功
- + (UIImage *)xmg_imageNamed:(NSString *)name
- {
- // 图片
- UIImage *image = [UIImage xmg_imageNamed:name];
-
- if (image) {
- NSLog(@"加载成功");
- } else {
- NSLog(@"加载失败");
- }
-
- return image;
- }
- @end
#import <UIKit/UIKit.h>
@interface UIImage (XL)
+ (instancetype)imageWithName:(NSString *)name;
@end
///////////////
#import "UIImage+XL.h"
#import <objc/runtime.h>
@implementation UIImage (XL)
+ (void)load
{
Method m1 =class_getClassMethod([UIImageclass],@selector(imageWithName:));
Method m2 =class_getClassMethod([UIImageclass],@selector(imageNamed:));;
method_exchangeImplementations(m1, m2);
}
+ (instancetype)imageWithName:(NSString *)name
{
UIImage *image = [UIImage imageWithName:imageName];
if (image ==nil) {
image = [UIImageimageWithName:name];
}
return image;
}
@end
===================================
#import <Foundation/Foundation.h>
@interface NSMutableArray (XL)
@end
///////////
#import "NSMutableArray+XL.h"
#import <objc/runtime.h>
@implementation NSMutableArray (XL)
+ (void)load
{
Method m1 =class_getInstanceMethod(NSClassFromString(@"__NSArrayM"),@selector(cz_addObjct:));
Method m2 =class_getInstanceMethod(NSClassFromString(@"__NSArrayM"),@selector(addObject:));;
method_exchangeImplementations(m1, m2);
}
- (void)cz_addObjct:(id)objc
{
if (objc ==nil) {
[selfcz_addObjct:@"you are sb! is nil "];
return;
}
[selfcz_addObjct:objc];
}
@end
===================================
交换原理:当一个类加载到内存中时,需要给每个类方法或对象方法生成方法映射表,需要调用方法的时候根据方法编号(SEL类型的方法选择器)到方法映射表中去寻查询,然后找到对应的函数实现;
·交换之前:
·交换之后:
==========动态的添加方法================
-
- /**
- runtimed动态添加方法,
- 使用:
- AddDynamicMethodVC *addDynamicMethodVC=[[AddDynamicMethodVC alloc]init];
- [addDynamicMethodVC performSelector:@selector(eat)];//调用动态添加的方法时要用perfermSelector
- */
- #import <UIKit/UIKit.h>
-
- NS_ASSUME_NONNULL_BEGIN
-
- @interface AddDynamicMethodVC : UIViewController
-
- @end
-
- NS_ASSUME_NONNULL_END
-
-
-
- /**
- 下面是动态添加方法的实现
- */
- #import "AddDynamicMethodVC.h"
- #import <objc/message.h>
- @interface AddDynamicMethodVC ()
-
- @end
-
- @implementation AddDynamicMethodVC
-
- - (void)viewDidLoad {
- [super viewDidLoad];
- // Do any additional setup after loading the view.
- }
- // 这个是新添加的方法,默认方法都有两个隐式参数,
- void eat(id self,SEL sel)
- {
- NSLog(@"%@--------- %@",self,NSStringFromSelector(sel));
- }
-
- // 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
- // 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
- + (BOOL)resolveInstanceMethod:(SEL)sel
- {
-
- if (sel== NSSelectorFromString(@"eat")) {
- // 动态添加eat方法
-
- // 第一个参数:给哪个类添加方法
- // 第二个参数:添加方法的方法编号
- // 第三个参数:添加方法的函数实现(函数地址)
- // 第四个参数:函数的类型,(返回值+参数类型) v指返回值void @指对象->self等参数 :表示SEL->_cmd
- class_addMethod(self, NSSelectorFromString(@"eat"), (IMP)eat, "v@:");
-
- }
-
- return [super resolveInstanceMethod:sel];
- }
-
-
-
- @end
一个c语言函数方法都有两个隐式参数:
void dynamicMethodIMP(id self, SEL _cmd)
{
NSLog(@"//动态添加的方法在这里实现");
}
- OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp,
- const char *types)
如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决;
在OC中,所有的控件(textFiled或者button等控件), 数组, 数据等都是以懒加载的形式加载的.真正使用的时候才会加载,或者添加方法.动态添加的方法的作用就是去处理未实现的实例方法或者是类方法.它的调用时刻: 只要我们调用了一个不存在的方法时,它就会动态添加方法.
动态添加方法之前我们还需要判断当前的方法有没有实现,如果没有实现才需要动态添加方法.
作为一种动态编程语言,Objective-C 拥有一个运行时系统来支持动态创建类,添加方法、进行消息传递和转发。
Objective-C 运行时会调用 +resolveInstanceMethod: 或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回 YES, 那运行时系统就会重新启动一次消息发送的过程。
如果 resolveInstanceMethod: 方法返回 NO,运行时就会进行下一步:消息转发(Message Forwarding)。
- 动态去判断下eat方法有没有实现,如果没有实现,动态添加.
- // 作用:处理未实现的对象方法
- // 调用时刻:只要调用了一个不存在的对象方法就会调用
- // sel:就是未实现方法编号
-
- // 判断对象方法有没有实现
- +(BOOL)resolveInstanceMethod:(SEL)sel
-
- // 判断类方法有没有实现
- + (BOOL)resolveClassMethod:(SEL)sel
Class cls
cls 参数表示需要添加新方法的类。
1 |
|
name 参数表示 selector 的方法名称,可以根据喜好自己进行命名。
1 |
|
imp 即 implementation ,表示由编译器生成的、指向实现方法的指针。也就是说,这个指针指向的方法就是我们要添加的方法。
1 |
|
最后一个参数 *types 表示我们要添加的方法的返回值和参数。
-
- / 参数解释:
- // Class;给哪个类添加方法
- // SEL:要添加的方法
- // IMP:方法实现,函数名(实现却是在另外一个方法,和添加的方法不一样)
- // types:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
- class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types)
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
-(void)resolveThisMethodDynamically;//动态添加的是这个方法,但是实现却是在另外一个方法中
@end
==================
在.m中
- (void)viewDidLoad {
[superviewDidLoad];
[selfresolveThisMethodDynamically];//调用的是没有实现的方法,是动态添加的
}
//动态添加的方法在这里实现
void dynamicMethodIMP(idself, SEL_cmd)
{
NSLog(@"//动态添加的方法在这里实现");
}
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL ==@selector(resolveThisMethodDynamically))
{
class_addMethod([selfclass], aSEL, (IMP)dynamicMethodIMP, "v@:");
returnYES;
}
return [superresolveInstanceMethod:aSEL];
}
===========获取去对象的属性和方法=============
-
- /**
- runtime获取成员变量,以及方法等
- */
- #import <Foundation/Foundation.h>
-
- NS_ASSUME_NONNULL_BEGIN
-
- @interface NSObject (RuntimeGetProperty)
-
- @end
-
- NS_ASSUME_NONNULL_END
-
-
- #import "NSObject+RuntimeGetProperty.h"
- #import <objc/message.h>
- @implementation NSObject (RuntimeGetProperty)
- -(void)getProperty{
- unsigned int count;
- Method *methods = class_copyMethodList([UITextView class], &count);//获取所有方法
- for (int i = 0; i < count; i++) {
- Method method = methods[i];
- SEL selector = method_getName(method);//获取方法
- NSString *name = NSStringFromSelector(selector);
-
- NSLog(@"method_getName:%@",name);
- }
-
- unsigned int numIvars;
- Ivar *vars = class_copyIvarList([UITextView class], &numIvars);//获取所有的属性
- NSString *key=nil;
- for(int i = 0; i < numIvars; i++) {
-
- Ivar thisIvar = vars[i];
- const char *memberName = ivar_getName(thisIvar);//获取属性名
- key = [NSString stringWithUTF8String:memberName];
- NSLog(@"variable_name :%@", key);
-
- const char * type = ivar_getTypeEncoding(thisIvar);//获取变量类型
- NSString *typeStr = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
- if ([typeStr isEqualToString:@"@\"NSString\""]){
- //修改属性的值
- object_setIvar(self, thisIvar, @"abc");
- }
-
- //获取指定名称的成员变量
- Ivar ivar = class_getInstanceVariable([self class], memberName);
-
- }
- free(vars);
- }
- @end
runtime的方法:
- unsigned int propertyCount = 0;
- objc_property_t *properties = class_copyPropertyList(klass,
- &propertyCount);//获取所有的属性对象
-
-
- for (unsigned int i = 0; i < propertyCount; ++i) {
- objc_property_t property = properties[i];
- const char * name = property_getName(property);//获取属性名字
- const char * attributes = property_getAttributes(property);//获取属性类型
- }
objc_property_t *properties = class_copyPropertyList(klass,
&propertyCount);//获取所有的属性对象
for (unsigned int i = 0; i < propertyCount; ++i) {
objc_property_t property = properties[i];
const char * name = property_getName(property);//获取属性名字
const char * attributes = property_getAttributes(property);//获取属性类型
}
- 获得某个类的所有成员变量(outCount 会返回成员变量的总数)
参数:
1、哪个类
2、放一个接收值的地址,用来存放属性的个数
3、返回值:存放所有获取到的属性,通过下面两个方法可以调出名字和类型
Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
- 获得成员变量的名字(带下划线 _name)
const char *ivar_getName(Ivar v)
- 获得成员变量的类型
const char *ivar_getTypeEndcoding(Ivar v)
为了帮助运行时系统,编译器将每个方法中的返回和参数类型进行编码,并将该字符串与该方法选择器关联。在其他情况下,编码体系也是很有用的,所以编码体系是带有@encode()编译指令的工公共的可用的。当给一个指定类型,@encode()返回指定的类型的字符串编码。这个类型可以是任何类型,可以是基本类型,如int型指针,可以是一个标记结构或联合,或类名,可以被C语言的sizeof()运算符作为参数使用。
下面的表格列出了编码类型。注意当对一个对象归档或者分发时,他们中的许多代码与你使用的代码重叠。然而,这些列表中的编码在你归档的时候不能使用他们,你可能想要在归档使用那些不是@encode()生成的代码。
编码类型
- 计算机唯一能识别的语言是机器语言,高级编程语言不能被直接识别,需要先编译为汇编语言,再由汇编语言编译
- 为机器语言才能被计算机识别。而 Objective-C语言不能被直接编译为汇编语言,它必须先编译为C语言,然后再
- 编译为汇编语言,最后再由汇编语言编译为机器语言才能被计算机识别。 从OC到C语言的过渡就是由runtime来实
- 现的。我们使用OC进行面向对象开发,但是C语言更多的是面向过程开发,这就需要将面向对象的类转变为面向过
- 程的结构体。
-
- . Runtime 是iOS中的一个运行时系统,是苹果用C语言和汇编语言编写的一套底层纯C语言API。
-
- 2. Runtime正是 Objective-C这门动态语言的核心(数据类型的确定由编译时推迟到运行时)。从OC到C语言的过
- 渡就是由Runtime来实现的。
-
- 3. OC代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式(objc_msgSend)。
-
- 4. OC类、对象和方法等都会被Runtime转化成C语言中的结构体。
-
- 5. 在Runtime中,id 是一个指向 objc_object 结构体的指针;class 是一个指向 objc_class 结构体的指针
-
- 6. SEL 是一个指向 objc_selector 结构体的指针; Ivar 是一个指向 objc_ivar 的结构体的指针
-
- 7. Method 是一个指向 objc_method 的结构体的指针;IMP 是一个函数指针
-
- 使用:
- 1. 动态方法交换 (method_exchangeImplementations)
- 2. 给分类添加属性 (objc_setAssociatedObject 和 objc_getAssociatedObject)
- 3. 获取类的详细信息
- 3.1 属性列表(class_copyPropertyList)
- 3.2 获取成员变量(class_copyIvarList)
- 3.3 获取所有方法(class_copyMethodList)
- 3.4 获取当前遵循的所有协议(class_copyProtocolList)
- 4. 解决同一方法高频率调用的效率问题
- 5. 方法动态解析与消息转发
- 5.1 动态添加方法
- 5.2 解决方法无响应崩溃问题
- 6. 动态操作属性
- 6.1 动态修改属性变量
- 6.2 实现 NSCoding 的自动归档和解档
- 6.3 实现字典与模型的转换
https://www.jianshu.com/p/51b20fcdf7bc
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。