気楽なソフト工房

プログラミングについていろいろな記事を書いています。



mykonos2008

Author:mykonos2008
システムエンジニアとして働いている30代の会社員です。
仕事や趣味でプログラムを書いている方の役に立つ記事を書いていきたいと思っています。
ご意見、ご感想はこちらまで
If you are an english speaker,Please visit my english blog.

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
iOSにはUIViewをアニメーションさせるための、便利なフレームワーク「Core Animation」があります。
でも、何となく使い方は分かってるけど、CALayerって何?っていうところがぼやけていたりしませんか?
そこで、本稿では、UIiewとCALayerの関係について書いてみたいと思います。

Core Animationについて


Core AnimationはViewをアニメーションさせるための基本的な仕組みを提供するもので、よく混同されがちなのですが、
Viewとは全く異なった役割を持ったものです。Core AnimationはViewのコンテンツをbitmapとしてキャッシュし、
それをGPUに直接処理させることで、アニメーションを実現するものです。
しかし、そのコンテンツそのものを準備する部分については、Viewに委ねられています。

UIViewとCALayerの関係について


Core Animationを扱う際、最も混乱しやすい部分がUIViewとCALayerの関係についてではないかと思います。

以下の2つのコードを見てください。
@implementation ViewController{
    IBOutlet UIImageView *_charactor;
}

・・・・・・・・・・・・・・・・・・・・・
-(IBAction)startButtonTouched:(id)sender
{
    [UIView animateWithDuration:1.0f animations:^{
         _charactor.center = CGPointMake(250,_charactor.center.y);
    }];
}
・・・・・・・・・・・・・・・・・・・・・


#import <QuartzCore/QuartzCore.h>

@implementation ViewController{
    IBOutlet UIImageView *_charactor;
}

・・・・・・・・・・・・・・・・・・・・・
-(IBAction)startButtonTouched:(id)sender
{
    [UIView animateWithDuration:1.0f animations:^{
        _charactor.layer.position = CGPointMake(250,_charactor.center.y);
    }];
}
・・・・・・・・・・・・・・・・・・・・・

2つのソースコードの実行結果は全く同じになります。画像を画面の左から右にアニメーションさせながら移動します。

実行前実行後


こうしてみると、ますます、UIViewとCALayerを混同してしまいそうですね。
しかし、UIViewをCALayerは全く異なった役割を持つ別のものなのです。

繰り返しになりますが、CALayerはUIViewのコンテンツをbitmapとしてキャッシュし、
その状態を変えることでアニメーションを実行する役割を担っています。
その一方で、イベントを処理したり、コンテンツを描画したりすることは出来ません。それはUIViewの役割になります。

iOSではUIViewのインスタンスには必ずそれに対応するCALayerのインスタンスが作成され、後ろに控えています。
そして、上記のサンプルコードのように、いくつかのCALayerのプロパティはUIViewのプロパティからも
アクセスできるようになっています。その場合、CALayerのプロパティとUIViewのプロパティが異なった
値を持っているわけではなく、常に同じ値を持っています。
(なお、CALayerのプロパティに直接アクセスするためには、QuartzCore/QuartzCore.hをインポートしておく
必要があります。)

一見すると、UIViewがアニメーションの機能を提供しているように見えますが、それは後ろに控えている
CALayerによって実現されているのです。

XMLからオブジェクトのデシリアライズをするiOS用のライブラリ「ChimeraXML(キメラXML)」をgithubに公開しました。
iOSのXMLデシリアライズライブラリ「ChimeraXML」

本稿はデシリアライズシリーズの最終回です。

テーマはコレクション型の要素を扱うようにすることです。

今回使用するXMLは以下です。

<User>
  <Id>1</Id>
  <Name>山田太郎</Name>
  <Family>
    <User>
       <Name>山田次郎</Name>
     </User>
  </Family>
  <Friends>
     <User>
       <Id>2</Id>
     </User>
     <User>
       <Id>3</Id>
     </User>
  </Friends>
  <Email>xxxxx@xxxx.com</Email>
</User>

子の要素として、複数のUser要素を持つFriends要素を追加しました。

まず、デシリアライズ先のUserクラスにプロパティを追加して、propertyInfoForElement:element:にも処理を追加します。

[User.h]

#import <Foundation/Foundation.h>
#import "XmlEntity.h"

@interface User : XmlEntity

@property(strong,nonatomic) NSString *userId;
@property(strong,nonatomic) NSString *name;
@property(strong,nonatomic) NSArray *friends;
@property(strong,nonatomic) NSString *email;

@end

[User.m]
#import "User.h"

@implementation User

+(PropertyInfo *)propertyInfoForElement:(NSString *)element
{
    static NSDictionary *_propDic;
    
    if(!_propDic){
        
        PropertyInfo *userId = [[PropertyInfo alloc] init];
        userId.name = @"userId";
        userId.type = [NSString class];
        
        PropertyInfo *name = [[PropertyInfo alloc] init];
        name.name = @"name";
        name.type = [NSString class];
        
        PropertyInfo *friends = [[PropertyInfo alloc] init];
        friends.name = @"friends";
        friends.type = [NSArray class];
        friends.subType = [User class];
        
        PropertyInfo *email = [[PropertyInfo alloc] init];
        email.name = @"email";
        email.type = [NSString class];
        
        _propDic = @{
                     @"Id": userId,
                     @"Name": name,
                     @"Friends":friends,
                     @"Email" :email
                     };
    }
    
    return _propDic[element];
}
@end

上記のコードでPropertyInfoクラスのsubTypeというプロパティにUserクラスを設定しています。
これは今回、PropertyInfoクラスに追加したプロパティで、Objective-cにはジェネリックコレクションの仕組みが無いため、
NSArray型のプロパティの要素として、何のクラスのオブジェクトが登録されるかを指定するためのものです。

「PropertyInfo.h」
#import <Foundation/Foundation.h>

@interface PropertyInfo : NSObject

@property(strong,nonatomic) NSString *name;
@property(strong,nonatomic) Class type;
@property(strong,nonatomic) Class subType;

@end

そして、最後にコレクションを扱う処理を追加したパース処理クラスです。

「ParserDelegateImpl.m」
#import "ParserDelegateImpl.h"
#import "ElementInfo.h"
#import "XmlEntity.h"
#import "PropertyInfo.h"

#import 

@implementation ParserDelegateImpl{
    
    NSMutableArray *_elementStack;
    id _rootObject; //デシリアライズ結果のオブジェクト
    int _depth; //現在処理しているXMLの階層を示す変数
}

-(id)initWithTargetClass:(Class)targetClass
{
    self = [super init];
    if(self){
        _elementStack = [NSMutableArray array];
        _rootObject = [[targetClass alloc] init];
    }
    return self;
}

-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI 
  qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
    if(_depth > [_elementStack count]){
        _depth++;
        return;
    }
    
    _depth++;   
    
    //ルート要素の場合
    if([_elementStack count] == 0){
        //最上位階層の情報を管理するElementInfoを生成し、Stackにつめる
        ElementInfo *info = [[ElementInfo alloc] init];
        info.elementName = elementName;
        info.target = _rootObject;

        [_elementStack addObject:info];
    }
    //ルート要素以外の場合
    else{
        //親要素の情報を取り出す
        ElementInfo *parent = [_elementStack lastObject];
        
        //親要素がコレクションの場合
        if([parent.target isKindOfClass:[NSArray class]]){
            ElementInfo *info = [[ElementInfo alloc] init];
            info.elementName = elementName;
            info.target = [[parent.propInfo.subType alloc] init];
            [parent.target addObject:info.target];
            [_elementStack addObject:info];
        }
        else{
            //Userクラスから要素に対応するプロパティの情報を取得する
            PropertyInfo *propInfo =[[parent.target class] propertyInfoForElement:elementName];
            if(!propInfo){
                return;
            }
            //プロパティが存在しているかチェックする
            objc_property_t prop =  class_getProperty([parent.target class], [propInfo.name UTF8String]);
            if(prop){
               
                //要素を管理するElementInfoを生成し、Stackにつめる
                ElementInfo *info = [[ElementInfo alloc] init];
                info.elementName = elementName;
                info.propInfo = propInfo;
                
                //プロパティの型に応じて処理を分ける
                //NSStringの場合
                if(propInfo.type == [NSString class]){
                    info.target = [[NSMutableString alloc] init];
                }
                //コレクションの場合
                else if(propInfo.type == [NSArray class] || propInfo.type == [NSMutableArray class]){
                    info.target = [NSMutableArray array];
                    [parent.target setValue:info.target forKey:propInfo.name];
                }
                
                [_elementStack addObject:info];
            }
        }
    }
}

-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
    namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    if(_depth == [_elementStack count]){
        //スタックにつめられている最後のオブジェクトを取得し、その要素名が引数のelementNameと一致するか判定する
        ElementInfo *lastElement = [_elementStack lastObject];
        if([lastElement.elementName isEqualToString:elementName]){
            //targetオブジェクトがNSMutableStringの場合(テキストノードの場合)
            if([lastElement.target isKindOfClass:[NSMutableString class]]){
                //もう一つ上の階層のオブジェクトのプロパティにテキストノードの値を設定する
                ElementInfo *parentElement = [_elementStack objectAtIndex:[_elementStack count] -2];
                [parentElement.target setValue:lastElement.target forKey:lastElement.propInfo.name];
            }
        }
        
        [_elementStack removeLastObject];
        
    }
    _depth--;
}

-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    //stackの最後のオブジェクトがNSMutableStringの場合、テキストノードの値をそれにつめる
    ElementInfo *lastElement = [_elementStack lastObject];
    if([lastElement.target isKindOfClass:[NSMutableString class]]){
        [((NSMutableString *)lastElement.target) appendString:string];
    }
}

//解析結果のオブジェクトを返却する
-(id)resultObject
{
    return _rootObject;
}

@end


これで一般的な形のXMLを扱うことが出来るものになったと思います。
実際にこれをライブラリとして使用する場合は、もう少し必要になることがあると思います。

今回のコードでは、デシリアライズ先のクラスのプロパティの型が、
NSStringとNSArray型のみでしたが、その他の型を扱えるようにする必要があると思います。

あとは、XMLが崩れていたり、デシリアライズとして、想定していないクラスが
渡されたりした場合などの例外処理と、チェック処理あたりになると思います。


XMLからオブジェクトのデシリアライズをするiOS用のライブラリ「ChimeraXML(キメラXML)」をgithubに公開しました。
iOSのXMLデシリアライズライブラリ「ChimeraXML」

前回の記事ではシンプルなXMLをデシリアライズする記事を紹介しました。

今回は、もう少し複雑なXMLを扱えるようにしたコードを紹介します。

<User>
  <Id>1</Id>
  <Name>山田太郎</Name>
  <Family>
    <User>
       <Name>山田次郎</Name>
     </User>
  </Family>
  <Email>xxxxx@xxxx.com</Email>
</User>

前回のXMLとの違いはFamilyという要素が加わっていることです。
今回の記事のテーマは、このFamily要素を処理することではなく、無視をすることです。

実際の開発の現場でも、APIのXMLに含まれる要素の一部が不要なケースは多々あると思います。
そこで、今回はUserクラスのpropertyInfoForElement:element:には手を加えず、
(つまり、処理対象として、Familyを追加せず)、その他の要素が正しく処理されるように
コードを変更します。ちなみに前回のコードで、今回のXMLを処理すると一部の要素が正しく
取得できません。

「ParserDelegateImpl.m」
#import "ParserDelegateImpl.h"
#import "ElementInfo.h"
#import "XmlEntity.h"
#import "PropertyInfo.h"

#import <objc/runtime.h>

@implementation ParserDelegateImpl{
    
    NSMutableArray *_elementStack;
    id _rootObject; //デシリアライズ結果のオブジェクト
    int _depth; //現在処理しているXMLの階層を示す変数
}

-(id)initWithTargetClass:(Class)targetClass
{
    self = [super init];
    if(self){
        _elementStack = [NSMutableArray array];
        _rootObject = [[targetClass alloc] init];
    }
    return self;
}

-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI
    qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
    if(_depth > [_elementStack count]){
        _depth++;
        return;
    }
    
    _depth++; 
    
    //ルート要素の場合
    if([_elementStack count] == 0){
        //最上位階層の情報を管理するElementInfoを生成し、Stackにつめる
        ElementInfo *info = [[ElementInfo alloc] init];
        info.elementName = elementName;
        info.target = _rootObject;

        [_elementStack addObject:info];
    }
    //ルート要素以外の場合
    else{
        //親要素の情報を取り出す
        ElementInfo *parent = [_elementStack lastObject];
        //Userクラスから要素に対応するプロパティの情報を取得する
        PropertyInfo *propInfo =[[parent.target class] propertyInfoForElement:elementName];
        if(!propInfo){
            return;
        }
        //プロパティが存在しているかチェックする
        objc_property_t prop =  class_getProperty([parent.target class], [propInfo.name UTF8String]);
        if(prop){
            //NSString *propAttr = [NSString stringWithUTF8String:property_getAttributes(prop)];
            //NSLog(@"propAttr = %@",propAttr);
            
            //要素を管理するElementInfoを生成し、Stackにつめる
            ElementInfo *info = [[ElementInfo alloc] init];
            info.elementName = elementName;
            info.propInfo = propInfo;
            
            //プロパティの型に応じて処理を分ける
            //NSStringの場合
            if(propInfo.type == [NSString class]){
                info.target = [[NSMutableString alloc] init];
            }
            
            [_elementStack addObject:info];
        }
    }     
}

-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
     namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    if(_depth == [_elementStack count]){
        //スタックにつめられている最後のオブジェクトを取得し、その要素名が引数のelementNameと一致するか判定する
        ElementInfo *lastElement = [_elementStack lastObject];
        if([lastElement.elementName isEqualToString:elementName]){
            //targetオブジェクトがNSMutableStringの場合(テキストノードの場合)
            if([lastElement.target isKindOfClass:[NSMutableString class]]){
                //もう一つ上の階層のオブジェクトのプロパティにテキストノードの値を設定する
                ElementInfo *parentElement = [_elementStack objectAtIndex:[_elementStack count] -2];
                [parentElement.target setValue:lastElement.target forKey:lastElement.propInfo.name];
            }
        }
        
        [_elementStack removeLastObject];
        
    }
    _depth--;
}

-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    //stackの最後のオブジェクトがNSMutableStringの場合、テキストノードの値をそれにつめる
    ElementInfo *lastElement = [_elementStack lastObject];
    if([lastElement.target isKindOfClass:[NSMutableString class]]){
        [((NSMutableString *)lastElement.target) appendString:string];
    }
}

//解析結果のオブジェクトを返却する
-(id)resultObject
{
    return _rootObject;
}

@end

変更点はほんの少しです。

まず、現在、処理をしている階層を管理するdepthという変数を追加しています。
そして、depthをdidStartElementの最初で1プラスし、didEndElementの終わりで1マイナスしています。

たとえば、Idタグに対応するdidStartElementがコールされたタイミングでは値は1になっています。
Userタグは開始していますが、終了はしていませんので。

そして、前回少し解説しましたが、このコードではデシリアライズをする際、NSMutableArrayをスタックとして使用し、
XMLを上から下の要素に移動する毎に、通過した要素の情報(ElementInfo)をNSMutableArrayに
追加していく処理をしています。逆に、要素の終了タグが見つかったタイミングで、スタックの最後に登録されているElementInfoを
削除しています。

このロジックですと、例えば、Idタグが発見され、didStartElementがコールされたタイミングでは、
Userタグ(ルート)に対応するElementInfo1つがスタックに登録されていることになります。
従って、depthとスタックのサイズは一致していることになります。

もう一点重要なポイントとして、処理の対象としない要素を発見した際は、スタックには登録しないロジック(下記の部分)になっています。

-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI
    qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
・・・・・・・・・・・・・・・・・
       PropertyInfo *propInfo =[[parent.target class] propertyInfoForElement:elementName];
        if(!propInfo){
            return;
        }
・・・・・・・・・・・・・・・・・
}

つまり、不要なタグを発見した以降は、depthがスタックのサイズより大きくなってしまいます。
これを利用して、didStartElement、didEndElementで処理を開始する前に
処理対象かどうかの判定を行っています。
次回でデシリアライズシリーズの最終回にしようと思いますが、 コレクションタイプの要素を扱えるようにしてみたいと思います。


前回の続きで、今回はいよいよ、デシリアライズする部分の処理を紹介します。

デシリアライズする対象のXMLは以下です。

<User>
  <Id>1</Id>
  <Name>山田太郎</Name>
  <Email>xxxxx@xxxx.com</Email>
</User>

今回は解説するより、ソースをみていただく方が早いかと思います。ですので、ここから一気にソースを紹介します。

まずはデシリアライズを起動する部分の処理。
XMLの解析にはNSXMLParserを使用します。

    NSData *xmlData = [_xml dataUsingEncoding:NSUTF8StringEncoding];
    ParserDelegateImpl *delegateImpl = [[ParserDelegateImpl alloc] initWithTargetClass:[User class]];
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
    parser.delegate = delegateImpl;
    [parser parse];
    
    id user = [delegateImpl resultObject];
    NSLog(@"name = %@",[user name]);

次に、処理の途中で必要となる情報を管理しておくためのクラスです。

「ElementInfo.h」
#import >Foundation/Foundation.h<
#import "PropertyInfo.h"

@interface ElementInfo : NSObject

//一つ下の階層の要素の値をセットする先となるオブジェクト
@property(strong,nonatomic) id target;

//要素名
@property(strong,nonatomic) NSString *elementName;

//要素に対応するプロパティの情報を管理するオブジェクト
@property(strong,nonatomic) PropertyInfo *propInfo;

@end

そして、次がデシリアライズ処理を行っている部分のソースです。

「ParserDelegateImpl.m」
#import "ParserDelegateImpl.h"
#import "ElementInfo.h"
#import "XmlEntity.h"
#import "PropertyInfo.h"

#import >objc/runtime.h<

@implementation ParserDelegateImpl{
    
    NSMutableArray *_elementStack;
    id _rootObject; //デシリアライズ結果のオブジェクト
}

-(id)initWithTargetClass:(Class)targetClass
{
    self = [super init];
    if(self){
        _elementStack = [NSMutableArray array];
        _rootObject = [[targetClass alloc] init];
    }
    return self;
}

-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI
      qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
    
    //ルート要素の場合
    if([_elementStack count] == 0){
        //最上位階層の情報を管理するElementInfoを生成し、Stackにつめる
        ElementInfo *info = [[ElementInfo alloc] init];
        info.elementName = elementName;
        info.target = _rootObject;

        [_elementStack addObject:info];
    }
    //ルート要素以外の場合
    else{
        //親要素の情報を取り出す
        ElementInfo *parent = [_elementStack lastObject];
        //Userクラスから要素に対応するプロパティの情報を取得する
        PropertyInfo *propInfo =[[parent.target class] propertyInfoForElement:elementName];
        if(!propInfo){
            return;
        }
        //プロパティが存在しているかチェックする
        objc_property_t prop =  class_getProperty([parent.target class], [propInfo.name UTF8String]);
        if(prop){
            //要素を管理するElementInfoを生成し、Stackにつめる
            ElementInfo *info = [[ElementInfo alloc] init];
            info.elementName = elementName;
            info.propInfo = propInfo;
            
            //プロパティの型に応じて処理を分ける
            //NSStringの場合
            if(propInfo.type == [NSString class]){
                info.target = [[NSMutableString alloc] init];
            }
            
            [_elementStack addObject:info];
        }
    }     
}

-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI 
   qualifiedName:(NSString *)qName
{
    //スタックにつめられている最後のオブジェクトを取得し、その要素名が引数のelementNameと一致するか判定する
    ElementInfo *lastElement = [_elementStack lastObject];
    if([lastElement.elementName isEqualToString:elementName]){
        //targetオブジェクトがNSMutableStringの場合(テキストノードの場合)
        if([lastElement.target isKindOfClass:[NSMutableString class]]){
            //もう一つ上の階層のオブジェクトのプロパティにテキストノードの値を設定する
            ElementInfo *parentElement = [_elementStack objectAtIndex:[_elementStack count] -2];
            [parentElement.target setValue:lastElement.target forKey:lastElement.propInfo.name];
        }
    }
    
    [_elementStack removeLastObject];
}

-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    //stackの最後のオブジェクトがNSMutableStringの場合、テキストノードの値をそれにつめる
    ElementInfo *lastElement = [_elementStack lastObject];
    if([lastElement.target isKindOfClass:[NSMutableString class]]){
        [((NSMutableString *)lastElement.target) appendString:string];
    }
}

//解析結果のオブジェクトを返却する
-(id)resultObject
{
    return _rootObject;
}

@end


XMLの要素の開始を発見する毎に、その要素に対応するElementInfoクラスのインスタンスを生成し、
NSMutableArrayに追加していきます。要素の終了タグを発見すると、NSMutableArrayの最後のオブジェクトを
削除します。

これによって、NSMutableArrayには常に、現在処理中の要素の上位要素が管理されていることになります。
要するにNSMutableArrayをスタックとして使用しているわけです。

デシリアライズ処理の過程では常に現在の要素の上位要素に対応するオブジェクトへのアクセスが必要になります。
今回のサンプルでは、NameタグのテキストをUserタグに対応するUserクラスのインスタンスのプロパティに設定します。
これをスタックの仕組みを使って実現しているのです。

今回のソースではまだとてもシンプルな形をしたXMLしか扱うことができません。
そして、XMLに処理対象ではないXMLがあった場合の挙動も考慮出来ていません。
ですから、まだ実用レベルには達していないかと思います。
次回はもう少しその辺りを突き詰めてみます。


XMLからオブジェクトのデシリアライズをするiOS用のライブラリ「ChimeraXML(キメラXML)」をgithubに公開しました。
iOSのXMLデシリアライズライブラリ「ChimeraXML」

さて、前回の記事の続きですが、本稿では、Javaのアノテーションや、C#のアトリビュートのように
Objective-cのプロパティにXMLの要素名をメタ情報として、付与する方法を紹介します。

といってもObjective-cには、アノテーションの仕組みはありませんので、クラスメソッドを定義し、そのメソッドで
メタ情報を返す仕組みを考えてみました。

まず、プロパティのメタ情報を保持するクラスとして、PropertyInfoクラスを定義しました。

「PropertyInfo.h」
@interface PropertyInfo : NSObject

@property(strong,nonatomic) NSString *name;
@property(strong,nonatomic) Class type;

@end

プロパティ名と型を持つようにしました。
型はデシリアライズ処理をする際に必要になります。

次に、デシリアライズ先となるクラスの共通の親クラスとなる「XmlEntity」クラスを定義します。

「XmlEntityu.h」
@interface XmlEntity : NSObject

+(PropertyInfo *)propertyInfoForElement:(NSString *)element;

@end

要素名に対応するプロパティの情報を返すクラスメソッドを定義しています。

実装ファイルは以下になります。

「XmlEntityu.m」
@implementation XmlEntity

+(PropertyInfo *)propertyInfoForElement:(NSString *)element
{
    return nil;
}

@end

子クラスでそれぞれの要素名に対応するプロパティ情報を返すように実装するのでここでは、ただ、nilを返すようにしています。

そして、このクラスをデシリアライズ先となるUserクラスにXMLEntityクラスを継承させます。
その前に今回解析する対象のXMLを以下のように少し変更します。

前回のXMLでは、要素名とプロパティ名が完全一致するようにしていましたが、
今回は、一致しないようにしました。

<User>
  <Id>1</Id>
  <Name>山田太郎</Name>
  <Email>xxxxx@xxxx.com</Email>
</User>

Userクラスは以下のようになります。

「User.h」

@interface User : XmlEntity

@property(strong,nonatomic) NSString *userId;
@property(strong,nonatomic) NSString *name;
@property(strong,nonatomic) NSString *email;

@end

「User.m」
@implementation User

+(PropertyInfo *)propertyInfoForElement:(NSString *)element
{
    static NSDictionary *_propDic;
    
    if(!_propDic){
        
        PropertyInfo *userId = [[PropertyInfo alloc] init];
        userId.name = @"userId";
        userId.type = [NSString class];
        
        PropertyInfo *name = [[PropertyInfo alloc] init];
        name.name = @"name";
        name.type = [NSString class];
        
        PropertyInfo *email = [[PropertyInfo alloc] init];
        email.name = @"email";
        email.type = [NSString class];
        
        _propDic = @{
                     @"Id": userId,
                     @"Name": name,
                     @"Email" :email
                     };
    }
    
    return _propDic[element];
}
@end

「XMLEntity」クラスで定義された「propertyInfoForElement:element:」メソッドをオーバーライドし、
要素名に対応するUserクラスのプロパティ情報を返却するようにしました。

PropertyInfoのtypeは動的に取得することも可能ですが、ここでは一旦、手動で設定しています。

さて、これで、ようやく準備が整いましたので、次回は、デシリアライズする部分を紹介します。


上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。