查看: 242|回复: 0

[IOS开发教程] iOS - Runtime

发表于 7 天前
一、Runtime简介 1.1 简单介绍
  • Runtime简称运行时。OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制;
  • 对于C语言,函数的调用在编译的时候会决定调用哪个函数;
  • 对于OC的函数,属于动态调用的过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用;
  • 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错;在编译阶段,C语言调用未实现的函数就会报错。
1.2 可以用来做什么
  • 在程序运行的过程中,动态的创建一个类(比如KVO的底层实现)
  • 在程序运行过程中,动态地为某个类添加属性/方法,可以用于封装框架(想怎么改就怎么改),这也是我们runtime机制的主要运用方向
  • 遍历一个类中所有的成员变量(属性)/所有方法。比如字典转模型:利用runtime遍历模型对象的所有属性,根据属性名从字典中取出对应的值,设置到模型的属性上;还有归档和解档,利用runtime遍历模型对象的所有属性。
二、Runtime常用的方法
  1. // 获取类
  2. Class PersonClass = object_getClass([Person class]);
  3. // SEL是selector在Objc中的标示 method 是方法名
  4. SEL oriSEL = @selector(method);
  5. // 获取类方法
  6. Method oriMethod = class_getClassMethod(Class cls , SEL name);
  7. // 获取实例方法
  8. Method insMethod = class_getInstanceMethod(Class cls, SEL name);
  9. // 添加一个新方法
  10. BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
  11. // 替换原方法实现
  12. class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
  13. // 交换两个方法
  14. method_exchangeImplementations(oriMethod, cusMethod);
  15. // 获取一个类的属性列表(返回值是一个数组)
  16. objc_property_t *propertyList = class_copyPropertyList([self class], &count);
  17. // 获取一个类的方法列表(返回值是一个数组)
  18. Method *methodList = class_copyMethodList([self class], &count);
  19. // 获取一个类的成员变量列表 (返回值是一个数组)
  20. Ivar *ivarList = class_copyIvarList([self class], &count);
  21. // 获取成员变量的名字
  22. const char *ivar_getName(Ivar v)
  23. // 获取成员变量的类型
  24. const char *ivar_getTypeEndcoding(Ivar v)
  25. // 获取一个类的协议列表(返回值是一个数组)
  26. __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
  27. // set方法
  28. //将值value 跟对象object 关联起来(将值value 存储到对象object 中)
  29. //参数 object:给哪个对象设置属性
  30. //参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节
  31. //参数 value:给属性设置的值
  32. //参数policy:存储策略 (assign 、copy 、 retain就是strong)
  33. void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy)
  34. // get方法,利用参数key将对象object中存储的对应值取出来
  35. id objc_getAssociatedObject(id object , const void *key)
复制代码
三、Runtime相关术语的数据结构

我感觉还是有必要看一下理论知识,如果不看跳过---

3.1 SEL

它是selector(方法选择器)在Objc中的标示(Swift中是Selector类)。Objc在相同的类中不会有命名相同的两个方法。selector对方法名进行包装,以便找到对应的方法实现,它的数据结构:

  1. // 它是个映射到方法的C语言字符串,可以通过Objc编译器命令@selector 或者Runtime 系统的 sel_registerName 函数来获取一个 SEL 类型的方法选择器。<br>typedef struct objc_selector *SEL;
复制代码
3.2 id

id 是一个参数类型,它是指向某个类的实例的指针。定义如下:

  1. // 我们可以看到 objc_object 结构体包含一个isa指针,根据isa指针就可以找到对象所属的类。<br>// isa 指针在代码运行时并不总指向实例对象所属的类型,所以不能依靠它来确定类型,要想确定类型还是需要用对象的 -class方法。<br>// KVO的实现原理就是将被观察对象的isa指针指向了一个中间动态创建的中间类,而不是真实类型。<br>typedef struct objc_object *id;
  2. struct objc_object {Class isa;};
复制代码
3.3 Class
  1. typedef struct objc_class *Class;
复制代码

Class其实是指向objc_class 结构体的指针。objc_class 的数据结构如下:

  1. struct objc_class {
  2. Class isa OBJC_ISA_AVAILABILITY;
  3. #if !__OBJC2_
  4. Class super_class// 父类
  5. const char *name // 类名
  6. long version // 类的版本信息,默认为0<br>
  7. long info // 类信息,供运行期间使用的一些位标识
  8. long instance_size // 类的实例变量大小
  9. struct objc_ivar_list *ivars // 累的成员变量链表
  10. struct objc_method_list **methodLists // 方法链表
  11. struct objc_cache *cache // 方法缓存
  12. struct objc_protocol_list *protocols // 协议链表
  13. #endif
  14. } OBJC2_UNAVAILABLE;
复制代码

从 objc_class 可以看到,一个运行时类中关联了它的父类指针、类名、成员变量、方法、缓存以及附属的协议。

其中 objc_ivar_list 和 objc_method_list 分别是成员变量列表和方法列表:

  1. // 成员变量列表
  2. struct objc_ivar_list {
  3. int ivar_count OBJC2_UNAVAILABLE;
  4. #ifdef __LP64__
  5. int space OBJC2_UNAVAILABLE;
  6. #endif
  7. /* variable length structure */
  8. struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
  9. } OBJC2_UNAVAILABLE;
  10. // 方法列表
  11. struct objc_method_list {
  12. struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
  13. int method_count OBJC2_UNAVAILABLE;
  14. #ifdef __LP64__
  15. int space OBJC2_UNAVAILABLE;
  16. #endif
  17. /* variable length structure */
  18. struct objc_method method_list[1] OBJC2_UNAVAILABLE;
  19. }
复制代码

由此可见,我们可以动态修改 *methodList 的值来添加成员方法,这也是 Category 实现的原理,同样解释了 Category 不能添加属性的原因。

objc_ivar_list 结构体用来存储成员变量的列表,而 objc_ivar 则是存储了单个成员变量的信息;同理,objc_method_list 结构体存储着方法数组的列表,而单个方法的信息则由 objc_method 结构体存储。

值得注意的是,objc_class 中也有一个 isa 指针,这说明 Objc 类本身也是一个对象。为了处理类和对象的关系,Runtime库创建了一种叫做 Meta Class(元类) 的东西,类对象所属的类就叫做元类。元类表述了类对象本身所具备的元数据。

我们所熟悉的类方法,就源自于 Meta Class。我们可以理解为类方法就是类对象的实例方法。每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。

当你发出一个类似 [NSObject alloc] (类方法)的消息时,实际上,这个消息被发送给了一个类对象,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类的实例,所有元类的isa指针最终都指向根元类。

所以当 [NSObject alloc] 这条消息发送给类对象的时候,运行时代码 objc_msgSend() 会去它元类中查找能够响应消息的方法实现,如果找到了,就会对这个类对象执行方法调用。

3.4 Method

Method 是一个方法结构体的指针:

  1. typedef struct objc_method *Method;
  2. struct objc_method {
  3. SEL method_name OBJC2_UNAVAILABLE;
  4. char *method_types // 方法的返回值,和各个参数类型的字符串描述;
  5. IMP method_imp OBJC2_UNAVAILABLE;
  6. }
复制代码

objc_method 存储了方法名,方法类型和方法实现:

  • 方法名类型为SEL
  • 方法类型 method_types 是个char指针,存储方法的参数类型和返回值类型
  • method_imp 指向了方法的实现,本质是一个函数指针
3.5 Ivar

Ivar 是表示成员变量的类型。

  1. typedef struct objc_ivar *Ivar;
  2. struct objc_ivar {
  3. char *ivar_name OBJC2_UNAVAILABLE;
  4. char *ivar_type OBJC2_UNAVAILABLE;
  5. int ivar_offset OBJC2_UNAVAILABLE;
  6. #ifdef __LP64__
  7. int space OBJC2_UNAVAILABLE;
  8. #endif
  9. }<br><br>其中,有成员变量的名称,类型,空间, ivar_offset是基地址偏移字节
复制代码
3.6 IMP

IMP在objc.h中的定义是:

  1. typedef id (*IMP)(id, SEL,...);
复制代码

它就是一个函数指针,这是由编译器生成的。当你发起一个ObjC消息之后,最终它会执行的那段代码就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。

如果得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法,这在后面Cache中会提到。

你会发现 IMP 指向的方法与 objc_msgSend 函数类型相同,参数都包含 id 和 SEL 类型。每个方法对应一个 SEL 类型的方法选择器,而每个实例对象中的SEL对应的方法实现肯定是唯一的,通过id和 SEL 参数就能确定唯一的方法实现地址,而一个确定的方法也只有唯一的一组 id 和 SEL 参数。

3.7 Cache

Cache 定义如下:

  1. typedef struct objc_cache *Cache
  2. struct objc_cache {
  3. unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
  4. unsigned int occupied OBJC2_UNAVAILABLE;
  5. Method buckets[1] OBJC2_UNAVAILABLE;
  6. };
复制代码

Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找到能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。

Runtime 系统会把被调用的方法存到 Cache 中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的收就会效率更高。

四、消息发送和消息转发 4.1 消息发送

消息发送举例:

  1. [person read:book];
复制代码

会被编译成:

  1. objc_msgSend(person, @selector(read:), book);
复制代码

objc_msgSend的具体流程如下:

  • 通过 isa 指针找到所属的类
  • 查找类的 cache 列表,如果没有则下一步
  • 查找类的方法列表
  • 如果能找到与选择子名称相符的方法,就跳至其实现代码
  • 找不到的话,就沿着继承体系继续向上查找
  • 如果能够找到与选择子名称相符的方法,就调至其实现代码
  • 如果还没找到,就执行 “消息转发”
4.2 消息转发

这里会给接收者最后一次机会把这个方法处理了,搞不定程序就会崩溃

  1. - (void)forwardInvocation:(NSInvocation *)invocation
  2. // invocation : 封装了与那条尚未处理的消息相关的所有细节的对象
复制代码

在这里能做的比较现实的事就是:在触发消息前,先以某种方式改变消息内容,比如追加另外一个参数,或是改变消息等等。实现此方法时,如果发现某调用操作不应该由本类处理,可以调用超类的同名方法,则继承体系中的每个类都有机会处理该请求,知道 NSObject ,如果NSObject 搞不定,则还会调用 doesNotRecognizeSelector: 来抛出异常,此时你就会在控制台看到熟悉的 unrecognized selector sent to instance..1

上面这4个方法均是模板方法,开发者可以override,由runtime来调用。最常见的实现消息转发,就是重写3和4。

五、Runtime的作用 5.1 发送消息
  • 方法调用的本质,就是让对象发送消息;
  • objc_msgSend,只有对象才能发送消息,因此以objc开头;
  • 使用消息机制的前提,必须导入 #import
  • 消息机制简单使用
  1. // 比如创建一个Person对象,我们通常会如下写
  2. Person *p = [[Person alloc] init];
  3. // 其实方法的调用就是向这个对方发送消息,所以上面的代码底层实现的主要代码就是:
  4. // 由于iOS5以后,苹果不支持这样写,所以需要在 Build Setting 中 将 Enable Strick Checking 设置为 NO
  5. Person *p = objc_msgSend(objc_msgSend(objc_getClass("Person"),sel_registerName("alloc")),sel_registerName("init"));
复制代码

消息机制的原理:对象根据方法编号SEL去映射表查找对应的方法实现。

5.2 交换方法

简单来说就是将两个方法实现进行交换,比如系统自带的方法功能不够,我们想给系统自带的方法扩展一些功能,并且保持原有的功能,我们就可以使用Runtie,交换方法。

比如:我们想在 NSMutableArray 添加新对象的时候,进行置空判断。

  1. // 需求:给 addObject: 方法提供功能,当添加一个新的对象时,进行为空判断
  2. // 1 先弄个分类 定义一个 能添加新对象又能进行为空判断的方法
  3. // 2 和系统的方法进行交换
  4. #import <objc/message.h>
  5. @implementation NSMutableArray (Category)
  6. // 加载分类到内存的时候调用,会比main函数先调用
  7. +(void)load{
  8. // 交换方法 获取类名,如果使用[self class] 获取的是 NSMutableArray ,因为NSMutableArray 是类簇,这个可以看看了解一下
  9. Class selfClass = NSClassFromString(@"__NSArrayM");
  10. // 获取系统 addObject: 方法地址
  11. SEL sel_add = @selector(addObject:);
  12. Method addObject1 = class_getInstanceMethod(selfClass, sel_add);
  13. // 获取自定义 safeAddObject: 方法地址
  14. SEL sel_safeAdd = @selector(safeAddObject:);
  15. Method safeAddObject2 = class_getInstanceMethod(selfClass, sel_safeAdd);
  16. // 然后交换方法地址,相当于交换实现方式
  17. method_exchangeImplementations(addObject1,safeAddObject2);
  18. }
  19. // 我们这里定义一个新的添加对象的方法 , 不能在分类中重写系统方法 addObject 这样会覆盖系统的addObject ,并且分类中也不能使用 super
  20. - (void)safeAddObject:(id)anObject{
  21. // 我们可以加判断,如果对象为空
  22. if (!anObject) {
  23. NSLog(@"对象为空,添加新对象失败");
  24. return;
  25. }
  26. // 这里调用 safeAddObject 其实是调用 addObject: 因为在load 方法中,系统的方法和你自己实现的方法已经交换了
  27. [self safeAddObject:anObject];
  28. }
复制代码
5.3 动态添加方法

如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。

  1. NSMutableArray *mArr = [NSMutableArray array];
  2. // WCE_addObject 方法只是在分类中声明,但是并没有实现
  3. [mArr performSelector:@selector(WCE_addObject)];
  4. // 现在我们在分类中默认去实现
  5. / 当对象调用一个未实现的对象方法,会进入这个方法,并把对应的方法列表传过来
  6. +(BOOL)resolveInstanceMethod:(SEL)sel{
  7. if (sel == @selector(WCE_addObject)) {
  8. // 动态添加 WCE_addObject 方法
  9. // 第一个参数:给哪个类添加方法
  10. // 第二个参数:添加方法的方法编号
  11. // 第三个参数:添加方法的函数实现(函数地址) 这里需要转换一下
  12. // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象-self SEL:_cmd
  13. class_addMethod(self, @selector(WCE_addObject), (void *)WCE_addObject, "v@:");
  14. }
  15. return [super resolveInstanceMethod:sel];
  16. }
  17. // 默认方法都有两个隐式参数 调用方法者 调用的方法
  18. void WCE_addObject(id self,SEL sel){
  19. NSLog(@"这个方法被默认执行了");
  20. }
复制代码
5.4 给分类添加属性

给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。

比如我们想给NSMutableArr类动态添加一个wce_name属性:

  1. // wce_name 是动态的给NSMutableArray 添加的属性
  2. NSMutableArray *mArr = [NSMutableArray array];
  3. mArr.wce_name = @"这是我的东西哦";
  4. NSLog(@"%@",mArr.wce_name);
  5. // 下面是具体实现
  6. // 定义关联的key
  7. static const char *key = "wce_name";
  8. @implementation NSMutableArray (Property)
  9. -(NSString *)wce_name{
  10. // 根据关联的key,获取关联的值
  11. return objc_getAssociatedObject(self, key);
  12. }
  13. -(void)setWce_name:(NSString *)wce_name{
  14. // 第一个参数:给哪个对象添加关联
  15. // 第二个参数:关联的key,通过这个key获取
  16. // 第三个参数:关联的value
  17. // 第四个参数:关联的策略 retain copy strong
  18. objc_setAssociatedObject(self, key, wce_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
  19. }
复制代码
5.5 获取对象的所有属性

比如我们想把一个字典转化成对象,可以使用KVC,setValuesForKeysWithDictionary: ,但是必须要保证:模型中的属性和字典中key一一对应,如果不一致,就会报错。

所以,我们可以使用Runtime,遍历对象中的所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。建一个NSObject分类,专门字典转模型,以后所有的模型都可以通过这个分类转。

  1. // 字典转model
  2. + (instancetype)WCE_objectWithKeyValues:(NSDictionary *)dict{
  3. NSArray *propertyList = [self WCE_propertyList];
  4. id object = [[self alloc] init];
  5. for (NSString *key in propertyList) {
  6. if (dict[key]) {
  7. [object setValue:dict[key] forKey:key];
  8. }
  9. }
  10. return object;
  11. }
  12. // 获取所有的属性列表
  13. + (NSArray *)WCE_propertyList{
  14. NSMutableArray *mArr = [NSMutableArray array];
  15. // 获取所有的属性列表
  16. unsigned int count = 0;
  17. // 获取各种所有成员属性,无论是属性还是成员变量。私有的也可以,这里假设Person只有三个属性<br>  // 如果只想获取属性可以用 class_copyPropertyList
  18. Ivar *ivarList = class_copyIvarList(self, &count);
  19. for (int i = 0; i < count; i++) {
  20. Ivar ivar = ivarList[i];
  21. // 获取成员属性名
  22. NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
  23. // 因为获取的是属性,成员变量就是 _属性名,这里是去除下划线
  24. name = [name substringFromIndex:1];
  25. // 添加到数组
  26. [mArr addObject:name];
  27. }
  28. return mArr;
  29. }
复制代码
5.6 动态添加一个类

KVO(键值观察)的实现就是利用的runtime能够动态添加类

当你对一个对象进行观察时,系统会自动新建一个类继承自源类,然后重写被观察属性的setter方法,然后重写的setter方法会负责在调用原setter方法前后通知观察者,然后把原对象的isa指针指向这个新类,我们知道,对象是通过isa指针去查找自己是属于哪个类,并去所在类的方法列表中查找方法的,所以这个时候这个对象就自然地变成了新类的实例对象。

就像KVO一样,系统是在程序运行的时候,根据你要监听的类,动态添加一个新类继承自该类,然后重写原类的setter方法并在里面通知observer的。

那么,如何动态添加一个类呢?

  1. // 创建一个类(size_t extraBytes该参数通常指定为0, 该参数是分配给类和元类对象尾部的索引ivars的字节数。)
  2. Class clazz = objc_allocateClassPair([NSObject class], "GoodPerson", 0);
  3. // 添加ivar
  4. // @encode(aType) : 返回该类型的C字符串
  5. class_addIvar(clazz, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
  6. class_addIvar(clazz, "_age", sizeof(NSUInteger), log2(sizeof(NSUInteger)), @encode(NSUInteger));
  7. // 注册该类
  8. objc_registerClassPair(clazz);
  9. // 创建实例对象
  10. id object = [[clazz alloc] init];
  11. // 设置ivar
  12. [object setValue:@"Tracy" forKey:@"name"];
  13. Ivar ageIvar = class_getInstanceVariable(clazz, "_age");
  14. object_setIvar(object, ageIvar, @18);
  15. // 打印对象的类和内存地址
  16. NSLog(@"%@", object);
  17. // 打印对象的属性值
  18. NSLog(@"name = %@, age = %@", [object valueForKey:@"name"], object_getIvar(object, ageIvar));
  19. // 当类或者它的子类的实例还存在,则不能调用objc_disposeClassPair方法
  20. object = nil;
  21. // 销毁类
  22. objc_disposeClassPair(clazz);
复制代码

六、项目中的具体使用 6.1 替换系统的方法

也就是将系统的方法和自己定义的方法替换,比如可变数组添加新对象、按标取值时的增加的防止崩溃的判断,当我们接手一个新项目时,想更快了解界面跳转,可以替换系统的viewWillAppear:方法。

6.2 实现分类也可以添加属性

MJRefresh 就是动态的给UIScrollView增加的mj_header和mj_footer

6.3 字典和模型的自动转换

其实主要的也是使用了,遍历属性这个方法。

6.4 动态的增加方法

动态的为某个类增加一个方法,可以对某一个功能,做扩展。

6.5 自动归档和自动解档

我们假设有个Moviel类,有三个属性 id name url ,通常在归解档的时候,我们都会

实现这两个协议方法:

  1. - (void)encodeWithCoder:(NSCoder *)aCoder
  2. {
  3. [aCoder encodeObject:_movieId forKey:@"id"];
  4. [aCoder encodeObject:_movieName forKey:@"name"];
  5. [aCoder encodeObject:_pic_url forKey:@"url"];
  6. }
  7. - (id)initWithCoder:(NSCoder *)aDecoder
  8. {
  9. if (self = [super init]) {
  10. self.movieId = [aDecoder decodeObjectForKey:@"id"];
  11. self.movieName = [aDecoder decodeObjectForKey:@"name"];
  12. self.pic_url = [aDecoder decodeObjectForKey:@"url"];
  13. }
  14. return self;
  15. }
  16. @end
复制代码

如果这里有100个属性,那么我们也只能把100个属性都给写一遍,

不过用runtime之后,就简单了:

  1. #import "Movie.h"
  2. #import <objc/runtime.h>
  3. @implementation Movie
  4. - (void)encodeWithCoder:(NSCoder *)encoder
  5. {
  6. unsigned int count = 0;
  7. Ivar *ivars = class_copyIvarList([Movie class], &count);
  8. for (int i = 0; i<count; i++) {
  9. // 取出i位置对应的成员变量
  10. Ivar ivar = ivars[i];
  11. // 查看成员变量
  12. const char *name = ivar_getName(ivar);
  13. // 归档
  14. NSString *key = [NSString stringWithUTF8String:name];
  15. id value = [self valueForKey:key];
  16. [encoder encodeObject:value forKey:key];
  17. }
  18. free(ivars);
  19. }
  20. - (id)initWithCoder:(NSCoder *)decoder
  21. {
  22. if (self = [super init]) {
  23. unsigned int count = 0;
  24. Ivar *ivars = class_copyIvarList([Movie class], &count);
  25. for (int i = 0; i<count; i++) {
  26. // 取出i位置对应的成员变量
  27. Ivar ivar = ivars[i];
  28. // 查看成员变量
  29. const char *name = ivar_getName(ivar);
  30. // 归档
  31. NSString *key = [NSString stringWithUTF8String:name];
  32. id value = [decoder decodeObjectForKey:key];
  33. // 设置到成员变量身上
  34. [self setValue:value forKey:key];
  35. }
  36. free(ivars);
  37. }
  38. return self;
  39. }
  40. @end
复制代码

如果还嫌麻烦,我们可以把encodeWithCoder 和 initWithCoder 这两个方法抽成宏

  1. #import "Movie.h"
  2. #import <objc/runtime.h>
  3. #define encodeRuntime(A) \
  4. \
  5. unsigned int count = 0;\
  6. Ivar *ivars = class_copyIvarList([A class], &count);\
  7. for (int i = 0; i<count; i++) {\
  8. Ivar ivar = ivars[i];\
  9. const char *name = ivar_getName(ivar);\
  10. NSString *key = [NSString stringWithUTF8String:name];\
  11. id value = [self valueForKey:key];\
  12. [encoder encodeObject:value forKey:key];\
  13. }\
  14. free(ivars);\
  15. \
  16. #define initCoderRuntime(A) \
  17. \
  18. if (self = [super init]) {\
  19. unsigned int count = 0;\
  20. Ivar *ivars = class_copyIvarList([A class], &count);\
  21. for (int i = 0; i<count; i++) {\
  22. Ivar ivar = ivars[i];\
  23. const char *name = ivar_getName(ivar);\
  24. NSString *key = [NSString stringWithUTF8String:name];\
  25. id value = [decoder decodeObjectForKey:key];\
  26. [self setValue:value forKey:key];\
  27. }\
  28. free(ivars);\
  29. }\
  30. return self;\
  31. \
  32. @implementation Movie
  33. - (void)encodeWithCoder:(NSCoder *)encoder
  34. {
  35. encodeRuntime(Movie)
  36. }
  37. - (id)initWithCoder:(NSCoder *)decoder
  38. {
  39. initCoderRuntime(Movie)
  40. }
  41. @end
复制代码
6.6 界面指定跳转

在接收推送通知的时候,我们希望可以点击这条通知跳转到对应的页面,简单的方法就是,判断,在判断。但是我们可以利用runtime动态生成对象、属性、方法这特性,我们可以先跟服务端商量好,定义跳转规则,比如要跳转到A控制器,需要传属性 id、type,那么服务端返回字典给我,里面有控制器名,两个属性名跟属性值,客户端就可以根据控制器名生成对象,再用kvc给对象赋值,这样就搞定了。

比如,根据推送规则跳转对应页面 HSFeedsViewController

  1. // 进入该页面需要穿的属性
  2. @interface HSFeedsViewController : UIViewController
  3. // 注:根据下面的两个属性,可以从服务器获取对应的频道列表数据
  4. /** 频道ID */
  5. @property (nonatomic, copy) NSString *ID;
  6. /** 频道type */
  7. @property (nonatomic, copy) NSString *type;
  8. @end
复制代码

AppDelegate.m 中添加以下代码片段:

  1. // 这个规则肯定事先跟服务端沟通好,跳转对应的界面需要对应的参数
  2. NSDictionary *userInfo = @{
  3. @"class": @"HSFeedsViewController",
  4. @"property": @{
  5. @"ID": @"123",
  6. @"type": @"12"
  7. }
  8. };
复制代码

接收到推送的消息后:

  1. - (void)push:(NSDictionary *)params
  2. {
  3. // 类名
  4. NSString *class =[NSString stringWithFormat:@"%@", params[@"class"]];
  5. const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
  6. // 从一个字串返回一个类
  7. Class newClass = objc_getClass(className);
  8. if (!newClass)
  9. {
  10. // 创建一个类
  11. Class superClass = [NSObject class];
  12. newClass = objc_allocateClassPair(superClass, className, 0);
  13. // 注册你创建的这个类
  14. objc_registerClassPair(newClass);
  15. }
  16. // 创建对象
  17. id instance = [[newClass alloc] init];
  18. // 对该对象赋值属性
  19. NSDictionary * propertys = params[@"property"];
  20. [propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
  21. // 检测这个对象是否存在该属性
  22. if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
  23. // 利用kvc赋值
  24. [instance setValue:obj forKey:key];
  25. }
  26. }];
  27. // 获取导航控制器
  28. UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
  29. UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];
  30. // 跳转到对应的控制器
  31. [pushClassStance pushViewController:instance animated:YES];
  32. }
复制代码

// 检查对象是否存在该属性:

  1. - (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
  2. {
  3. unsigned int outCount, i;
  4. // 获取对象里的属性列表
  5. objc_property_t * properties = class_copyPropertyList([instance
  6. class], &outCount);
  7. for (i = 0; i < outCount; i++) {
  8. objc_property_t property =properties[i];
  9. // 属性名转成字符串
  10. NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
  11. // 判断该属性是否存在
  12. if ([propertyName isEqualToString:verifyPropertyName]) {
  13. free(properties);
  14. return YES;
  15. }
  16. }
  17. free(properties); // 用copy的时候记得释放
  18. return NO;
  19. }
复制代码

// 官方文档翻译

http://blog.csdn.net/coyote1994/article/details/52441513



回复

使用道具 举报