IOS开发之NSCoding协议(使用runtime)
近期学习IOS的runtime库,然后看到之前写的NSCoding协议有点复杂,如果属性少还好,如果100多个属性,则会显得麻烦。下面使用常规方式和使用Runtime两种方式进行比较,然后总结一下中间遇到的坑。

1.常规方法做归档与解档

//自定义Person类继承自NSObject

.h文件

@interface Person : NSObject<NSCoding>
@property(nonatomic,strong) NSString * name;//名字
@property(nonatomic,strong) NSString * gender;//性别
@property(nonatomic,strong) NSString * address;//地址
@property(nonatomic) NSUInteger age;//年龄
-(instancetype)initWithName:(NSString*)name gender:(NSString*)gender address:(NSString*)adderss age:(NSUInteger)age;
@end

.m文件

#import "Person.h"
@implementation Person
/*
使用常规进行解档与归档。
*/
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:_name forKey:@"name"];
[aCoder encodeObject:_gender forKey:@"gender"];
[aCoder encodeObject:_address forKey:@"address"];
[aCoder encodeInteger:_age forKey:@"age"]; }
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
_name = [aDecoder decodeObjectForKey:@"name"];
_gender = [aDecoder decodeObjectForKey:@"gender"];
_address = [aDecoder decodeObjectForKey:@"address"];
_age = [aDecoder decodeIntegerForKey:@"age"]; }
return self;
}
-(instancetype)initWithName:(NSString *)name gender:(NSString *)gender address:(NSString *)adderss age:(NSUInteger)age{
if (self = [super init]) {
_name = name;
_gender = gender;
_address = adderss;
_age = age;
}
return self; }
-(NSString*)description{
return [NSString stringWithFormat:@"name:%@ gender:%@ age:%lu address:%@",self.name,self.gender,(unsigned long)self.age,self.address]; }
@end

上面定义了Person类,下面定义一个Teacher类继承自Person类

//自定义Student继承自Person类

.h文件

#import "Person.h"
@interface Teacher : Person
  @property(nonatomic,strong) NSString * course;//课程
  -(instancetype)initWithName:(NSString *)name gender:(NSString *)gender address:(NSString *)adderss age:(NSUInteger)age course:(NSString*)course;
@end

.m文件

#import "Teacher.h"

@implementation Teacher
  -(instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {
    _course = [aDecoder decodeObjectForKey:@"course"];
  }
  return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder{
  [super encodeWithCoder:aCoder];
  [aCoder encodeObject:_course forKey:@"course"];
}
-(instancetype)initWithName:(NSString *)name gender:(NSString *)gender address:(NSString *)adderss age:(NSUInteger)age course:(NSString *)course{
  if (self = [super initWithName:name gender:gender address:adderss age:age]) {
  _course = course;
}
return self;
}
-(NSString*)description{
  NSString* str = [super description];
  return [str stringByAppendingString:[NSString stringWithFormat:@" course:%@",self.course]];
}
@end

解档与归档成对存在,一定要都要实现。当一个类继承自自定义的类,一定要调用父类的归档和解档,调用[super initWithCoder:aDeoder]和[super encodeWithCoder:aCoder]完成父类的归档与解档。

2.使用Runtime完成归档与解档

只使用Runtime重构了Person类,因为Student类使用Runtime重构没有特别之处,与Person类相同。

//.h文件,与上面定义相同

#import <Foundation/Foundation.h>
@interface Person : NSObject<NSCoding>
  @property(nonatomic,strong) NSString * name;
  @property(nonatomic,strong) NSString * gender;
  @property(nonatomic,strong) NSString * address;
  @property(nonatomic) NSUInteger age;
  -(instancetype)initWithName:(NSString*)name gender:(NSString*)gender address:(NSString*)adderss age:(NSUInteger)age;
@end

.m文件

#import "Person.h"
#import <objc/runtime.h>
@implementation Person /** 这里才是运行时(runtime)的真正应用 */ /**
* 将某个对象写入文件时会调用
* 在这个方法中说清楚哪些属性需要存储
*/
-(void)encodeWithCoder:(NSCoder *)aCoder{
  unsigned int count = ;
  Ivar *ivarLists = class_copyIvarList([Person class], &count);// 注意下面分析
  for (int i = ; i < count; i++) {
    const char* name = ivar_getName(ivarLists[i]);
    NSString* strName = [NSString stringWithUTF8String:name];
    [aCoder encodeObject:[self valueForKey:strName] forKey:strName];
  }
  free(ivarLists); //一定不要忘了,自己释放。
} /**
* 从文件中解析对象时会调用
* 在这个方法中说清楚哪些属性需要存储
*/
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
  if (self = [super init]) {
    unsigned int count = ;
    Ivar *ivarLists = class_copyIvarList([Person class], &count);
    for (int i = ; i < count; i++) {
      const char* name = ivar_getName(ivarLists[i]);
      NSString* strName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
      id value = [aDecoder decodeObjectForKey:strName];
      [self setValue:value forKey:strName];
  }
  free(ivarLists);
}
return self;
} -(instancetype)initWithName:(NSString *)name gender:(NSString *)gender address:(NSString *)adderss age:(NSUInteger)age{
  if (self = [super init]) {
  _name = name;
  _gender = gender;
  _address = adderss;
  _age = age;
  }
  return self;
}
-(NSString*)description{
  return [NSString stringWithFormat:@"name:%@ gender:%@ age:%lu address:%@",self.name,self.gender,(unsigned long)self.age,self.address];
}
@end

在实现上面注意点的时候,由于对Runtime系统中super的理解不足,导致开始写的代码时Ivar *ivarLists = class_copyIvarList([self class], &count);,这样在没有子类继承的情况下没有错误,完全可以使用。但是如多有子类继承这个类,当子类进行解档和归档的的时候就会出错。下面分析一下问题:

1.解释一些理论知识

(1)当对一个类或者对象调用方法时候,在Runtime运行时系统转化成了发送消息,编译器在这时便根据情况在objc_msgSend(),objc_msgSend_stret()和objc_msgSendSuper(),objc_msgSendSuper_stret()四个函数选择一种调用。如果消息是传递给父类,就调用带super的方法;如果消息返回数据时数据结构而不是简单值时,调用名字带stret的函数。
(2)隐藏的关键字self:当objc_msgSend()函数找到方法实现的时候,将消息的所有参数都传递给方法的实现,同时还有两个参数,其中之一就是接受消息的对象self,其所指向的内容是当前对象的指针;另一个是方法选择器_cmd,其所指向的内容是当前方法的SEL指针。
(3)关键字super:super是super接受到消息时候,编译器创建的一个结构体objc_super:struct objc_super{ id receiver, Class class} ,里面的receiver 其实就是self的id指针,这个结构体指定了消息应该传递给指定的父类。例如我们如果调用[super class]方法,获取父类时候,编译器实际是把self的id指针和class的SEL传递给了objc_msgSendSuper().相当于self调用父类的方法。这个时候[super class]与[self class]一样。
总结一句就是使用super的时候只是提示编译器,我调用的是父类的方法,真正的传入的对象还是 self。由于水平有限,很深入的分析达不到,可能还有哪里说的不对,可以参考下面的讲解,我的有些内容也是参考这里。http://www.jianshu.com/p/1e06bfee99d0
(4)代码Ivar *ivarLists = class_copyIvarList([Person class], &count);生成实例变量的数组,不包含父类的。

2.分析原因

通过上面的基础的知识的了解,应该对知道大致的原因了。当子类调用调用[super initWithCoder:aDeoder]和[super encodeWithCoder:aCoder]时候,相当于使用self对象调用父类的initWithCoder:和encodeWithCoder:方法。
这个时候如果使用Ivar *ivarLists = class_copyIvarList([self class], &count);代码,self现在已经是子类的对象的指针,现在[self class]获取的是子类Teacehr的类型,获取的结果是Teacher类的实例变量列表,则父类的实例变量在子类没有被归档与解档,造成错误,并不抱错,知识归档与解档不完全,丢掉父类的内容。
而下面的代码一定要用self,因为你是为子类Teacher进行归档和解档的,需要传递Teacher对象的实例。

3.调用归档与解档

#import "ViewController.h"
#import "Teacher.h"
#import "Person.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Teacher* teacher1 = [Teacher factoryWithName:@"liyang" gender:@"male" address:@"shandong" age: course:@"math"];
Teacher* teacher2 = [Teacher factoryWithName:@"li" gender:@"female" address:@"shanghai" age: course:@"english"];
NSMutableData* data = [NSMutableData data];
NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data1];
[archiver encodeObject:teacher1 forKey:@"teacher"];
[archiver encodeObject:teacher2 forKey:@"teacher2"];
[archiver finishEncoding];
NSString* path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"teacher"];
[data writeToFile:path atomically:YES]; NSData* data2 = [NSData dataWithContentsOfFile:path];
NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data2];
Teacher* teacher3 = [unarchiver decodeObjectForKey:@"teacher"];
Teacher* teacher4 = [unarchiver decodeObjectForKey:@"teacher2"];
[unarchiver finishDecoding];
NSLog(@"\nteacher1:%@\nteacher3:%@\nteacher2:%@\nteacher4:%@\n%@",teacher1,teacher3,teacher2,teacher4,path);
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

总的来说使用Runtime库来写代码,需要注意很多地方,因为太灵活太强大,很难驾驭。需要多学习里面的机制和理论才能好好使用。使用不当就会掉入万劫不复之地。
使用NSObject类就能方便使用Runtime运行时库。
里面的内容参考很多东西网上的知识,自己归纳总结一下。
有错误的地方往指正。感觉有用给个好评。

runtime之归档和解档的更多相关文章

  1. 利用Runtime对Ivar实例变量进行共用的归档和解档方式

    一.介绍 在OC中每一个对象持有的变量都是实例变量,实例变量包括成员变量和属性变量,在runtime中用Ivar表示对象的实例变量.其实,runtime源码中可以看到,Ivar也是一个结构体(基本上在 ...

  2. 【IOS学习基础】归档和解档

    一.归档介绍 1.归档是指用某种格式来保存一个或多个对象,以便以后还原这些对象的过程.归档是将数据持久化的一种方式(所谓数据持久化,就是指在IOS开发过程中,将数据保存到本地,能够让程序的运行更加流畅 ...

  3. iOS基础知识之归档和解档

    归档和解档:即将数据写入文件和从文件中读取数据. 此处以plist文件为例说明, 一.plist文件使用时的注意事项: 1.plist文件中仅支持写入Array,Dictionary,Boolean, ...

  4. OC 归档和解档

    #import <Foundation/Foundation.h> #define PATH @"/Users/mac/Desktop/file.txt" int ma ...

  5. 归档和解档配合NSFile存储数据

    NSString *Name = @"yc"; //第一个常量NSDocumentDirectory表示正在查找沙盒Document目录的路径(如果参数为NSCachesDirec ...

  6. iOS--归档和解档(Archiver)、(UnArchiver)

    一.已有类型的归档和解档 首先来看一个简单的例子: //第一方式:归档对象 //对象-->文件 NSArray *array = [NSArray arrayWithObjects:@" ...

  7. iOS 数据存储 - 归档和解归档

    这里的归档主要是用于自定义类的归档和解档.我们这里使用NSKeyedArchiver和NSKeyedUnarchiver来归档和解档. 注意:自己定义的类需要实现<NSCoding>,如: ...

  8. (转载)OC学习篇之---归档和解挡

    前几篇文章说到了OC中的Foundation框架,今天我们来看一下OC中的一个重要知识点:归档 OC中的归档就是将对象写入到一个文件中,Java中的ObjectInputStream和ObjectOu ...

  9. iOS 浅复制、深复制、完全复制的知识点梳理验证(附加归档解档)

    在之前转载的一片文章中,文中对浅复制和深复制进行了详细的解读,同时还提到了深复制(one-level-deep copy).完全复制(true copy)的概念,并指出iOS开发中的深复制是单层深赋值 ...

随机推荐

  1. 第四章 走进jVM

    4.1字节码 java文件编译成字节码后由默认解释执行,热点代码编译执行.方法调用到一定程度的时候,进行JIT编译成机器码执行,后面直接运行JIT编译结果(机器码). 4.2类加载过程 加载链接初始化 ...

  2. activemq---点对点/发布订阅模式简单代码示例

    activemq 消息模式流程: ConnnectionFactory --> Connection --> Session --> Message ---ConnectionFac ...

  3. 1、pip不是内部运行程序 解决方法

    一.方式一 1.切换到pip所在路径: shit+ 右键. 再此处打开运行窗口 2.执行 pip install pytest 脚本即可. 二.方式二,添加环境变量 1.将pip所在的文件路径 添加到 ...

  4. java基础复习(二)

    一. 基本语法 如果一个源文件中什么内容都没有,编译会不会生成 字节码文件?不会 如果一个类中没有主方法(如下图),编译运行会怎么样?编译可以通过,但运行报错 : 没有主方法 主方法 : 是一个特殊的 ...

  5. Python的pip源切换为国内阿里云镜像

    Python的pip源切换为国内阿里云镜像 找到用户目录 C:\Users\用户\pip,如果不存在就新建该文件夹. 新建文件pip.ini,并用文本编辑器输入以下内容并保存 [global] ind ...

  6. 如何使用Keka for Mac提取受密码保护的文件?用Keka提取文件的格式

    如何使用Keka for Mac提取受密码保护的文件?keka Mac是很多人喜欢的压缩解压工具,以小巧,使用简单,界面简洁受到很多Mac用户的喜欢,你还可以使用它提取文件,下面我们就来介绍一下关于用 ...

  7. 01 spring security入门篇

    1. 环境搭建 使用SpringBoot搭建开发环境,只需在pom.xml添加如下依赖即可. <?xml version="1.0" encoding="UTF-8 ...

  8. sql 用语句还原多数据文件的数据库

    /* 还原bak数据库文件*/ RESTORE DATABASE userdb_cs /*还原为userdb名的新数据库*/ FROM DISK = 'D:\工作资料\数据库文件\userdb.bak ...

  9. codeforces 848A - From Y to Y(构造)

    原题链接:http://codeforces.com/problemset/problem/848/A 题意:让我们构造一个字符串.这里有一种操作:取走这个字符串的若干部分,分成两部分,然后将这两部分 ...

  10. delphi中如何将一整个文件读入内存

    来源 https://bbs.csdn.net/topics/390985048 分配一块大内存吧,要是一下申请不了64M那么大,就多申请几块小的,用个链表连起来.用FileStream类的方法读取文 ...