iOSでdropbox連携を行う(第3.5回)

iOSのdropbox連携の第3.5回。
第3回でファイルリストの取得が中途半端になったので、その補足というか、完結回です。
前回では、リストを取得して名前を出力したけど、それがフォルダなのかファイルなのか判断がつかない状況でした。
今回は、それをちゃんと判断して扱えるようにします。

1.最終的なコード

いきなりですが、今回の完成コードが次になります。
ViewController.swiftの全コードです。

import UIKit
import SwiftyDropbox

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let client = DropboxClientsManager.authorizedClient
        
        client?.files.listFolder(path: "").response(queue: DispatchQueue(label: "MyCustomSerialQueue")) { response, error in
            if let result = response {
                for elem in result.entries {
                    if let file = elem as? SwiftyDropbox.Files.FileMetadata {
                        print("filename = " + file.name)
                    }else if let folder = elem as? SwiftyDropbox.Files.FolderMetadata {
                        print("foldername = " + folder.name)
                    }else{
                        print("other")
                    }
                }
            }
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @IBAction func pushAuthentication(_ sender: Any) {
        DropboxClientsManager.authorizeFromController(UIApplication.shared,
                                                      controller: self,
                                                      openURL: { (url: URL) -> Void in
                                                        UIApplication.shared.openURL(url)
        })
    }
}

赤字の部分が今回のポイントとなるコードとなります。
これを実行すると次のようなログとなります。

foldername = プロジェクト
foldername = Photos
foldername = Public
foldername = private_data
foldername = カメラアップロード
foldername = program
foldername = 共有テスト
foldername = 勉強会資料
filename = はじめに.pdf
filename = ゲーム作りのフロー_120302.ppt
filename = SDキャララフ.lip
filename = test120509.java
filename = memo.txt
filename = MP_プロット
filename = 画面仕様書考案メモ

ちゃんとフォルダとファイルが判断できていますね。
ちなみに、ファイル名を出力しているところを次のように変えると、ファイルサイズも取れます。

print("filename = ",file.name,", size = ",file.size)
foldername = プロジェクト
foldername = Photos
foldername = Public
foldername = private_data
foldername = カメラアップロード
foldername = program
foldername = 共有テスト
foldername = 勉強会資料
filename =  はじめに.pdf , size =  231991
filename =  ゲーム作りのフロー_120302.ppt , size =  122368
filename =  SDキャララフ.lip , size =  1111040
filename =  test120509.java , size =  23479
filename =  memo.txt , size =  69
filename =  MP_プロット , size =  4166
filename =  画面仕様書考案メモ , size =  4107

これで、第3回分の完成となります。
ですが、結果だけ示して終わりでは少し味気ないので、この結果に至った経緯を説明したいと思います。
Tips的なことだけ知りたい方は、これ以降を読まなくても大丈夫ですので、そのまま次回をお待ちください。

2.情報の整理

さて、それではSwiftyDropboxのインターフェイス部分を少し探って見ましょう。
探るためのキーとなるのは次の事柄です。
・ resultをprintで表示した場合は色々なデータが取れている
・ result.entriesで要素を取り出すと名前のメンバくらいしか取れない
・ そもそもresultとかresult.entriesってどんな型(クラス)なの?
それと、正確な情報が欲しいので、次のサイトも参照します。
Dropbox API v2 Swift SDK
Dropbox API v2
1つ目はSwiftyDropboxのリファレンス、2つ目はDropboxのAPIのリファレンスです。
最終的に2つ目は特に使う必要がなかったのですが、一応載せておきます。
この情報と実際のコードを元に動きを紐解いていきましょう。

3.コードの分解

まずは問題となったコードを少し整理して見ます。

        let client = DropboxClientsManager.authorizedClient
        client?.files.listFolder(path: "").response(queue: DispatchQueue(label: "MyCustomSerialQueue")) { response, error in
            if let result = response {
                for elem in result.entries {
                        ……
                }
            }
        }

これがポイントとなるコードの全体です。
1行目の client を取得している部分は単にAPIのクライアントを取得しているだけなので問題ないでしょう。
ポイントは2行目のブロックです。
このままだと解りにくいので、少し分解しましょう。

client?.files
.listFolder(path: "")
.response(queue: DispatchQueue(label: "MyCustomSerialQueue"))
{ response, error in …… }

ずらーっと書かれていた部分は4つの要素で構成されています。
メソッド名から推理して行くと、

client?.files

ファイル関連のインスタンスを取得している

.listFolder(path:"")

pathが””のフォルダリスト取得する(実際には次が実際の実行文になるので準備する)

.response(…)

応答を得る(前回記載通り、別スレッドで実行します)

{}

このブロックで実行結果が返ってくるので処理を行います。
responseに取得情報が入って、何らかのエラーがあればerrorにエラー情報が入るのでしょう。

4.解析1

今回、応答を処理するブロックのresponseは一体何なのか?がポイントです。
Swiftは型推論なのでぱっと見では解りませんが、Quick Helpでは推論した型が確認できます。
実際に確認してみると、response変数は

(Files.ListFolderResult)?

と出ます。
このFilesクラスのListFolderResultは何でしょう?名前からして結果のクラスなのは確実です。
ここからは検索を行うか、Commandを押しながらコードをクリックして定義を追って行きます。
検索してみたら、次のコードが出てきました。

    /// The ListFolderResult struct
    open class ListFolderResult: CustomStringConvertible {
        open let entries: Array<Files.Metadata>
        open let cursor: String
        open let hasMore: Bool
        public init(entries: Array<Files.Metadata>, cursor: String, hasMore: Bool) {
            ……
        }
        open var description: String {
            return "\(SerializeUtil.prepareJSONForSerialization(ListFolderResultSerializer().serialize(self)))"
        }
    }

ここでのポイントは

open let entries: Array<Files.Metadata>

です。
このentriesメンバにファイル情報が入っているのは明らかですので、次はこのMetadataが何かを探りましょう。

    /// Metadata for a file or folder.
    open class Metadata: CustomStringConvertible {
        open let name: String
        open let pathLower: String?
        open let pathDisplay: String?
        open let parentSharedFolderId: String?
        public init(name: String, pathLower: String? = nil, pathDisplay: String? = nil, parentSharedFolderId: String? = nil) {
            ……
        }
        open var description: String {
            return "\(SerializeUtil.prepareJSONForSerialization(MetadataSerializer().serialize(self)))"
        }
    }

見やすくするためにコメント関係は削除していますが、上のように定義されています。
前回はこのnameを取得していたのですが、やはりそれ以外には有益な情報が取れないみたいです。
さて、これ以上は探れるものが無くなってしまいました。
……
実は、前回はここで時間切れとなってしまったのです。

5.解析2

普通にライブラリの設計を考えて、名前だけ取れてそれがフォルダなのかファイルなのか判断できないなんてことはあり得ないです。そんなライブラリ使いにくいです。
ということで、4.で追ったMetadataクラスにもう少し注目してみます。
何か情報がないかと Dropbox API v2 Swift SDK を眺めていたら見つけました。
どうやら FolderMetadata や FileMetadata というクラスが存在するみたいです。
そして、どちらのクラスもFiles.Metadataを継承しています。
つまり、フォルダならFolderMetadata、ファイルならFileMetadataで情報を格納していて、
レスポンスを返す時には親クラス型のアレイでまとめているのでは?となります。
ここまで推測できれば試しに、レスポンスで帰ってきた情報を FolderMetadataやFileMetadataにキャスト出来るか試してみるだけです。

        client?.files.listFolder(path: "").response(queue: DispatchQueue(label: "MyCustomSerialQueue")) { response, error in
            if let result = response {
                for elem in result.entries {
                    if let file = elem as? SwiftyDropbox.Files.FileMetadata { // File?
                        print("file")
                    }else if let folder = elem as? SwiftyDropbox.Files.FolderMetadata { // Folder?
                        print("folder")
                    }else{
                        print("other")
                    }
                }
            }
        }

これで実行すると”file”や”folder”が出力されます。予想通りの動きです。
ちなみに、”other”が出力される場合 DeletedMetadata というクラスと思われます。
念のためにMetadataを継承しているクラスを調べたところ、DeletedMetadata も出てきました。
名前からして削除されたフォルダなりファイルということだと思いますが、
今回の目的としては現存しているデータを取るのが目的なので無視することにします。

ここまで調べて完成したコードが初めに載せたものとなります。
少し長くなりましたし、調査方法の細部(Quick Helpの味方など)を省いたので分かりにくい部分があるかもしれませんが、
これでやっと次の段階に進めます。

次回は、実際にファイルをダウンロードしてみたいと思います。
画像とかPDFだと新たにViewを作らないといけないので、テキストだけに絞ってログ出力だけにしようかな?
…… 実際にどうなるか、乞うご期待!

Posted in システム開発 and tagged .