美文网首页IOSiOS面试那些事runtime
IOS开发之NSCoding协议(使用runtime)

IOS开发之NSCoding协议(使用runtime)

作者: 有心向往 | 来源:发表于2016-09-14 12:30 被阅读3451次
    近期学习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 = 0;
        Ivar *ivarLists = class_copyIvarList([Person class], &count);// 注意下面分析
        for (int i = 0; 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 = 0;
            Ivar *ivarLists = class_copyIvarList([Person class], &count);
            for (int i = 0; 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:24 course:@"math"];
        Teacher* teacher2 = [Teacher factoryWithName:@"li" gender:@"female" address:@"shanghai" age:23 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库讲解,请点击下面链接:
    http://www.jianshu.com/p/1e06bfee99d0
    http://www.jianshu.com/p/25a319aee33d

    相关文章

      网友评论

      • Shawn_Wang:写一句题外话,iOS是这么写的,而不是IOS。感觉这么写挺业余的
      • tangshu:帅哥,归档、解档Student类时,Person类中的成员变量丢失,怎么解决?
        6e331ee478cb:- (void)encodeWithCoder:(NSCoder *)aCoder
        {
        [super encodeWithCoder:aCoder];
        unsigned int count = 0;
        Ivar *ivarList = class_copyIvarList([Student class], &count);
        for (int i = 0 ; i < count; i++) {
        const char *name = ivar_getName(ivarList[i]);
        NSString *strName = [NSString stringWithUTF8String:name];
        [aCoder encodeObject:[self valueForKey:strName] forKey:strName];
        }
        free(ivarList);
        }

        - (instancetype)initWithCoder:(NSCoder *)aDecoder
        {
        self = [super initWithCoder:aDecoder];
        if (self) {
        unsigned int count = 0;
        Ivar *ivarList = class_copyIvarList([Student class], &count);
        for (int i = 0; i < count; i++) {
        const char *name = ivar_getName(ivarList[i]);
        NSString *strName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
        id value = [aDecoder decodeObjectForKey:strName];
        [self setValue:value forKey:strName];
        }
        free(ivarList);
        }
        return self;
        }
      • 男神已认证:大哥 NSUInteger能参与归档么?

      本文标题:IOS开发之NSCoding协议(使用runtime)

      本文链接:https://www.haomeiwen.com/subject/pedvettx.html