XMLとモデルの相互変換(Swift5)

## はじめに
Swift,XMLで検索するとパースについてはよく出てくるんだけどシリアライズやデシリアライズの情報があんまり出てこない。
実はみんなもっと別の概念で上手いことやってて自分だけ取り残されている可能性もあるけど、そこは置いておいて・・・
地味に苦労したので誰かの参考になればと思います。

## 実現すること
タップすると用意したXMLを読み込んで簡単なモデル（View）を表示します。
またモデルからXML文字列を生成します。

## 前提
[XMLMapper](https://github.com/gcharita/XMLMapper)ライブラリを使います。
PureなSwiftコードのみでの実現方法はわかりません。
またこのライブラリはSwift4.2に対応しているようでSwift5の環境では使えましたが警告が出ています。

## 準備
Podsでライブラリを追加します。
一般的な追加方法については以下の記事が参考になりますのでそちらを参照ください。
[【Swift】CocoaPods導入手順](https://qiita.com/ShinokiRyosei/items/3090290cb72434852460)
私の環境ではこのように設定しました。

```Podfile:Podfile
target 'Test' do
  use_frameworks!
  pod 'XMLMapper'
  pod 'XMLMapper/Requests'
end
```

## サンプルコード
まずViewControllerはひとまずテスト実施するために用意
タップしたらテストコードを実行するようにします

```ViewController.swift:ViewController.swift
//
//  ViewController.swift
//  Test
//
//  Created by hats on 2019/10/26.
//  Copyright © 2019年 hats. All rights reserved.
//

import UIKit

class ViewController: UIViewController, UIGestureRecognizerDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        // タップ操作の準備
        let tapGetsture = UITapGestureRecognizer(target: self, action: #selector(tapAction))
        self.view.addGestureRecognizer(tapGetsture)
        tapGetsture.delegate = self
    }

    
    @objc func tapAction(sender: UITapGestureRecognizer) {
        
        // インスタンスからXML
//        let m = ARTestView()
//        self.view.addSubview(m)
//        m.xmlName = "testモデルです!"
//        m.frame = CGRect(x: 200, y: 100, width: 150, height: 120)
//        let str = m.toXMLString()
//        print(str)
        
        // XMLからインスタンス
        do{
            let _path = Bundle.main.path(forResource: "test", ofType: "xml")!
            let strXml = try String(contentsOfFile: _path)
            let ar = ARTestView(XMLString: strXml)
            if ar == nil { return }

            self.view.addSubview(ar!)
            
        }catch(let err){
            print(err)
        }
        
    }

}
```

対象のモデルです。

```ARTestView.swift:ARTestView.swift
//
//  ARTestView.swift
//  Test
//
//  Created by hats on 2019/10/26.
//  Copyright © 2019 hats. All rights reserved.
//

import UIKit
import XMLMapper

class ARTestView: UIView ,XMLMappable {

    var nodeName: String!
    let dummySize = CGRect(x: 0, y: 0, width: 0, height: 0)

    // コードからインスタンス化
    init() {
        super.init(frame: dummySize)
        self.backgroundColor = UIColor.blue     // わかりやすいように色固定
    }
    // デシリアライズを通してインスタンス化
    required init?(map: XMLMap) {
        super.init(frame: dummySize)
        self.backgroundColor = UIColor.blue     // わかりやすいように色固定
    }
    // storyboardから作成
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    

    // 以下、各プロパティごとのXMLへの保存と読み込み用のIF
    var xmlName :String!
    var xmlFramePoint :XMLPoint! {
        get{
            return XMLPoint(pt: self.frame.origin)
        }
        set{
            self.frame.origin = newValue.getPoint()
        }
    }
    var xmlFrameSize :XMLSize! {
        get{
            return XMLSize(size: self.frame.size)
        }
        set{
            self.frame.size = newValue.getSize()
        }
    }
    func mapping(map: XMLMap) {
        xmlName <- map["Name"]
        xmlFramePoint <- map["Frame.Point"]
        xmlFrameSize <- map["Frame.Size"]
    }

}

class XMLPoint: XMLMappable{
    var nodeName: String!
    
    var _x:Double!
    var _y:Double!
    var x:CGFloat!
    var y:CGFloat!
    
    init(pt:CGPoint){
        self.x = pt.x
        self.y = pt.y
        self._x = (Double)(self.x)
        self._y = (Double)(self.y)
    }
    required init?(map: XMLMap) {}
    
    func mapping(map: XMLMap) {
        _x <- map.attributes["x"]
        _y <- map.attributes["y"]
        x = (CGFloat)(_x)
        y = (CGFloat)(_y)
    }
    func getPoint()->CGPoint {
        return CGPoint(x: self.x, y: self.y)
    }

}

class XMLSize: XMLMappable{
    var nodeName: String!
    
    var _width:Double!
    var _height:Double!
    var width:CGFloat!
    var height:CGFloat!
    
    init(size:CGSize){
        self.width = size.width
        self.height = size.height
        self._width = (Double)(self.width)
        self._height = (Double)(self.height)
    }
    required init?(map: XMLMap) {}
    
    func mapping(map: XMLMap) {
        _width <- map.attributes["width"]
        _height <- map.attributes["height"]
        width = (CGFloat)(_width)
        height = (CGFloat)(_height)
    }
    func getSize()->CGSize{
        return CGSize(width: self.width, height: self.height)
    }
    
}
```

読み込み対象のXMLです。
今回、簡単にファイルを組み込んで利用します。

```test.xml:test.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<root>
    <Name>testモデルです!</Name>
    <Frame>
        <Point x="300.0" y="200.0" />
        <Size height="120.0" width="150.0" />
    </Frame>
</root>
```


## ちょっとだけ解説
XMLMapperの使い方はリンクやReadMeを確認ください。
ARTestViewにXMLとの変換用のIFを実装しています。
UIViewController.swiftのtapActionでコメントアウトした`インスタンスからXML`を実行すればモデルからXML文字列を取得できます。

ポイントとしては、XMLMapperが変換できる型は少ないため、例えばUIViewのCGPointやCGSize、及びその構成しているCGFloat型はそのままではサポートされていないためラップしてあげる必要があります。
応用していけば様々なクラス構造や型に対応できるのかなと思います。



今回の実装はベストプラクティスではないと思います。
参考になったら、またもっと良い方法があるよとかがあればおしらせくださると幸いです。