Android, iPhone等のスマートフォン向けアプリ開発などの話題を中心に、時事ネタなどを気の向くままに書いています。
Home
 

iOS10.3のファイルパス問題について




iOS10.3の正式版がリリースされましたね。
早速ですが、アプリに不具合は出ていませんか?(笑)

iOS10.3では、ファイルシステムが Apple File System (APFS) に変更される、という事は前々からわかっていたのですが、流石に変な変更はしないだろうと考えていました。
そして、iOS10.3の正式版がリリースされた後、Twitterに「iOS10.3 は Unicode Normalization 無し」といった不吉なツイートが流れてきたので、急いでリリースされたiOS10.3を入れてビックリ! 自アプリが動かない!(汗)
折角の自アプリの新バージョンのリリース日にやってくれたー!という感じです(苦)
参照: iOS向け超高機能テキストエディタ 「Wrix」(無料)
参照: iOS向け超高機能ファイル管理&メモアプリ「NeoFiler」(無料)

参照: なるせにっき- macOS上のAPFSはUnicode Normalizationを行うのか?

なぜこの様な事態になったのかとこの問題の詳細を備忘録と情報共有の意味も含めて記載したいと思います。
間違いがありましたならばご指摘ください。

なお、ここに記載されている事に関連して損害を被った場合でも一切の補償はいたしません。自己責任でご利用下さい。

2017/03/30 わかりやすく修正しました。
2017/03/30 「UTF-8-MAC」の呼び方、符号化方式の違い等に付いて正確ではないとのご指摘を頂きました。ここでは本問題の理解をする事を優先したいと思いますので、詳細は以下のページを参照して下さい。
参照: ものかの – HFS+のテキストエンコーディング
参照: ものかの – 普通のUnicodeはNFCなのか

2017/03/31 CFStringNormalizeでの変換に関して、「CFStringNormalizeは純正のUnicode正規化形式を適用するメソッドなので、ファイルパスに適用するのは問題あり」とのご指摘を頂きました。詳細は以下のページを参照して下さい。
参照: Qiita – 結合文字列をUnicode正規化で合成する方法の危険性
2017/03/31 NFDに変換(またはiOS/Macのパス表現に変換)する方法をNSStringクラスのfileSystemRepresentationプロパティに変更しました。

2017/04/08 注意点を追加しました。

2017/04/10 NSString.fileSystemRepresentationプロパティをNSURL.fileSystemRepresentationに修正しました。

2017/04/12 UIDocumentInteractionController、QLPreviewControllerクラス使用時の注意を追加しました。

 

iOS/Macのファイルパスの基礎

iOS/Macは、ファイルパスにUTF-8の独自実装である「UTF-8-MAC」を使用してノーマライズしています。
一般的にはUTF-8の符号化方式は、Normalization Form C (NFC) ですが、iOS/Macの場合は Normalization Form D (NFD) です。
その為、一部の文字が使用されているファイルパスを使用すると符号化方式の違いによる問題が発生します。

その一部の文字ですが、日本語では濁点/半濁点が付いたひらがな/カタカナが該当し、元の文字 + (濁点/半濁点)の2文字に分解されます。

「ピ」という文字があった場合、濁点/半濁点が分割される為、以下の様に文字数が変わってしまいます。その為、ファイルパスが別物として扱われてしまい、うまく動かない、という事になります。

0xE30x830x94 <=                //Normalization Form C (NFC) 
0xE30x830x92 0xE30x820x9A <= ヒ ゜ //Normalization Form D (NFD)

NSFileManagerクラスの何らかの機能でパスを取得するとNFD方式のパスが返されます。しかし、UI等に表示する際には都合が悪いので、NFC方式に戻す必要があるかと思います。その際には以下の様にすればファイル名をNFDからNFCに変換できます。

NSMutableString *tempName = [NSMutableString stringWithString:[fileUrl lastPathComponent]];
CFStringNormalize((CFMutableStringRef)tempName, kCFStringNormalizationFormC);

または NSString クラスの precomposedStringWithCanonicalMapping プロパティが使用できます。
※これらの変換機能は、結合文字列をうまく扱えない場合がありますので、注意して下さい。

 

iOS 10.2.xまでの動作

iOS 10.2.xまでは、以下の様な動作をしていました。

  • NSFileManagerクラスに指定するパスはNFDとNFC方式どちらでも使用可能。
    ただし、何らかの機能でパスを取得するとNFD方式のパスが返される。
  • UIDocumentInteractionController、QLPreviewControllerクラスに指定するURLはNFDとNFC方式どちらでも使用可能
  • C言語のfopen関数等のFoundation API以外のファイルAPIで使用するパスはNFDとNFC方式どちらでも使用可能

※UIDocumentInteractionControllerクラスは共有機能、QLPreviewControllerはクラスはQuickLook機能で使用します。

 

iOS 10.3の動作

iOS 10.3は、以下の様な動作をします。

  • NSFileManagerクラスに指定するパスはNFDとNFC方式どちらでも使用可能。
    ただし、何らかの機能でパスを取得するとNFD方式のパスが返される。
  • UIDocumentInteractionController、QLPreviewControllerクラスに指定するURLはNFD方式のみ
  • C言語のfopen関数等のFoundation API以外のファイルAPIで使用するパスはNFD方式のみ

10.2.xと10.3の違いは、以下の通りです。

  • UIDocumentInteractionController、QLPreviewControllerクラスに指定するURLがNFD方式のみかどうか
  • C言語のfopen関数等のFoundation API以外のファイルAPIで使用するパスがNFD方式のみかどうか

言い換えると、Foundation API以外のファイルAPIに関しては新しいファイルシステムがUnicode正規化を行わなくなった事によるものと考えられます
UIDocumentInteractionController、QLPreviewControllerクラスに指定するURLに関しては、内部的にC言語のfopen関数等のFoundation API以外のファイルAPIが使用されている為に問題が出ていると考えられます

 

iOS 10.3では何が問題なの?

NSFileManagerクラスの処理は今まで通りのままで正常に動作しています。

問題なのは、UIDocumentInteractionController、QLPreviewControllerクラスとC言語のfopen関数等のFoundation API以外のファイルAPIが使用するパスがNFD方式のみになった事です。

パスがNFCからNFDのみになった事により、場合によってはNFDへの変換が必要になります。
例えば、ソース内で固定文字列としてファイルパスを指定する場合、NFCからNFDへの変換が必要になります。

NSURLクラスのfileSystemRepresentationプロパティを使用する事でパスをNFDに変換(またはiOS/Macのパス表現に変換)できるかと思います。
※詳細は「Apple File System Guide」のFAQページを参照してください。

Objetive-C:
@property(readonly) const char *fileSystemRepresentation;

Swift:
var fileSystemRepresentation: UnsafePointer<Int8> { get }

※NSURLの場合、正しいURL文字列を指定しないとNULLになる事があります。その場合はNSStringの同名プロパティが使えるかと思います。
※NSFileManagerクラスから取得したNSURLはURLエンコードされていますが、URLエンコードされている場合は正常に変換できない可能性があります。

上記の方法等を使用する事で自アプリの独自実装部分はNFCからNFDに変換すれば何とかなるかと思います。

しかし、外部ライブラリの内部処理はどうでしょうか?
ソースが公開されて入ればまだ何とかなりますが、ソースが公開されていない場合はライブラリ提供者に直してもらうしかありません。

確かに日本語のパスを使う事はそれ程はありませんが、実際に使用する場合はうまく動かなくなる可能性があります。
今回は日本語だけを扱っていますが、他の言語でも同様な問題がある可能性があります。

何にしても厄介な事には変わりませんね。

 

なお、実機では、NFDにしないと動作しませんが、シミュレータではNFC、NFD共に動作します。
えっ!?(笑)
この様に実機とシミュレータの動作が違いますので、この問題に関しては必ず実機でテストする様にしてください。
※結局、この問題は、iOS10.3のBeta版の時点で実機に入れて試していないと正式版リリースまで気が付かなかった、という事になります(苦)

 

注意点

実機とシミュレータの動作が違う事は既に説明しましたが、以下に挙げる他の部分での注意点を記載しておきたいと思います。

  • C言語のfopen関数を使うとNFCのファイル名を作成できる。
  • iTunes for Windowsからアプリにファイルを転送するとNFCのファイル名が渡される。

まずは、「C言語のfopen関数を使うとNFCのファイル名が作成できる。」問題です。
C言語のfopen関数でファイルを作成したとします。その際、NFCのファイル名を渡すとNFCのままの文字でファイルができてしまいます。濁点/半濁点は元の文字と分割されません。
そして、NSFileManagerクラスでファイル一覧を取得するとNFCのファイル名のままのURLが取得できます。しかし、取得したNFCのファイル名のURLをNSFileManagerクラスでそのまま処理しようとすると失敗します。

次に「iTunes for Windowsからアプリにファイルを転送するとNFCのファイル名が渡される。」問題です。
こちらは、iTunes for Windowsを使ってアプリにファイル転送する際、ファイル名がNFCのままになり、「C言語のfopen関数を使うとNFCのファイル名が作成できる。」問題と同様にNSFileManagerクラスではうまく扱えないファイルができてしまいます。
この問題はiTunes for macOSでは発生していません。

もし、同じファイルをiTunes for WindowsとiTunes for macOS両方で転送した場合、見た目は同じですが、別名ファイルとして扱われます。
NSFileManagerクラスで一覧を取得するとNFCとNFDの違いがある為、別URLとして扱われます。
しかし、NSFileManagerクラスで取得したURLを元にファイルの属性を取得するとNFDのファイルの方が優先というか上書きされる様です。更新日付を変えてみるとわかると思います。

上記の様に、この問題はなかなか奥が深いのですが、iOSアプリ内でfopen関数を使用したり、iTunes for Windowsを使う事でNSFileManagerクラスがうまく取り扱いできないNFCのファイル名のファイル/フォルダを作成できてしまう、という問題もあるという事です。

なお、こちらの問題もiTunes、NSFileManagerクラスの両方からBugReport済みです。

 

まとめ

Appleにはこの問題のBugReportは出しましたが、重複Closeされました。既に他にもおかしいと思っていた方が居た様ですね。

ちなみに、iOS10.3のRelease Notesには特にこの問題、というか変更の記載はありませんでした。
※Appleは、この様に多くのファイルを扱うライブラリを全滅させかねない大幅な変更を平気でするのですね(苦)

何となく「これがiOS10.3以降の仕様である!」とAppleは修正しない気がしています。
その為、Appleが直してくれるかも、という淡い期待を持たず、不具合が出る可能性があるのであれば、対策をしておく方が賢明かと思います。
この問題関連の不具合は全て前面に出るアプリが責められる事になりますし、多くの開発者の方はそれを望んでいないと思います。

自アプリ内の処理は何となるとして、外部ライブラリの内部処理まで対応させるかは悩み所ではありますね。
ソースが公開されていれば何とかなりますが、公開されていない場合はどうしようもないですし。
※公開されていても手間がかかる事には変わりありませんが。

Foundation APIに関しても、内部的な処理がブラックボックスで良くわからない為、Foundation APIだから大丈夫、という話でも無い事が問題を複雑化させています。
半角英数字以外のファイルパスを使用する箇所は総点検した方が良いかも知れません。

ちなみに、ウチのアプリで使用しているC言語で記載された外部ライブラリ(ソースコードあり)のfopen関数使用部分を対応させる事はできました!
その際には、C言語ファイルからObjective-Cファイルで用意したC言語関数(OSのバージョン判断&NFCからNFDに変換処理)を呼び出して対応しました。
参照: iOS向け超高機能テキストエディタ 「Wrix」(無料)
参照: iOS向け超高機能ファイル管理&メモアプリ「NeoFiler」(無料)

※WrixとNeoFilerの最新版では、iTunes for Windowsから転送されたファイルシステムの仕様を無視したファイル名の存在を確認した際に自動でファイル名を修正する機能を追加しました!(2017/04/18 追記)

これらの情報を元に多くの方に早めにこの問題を認識してもらい、同じ様に困る人が少なくなれば幸いです。

2017/03/29 This post was written by Categories: iOSiPhone Tagged with:
6 comments


6 Responses to “iOS10.3のファイルパス問題について”


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

*

Top