7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[iOS] iOS10.3 で、ファイル名に濁点・半濁点のついたファイルにアクセスできない(Unicode 正規化)

Last updated at Posted at 2017-04-09
  • 追記(2017/04/09 23:50) Windows が採用しているファイルパスの正規化形式「NFC」で作成されたファイルにアクセス出来ないことが問題なのであり、「iTunes for Windows」の問題ではないと考えられる。そこでタイトルを以下のように変更。
    • 旧:[iOS] iOS10.3 で、iTunes for Windows から転送した濁点付きファイルにアクセスできない(Unicode 正規化)
    • 新:[iOS] iOS10.3 で、ファイル名に濁点・半濁点のついたファイルにアクセスできない(Unicode 正規化)

はじめに

  • iOS10.3上のアプリが、Windows などで作成した濁点・半濁点付きのファイルにアクセスできない問題について、解決方法を書いたものである。
  • コメント、ツッコミ歓迎です。

問題

  • Windows 上で作成したファイルを、iTunes のファイル共有機能などを通じてアプリに転送した際、濁点・半濁点を含むファイルにアプリ上からアクセスできない
  • 環境
OS/App Version
iOS 10.3.1
Windows 7
iTunes 12.6
Xcode 8.3
  • 例:2つのファイルにアクセス
    • ぱ_Mac.pdf:macOS(Sierra) の iTunes から転送
    • ぱ_Win.pdf:Windows7 の iTunes から転送
ファイルの存在チェック
NSArray* filepaths = ...
NSFileManager *fileManager = [NSFileManager defaultManager];

for(NSString *filepath in filepaths) {
        
    NSLog(@"==== %@ ====", filepath.lastPathComponent);

    NSLog(@"NSFileManager\t=> %@", ([fileManager fileExistsAtPath:filepath] ? @"YES":@"NO"));
}
出力結果(iOS10.0.1)
==== ぱ_Mac.pdf ====
NSFileManager	=> YES

==== ぱ_Win.pdf ====
NSFileManager	=> YES
出力結果(iOS10.3)
==== ぱ_Mac.pdf ====
NSFileManager	=> YES

==== ぱ_Win.pdf ====
NSFileManager	=> NO <<<< ??

原因

  • 実際のファイル名と、アクセスする際のファイル名について、それぞれの Unicode の正規化形式が異なり、別ファイル扱いとなってしまうため
アクセス方法 正規化形式 備考
Windows で作成したファイル NFC macOS で作成したファイルは NFD
NSFileManagerでアクセス NFD iOS10.3 で採用されたファイルシステム Apple File System(APFS) は、 NFD(Normalization Form Canonical Decomposition) 形式を前提としており、NSFileManager が内部でNFD形式に変換してるものと考えられる

解決方法

Foundation API を使う場合

  • [[NSURL alloc] initFileURLWithFileSystemRepresentation:[filepath UTF8String] isDirectory:NO relativeToURL:nil] を使うようにする
for(NSString* filepath in filepaths) {
                
     NSLog(@"==== %@ ====", filepath.lastPathComponent);

     NSURL* fileURL = [[NSURL alloc] initFileURLWithFileSystemRepresentation:[filepath UTF8String] isDirectory:NO relativeToURL:nil];
     NSLog(@"exist(NSURL)\t=> %@",     ([fileURL checkResourceIsReachableAndReturnError:nil] ? @"YES":@"NO"));
}
出力結果(iOS10.0.1)
==== ぱ_Mac.pdf ====
NSURL  => YES

==== ぱ_Win.pdf ====
NSURL  => YES
出力結果(iOS10.3)
==== ぱ_Mac.pdf ====
NSURL  => YES

==== ぱ_Win.pdf ====
NSURL  => YES

fopen などの低レベルな関数を使う場合

  • NSURL.fileSystemRepresentation か、 NSString.UTF8String を使う
fopen
...
for(NSString* filepath in filepaths) {
                
    NSLog(@"==== %@ ====", filepath.lastPathComponent);
    
    // NSURLでファイルパス情報を取得できたと仮定
    NSURL* fileURL = [[NSURL alloc] initFileURLWithFileSystemRepresentation:[filepath UTF8String] isDirectory:NO relativeToURL:nil];

    NSLog(@"fopen(NSRUL.fileSystemRepresentation)\t=> %@", ((fopen([fileURL fileSystemRepresentation], "r") != NULL) ? @"YES":@"NO"));
    NSLog(@"fopen(NSString.UTF8String)\t\t\t\t=> %@",      ((fopen([filepath UTF8String], "r") != NULL) ? @"YES":@"NO"));
}
出力結果(iOS10.0.1)
==== ぱ_Mac.pdf ====
fopen(NSURL.fileSystemRepresentation)	=> YES
fopen(NSString.UTF8String)				=> YES

==== ぱ_Win.pdf ====
fopen(NSURL.fileSystemRepresentation)	=> YES
fopen(NSString.UTF8String)				=> YES
出力結果(iOS10.3)
==== ぱ_Mac.pdf ====
fopen(NSURL.fileSystemRepresentation)	=> YES
fopen(NSString.UTF8String)				=> YES

==== ぱ_Win.pdf ====
fopen(NSURL.fileSystemRepresentation)	=> YES
fopen(NSString.UTF8String)				=> YES

疑問

NSString.fileSystemRepresentation の使用について

  • 低レベルな関数を使ってアクセスする場合、UTF8String ではなく NSString.fileSystemRepresentation を使うべし、という記述を見かける(ここここなど)が、NSString.fileSystemRepresentation だとアクセス出来ない。何かが間違えているのだろうか?
NSString.fileSystemRepresentation
...
for(NSString* filepath in filepaths) {
                
    NSLog(@"==== %@ ====", filepath.lastPathComponent);
    
    NSLog(@"fopen(fileSystemRepresentation)=> %@", ((fopen([filepath fileSystemRepresentation], "r") != NULL) ? @"YES":@"NO"));

}
出力結果
==== ぱ_Mac.pdf ====
fopen(fileSystemRepresentation)	=> YES

==== ぱ_Win.pdf ====
fopen(fileSystemRepresentation)	=> NO <<< ??
  • 追記(2017/04/09 23:10)
    • Apple File System Guide の FAQによれば、「Use the fileSystemRepresentation property of NSURL objects」とあるので、NString.fileSystemRepresentationではなく、NSURL.fileSystemRepresentation が正しいのではないだろうか。
    • 以下、引用(太線は追加)

To avoid introducing bugs in your code with mismatched Unicode normalization in filenames:
Use high-level Foundation APIs such as NSFileManager and NSURL when interacting with the filesystem
Use the fileSystemRepresentation property of NSURL objects when creating and opening files with lower-level filesystem APIs such as POSIX open(2), or when storing filenames externally from the filesystem

おまけ

  • Xcode などの標準出力文字列をコピーした際に、正規化形式もそのままコピペできる
NFC/NFD文字列をコピペ
NSString* filename1 = @"ぱ_Win.pdf"; // NFC
NSString* filename2 = @"ぱ_Win.pdf"; // NFD

NSLog(@"isEqual? %d", [filename1 isEqualToString:filename2]);
出力結果
isEqual? 0
  • Emacs では、NFCとNFD文字列を視覚的に判別できる
    スクリーンショット 2017-04-09 17.18.19.png

参考

iOS10.3 / APFS

Unicode 正規化形式

API

7
9
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?