Objective-C
Mac

NSURL で file URL にパラメータを付加する

More than 5 years have passed since last update.

+[NSURL fileURLWithPath:] は、 +[NSURL URLWithString:] と違って path に含まれる URL 特殊文字をエスケープする(?%3Fなど)。またそれが返す NSURL オブジェクトは -isFileURL が真になっていて、 -URLByAppendingPathComponent: などの引数も同様にエスケープする。File URL にパラメータ文字列(例:?key=value)を含めるには、次ように +[NSURL URLWithString:relativeToURL:] を使う:

FileURLWithParam.m
// こうする
NSURL *fileURL = [NSURL fileURLWithPath:path];
NSURL *URLWithParam = [NSURL URLWithString:@"?key=value" relativeToURL:fileURL];

// これはだめ
// path に `?` や `#` が含まれている場合、それらはエスケープされるべきだが、
// +[NSURL URLWithString:] はエスケープしてくれない
NSURL *URLWithParam = [NSURL URLWithString:[path stringByAppendingString:@"?key=value"]];

// これもだめ
// `?key=value` の `?` までエスケープされる
NSURL *URLWithParam = [NSURL fileURLWithPath:[path stringByAppendingString:@"?key=value"]];

正確には、この手順で得られる URLWithParam は file URL ではなくなる(-isFileURL が偽になる)ので気をつけること。

また、 -URLWithString:relativeToURL: の string 引数内の URL 特殊文字は一切エスケープされないので、必要ならあらかじめエスケープすること。

蛇足:なぜこうしたかったか

WebView で HTML を表示するとき、ローカルの JS や CSS を キャッシュを回避して 参照したかったから。

WebView に表示する HTML からは file:// スキームでローカルファイルを参照できるが、キャッシュがあるためファイルを書き換えて WebView をリロードしても古い内容が読み込まれてしまう。キャッシュをクリアしてもいいが、リモートから取得した画像などのキャッシュはできれば保持しておきたい。

そこで、ローカルファイルの URL のパラメータにタイムスタンプを追加し、キャッシュが効かないようにしたという次第。

- (void)reloadWebView {
  // ローカルの CSS の URL にタイムスタンプを追加
  NSURL *cssURL = [NSURL fileURLWithPath:@"/path/to/local.css"];
  NSString *timestamp = [NSString stringWithFormat:@"?timestamp=%lf", [[NSDate date] timeIntervalSince1970]];
  cssURL = [NSURL URLWithString:timestamp relativeToURL:cssURL];

  // WebView に HTML を表示
  NSString *HTMLFormat = (
    @"<html><head><link type='text/stylesheet' href='%@'></head>"
    @"<body>%@</body></html>"
  );
  NSString *HTML = [NSString stringWithFormat:contentFormat, [cssURL absoluteString], self.content];
  [self.webView.mainFrame loadHTMLString:HTML baseURL:self.baseURL];
}