web-dev-qa-db-ja.com

NSDateFormatterの割り当てまたは初期化が「高価」と見なされるのはなぜですか?

これが高いと言うとき、人々はどういう意味ですか?中間ストレージ用に多数の一時オブジェクトのインスタンスを作成します(NSStringとNSDateが一般的なものです)。 NSDateFormatterのプログラム使用がそれをやり過ぎているかどうかをどうやって知ることができますか?

これまで、シングルトンに相当するものを作成する傾向がありましたが、自己参照を使用できるように、関連付けられている他のオブジェクトのいくつかにカプセル化することを好みます。

パフォーマンステストを実行するのではなく、なぜこれを実行する必要があるのか​​、実行しないのかについてのより良い「経験則」の理解を探しています。

45
Draco

このようなものが高価であると言われるとき、それは必ずしもあなたが決してそれをしてはいけないという意味ではありません、それはあなたができるだけ早くメソッドから抜け出す必要がある状況でそれをしないことを意味します。たとえば、iPhone 3Gが最新のデバイスだった頃、私はUITableViewを使用して、各セルに表示する数値をフォーマットしたアプリケーションを作成していました(これは、iOS開発の初心者のときに戻ってきました。 )。私の最初の試みは次のとおりでした:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    NSString *reuseIdentifier = @"cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath];

    MyManagedObject *managedObject = [self.managedObjects objectAtIndex:indexPath.row];
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];

    [cell.textLabel setText:[managedObject title]];
    [cell.detailTextLabel setText:[numberFormatter stringFromNumber:[managedObject amount]]];

    return cell;
}

このコードのスクロールパフォーマンスはひどい。tableView:cellForRowAtIndexPath:がヒットするたびに新しいNSNumberFormatterを割り当てていたため、フレームレートは約15FPSに低下しました。

コードを次のように変更して修正しました。

- (NSNumberFormatter *)numberFormatter {

    if (_numberFormatter != nil) {
        return _numberFormatter;
    }

    _numberFormatter = [[NSNumberFormatter alloc] init];
    [_numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];

    return _numberFormatter;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    NSString *reuseIdentifier = @"cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath];

    MyManagedObject *managedObject = [self.managedObjects objectAtIndex:indexPath.row];
    NSNumberFormatter *numberFormatter = [self numberFormatter];

    [cell.textLabel setText:[managedObject title]];
    [cell.detailTextLabel setText:[numberFormatter stringFromNumber:[managedObject amount]]];

    return cell;
}

ここでの違いは、NSNumberFormatterをivarに遅延ロードしたため、tableView:cellForRowAtIndexPath:を実行するたびに新しいインスタンスが割り当てられなくなったことです。この単純な変更により、スクロールパフォーマンスが約60FPSに戻りました。

新しいチップはスクロールパフォーマンスに影響を与えることなく割り当てを処理できるため、この特定の例はもはやそれほど重要ではありませんが、可能な限り効率的であることが常に優れています。

39
Ell Neal

私はいつか同じ質問をしました。作業中のアプリでInstrumentsを実行しましたが、以前の開発者がカスタムログごとに新しいNSDateFormatterを作成していたことがわかりました。各画面について、彼らは約3行を記録していたので。このアプリは、NSDateFormattersの作成に約1秒を費やしていました。

簡単な解決策は、クラス内の日付フォーマッターインスタンスを属性などとして保持し、それを各ログ行に再利用することです。

ささいなことを考えた後、必要なフォーマットとロケールに基づいてNSDateFormattersの再利用を処理するための「ファクトリ」が付属しました。いくつかの形式とロケールの日付フォーマッターを要求すると、クラスはすでにロードされているフォーマッターを私に渡します。優れたパフォーマンスチューニングを試してみてください。

PS:誰かがそれをテストしたいと思うかもしれないので、私はそれを公開しました: https://github.com/DougFischer/DFDateFormatterFactory/blob/master/README.md

7
Douglas Fischer