アプリケーションのサイズを小さくする:実績のある方法

はじめに



モバイルアプリケーション開発の重要な側面の1つは、サイズの最適化です。 個人の経験から、特にWi-Fiアクセスポイントが手元になく、モバイルインターネットの速度やトラフィックが望まれない場合は特に、アプリケーションの重量が少ないほど、ダウンロードする意思があることがわかります。 さらに、リリースされたアプリケーションのサイズに制限を設けている市場があることを忘れてはなりません。 たとえば、App Storeでは、最大100 MBのサイズの製品をモバイルインターネットでダウンロードできます。アプリケーションの重みがこのしきい値を超える場合、Wi-Fi経由でのみダウンロードできます。 Playマーケットでは、100 MB以上を消費するアプリケーションは原則としてダウンロードできません。 この記事では、ネイティブiOSアプリケーション開発者が製品の重量を減らすためにどのような方法とトリックを使用したかを説明し、これにネットワークで見られる実用的なヒントを追加します。









アプリケーションサイズを縮小する基本的な方法



グラフィックコンテンツ



優れたアプリケーションでは、デザインが重要な役割を果たします。 インターフェイスが最小限のものであるか、製品に小さな機能セットがある場合は、この手順をスキップできます。 プロジェクトの機能が豊富であるか、多数の配色をサポートしている場合、ここでは、重量に対するすべての結果を伴う多数の画像なしでは実行できません。 さらに、多くのプロジェクトでは、デフォルトで、モバイルデバイスのさまざまなフォームファクター(iOSアプリケーションの@ 1x、@ 2x、@ 3xなど)に画像のセットが追加されます。 以下に、豊富なグラフィックコンテンツの問題を解決するためにアプリケーションで使用された方法を示します。 おそらくあなたはそれらのいくつかを自分で使うでしょう。



最も簡単な方法の1つは、3つのスケールではなく3x画像のみを使用することです。 この方法は最適とは言えません。1xスケールと2xスケールに向けられたデバイスでは、そのような画像は常に受け入れられるとは限らないためです。 ただし、より良いものがないため、この手法は、大量のグラフィックを含むプロジェクトのサイズを縮小するのに役立ちます。



別の方法は、ラスター画像の代わりにベクター画像を追加することです。 iOSでは、画像をPDFにエクスポートしました。 多くの場合、このようなファイルは実際には軽量ですが、すべての画像で機能するわけではありません。 ここでの問題は、一部の画像マスクがベクターグラフィックスで正しく表示されず、完全に黒になったり、色が歪んだりすることです。



次に、いくつかの配色(一般的な人々の「肌」)を備えたアプリケーションの例を見てみましょう。 アプリケーションの配色が多いほど、必要な画像の数が増えます。 画像が複数の色を使用する場合、各スキンにいくつかのオプションを保存する必要があります。 ただし、画像がモノフォニックの場合、テンプレートを作成し、コード自体で既に色合いの色(色合い)を変更できます。 iOSでは、次の2つの方法で同様のテンプレートを作成できます。



  1. Xcode自体にテンプレートイメージを設定します(図1を参照)。
  2. プログラムでパターンモードを設定する






図1 Xcodeでテンプレート画像モードを設定します。



UIImage *templateImage = [[UIImage imageNamed:@«Back Chevron»] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; [backButton setTintColor:[UIColor blueColor]];
      
      





-UIImageRenderingModeAlwaysTemplateはテンプレート画像モードです。



アニメーション画像の置き換え



アニメーションの追加は、アプリケーションでは一般的です。 必要なインターフェイスオブジェクトにユーザーの注意を引き付け、静的なものを減らし、より快適なインタラクションエクスペリエンスを提供します。 オブジェクトを画面のある部分から別の部分に移動したり、新しいウィンドウの下から表示したりするなど、いくつかの単純なアニメーションはプログラムで実行できます。 より複雑なものでは、アニメーションの各フレームをレンダリングする必要があります。 開発中にアニメーション画像の追加に最初に遭遇したとき、その実装に最も一般的な方法の1つ、つまり画像の配列によるアニメーションを使用しました。 このように見えた:



 NSArray *gif=@[@"frame1",@"frame2",@"frame3",@"frame4",@"frame5", @"frame6",@"frame7",@"frame8",@"frame9",@"frame10"]; NSMutableArray <UIImage *> *images = [[NSMutableArray alloc] init]; for (int i = 0; i < gif.count; i++) { UIImage *image=[UIImage imageNamed:[gif objectAtIndex:i]]; [images addObject:image]; } imageView.animationImages = images; imageView.animationDuration = 0.3; imageView.animationRepeatCount=1; [imageView startAnimating];
      
      





最初に、画像の名前を持つ配列が作成され、次に名前の画像で順番に補足される配列が作成されます。 次に、UIImageView型の変数に対して、アニメーション、アニメーションの継続時間、繰り返し回数の画像の配列が設定されます。 その後、アニメーション自体が開始されます。 ただし、多くのフレームがあり、それぞれに3つのスケールがある場合、アプリケーションのサイズにとってこれはうまくいきません。 このような悲しい結論に至ったので、一連の写真の代わりにgifファイルを追加する方法を自問しました。 幸いなことに、インターネット上で、UIImage + animatedGIFというカテゴリに出会いました。 このカテゴリは、UIImageクラスに2つのメソッドを追加します。



 + (UIImage * _Nullable)animatedImageWithAnimatedGIFData:(NSData * _Nonnull)theData; + (UIImage * _Nullable)animatedImageWithAnimatedGIFURL:(NSURL * _Nonnull)theURL;
      
      





最初のメソッドはデータとして保存されたgifをロードし、2番目のメソッドはアプリケーションリンクなどのリソースリンクから直接取得します。 gifファイル自体は、1秒あたりのフレーム数、圧縮、解像度が設定されたファイルを作成するために、一部のサービスの同じフレームから作成できます。 パラメータを適切に設定すると、出力に許容可能な重みのGIFが与えられます。 今では、バンドルに追加して上記の方法のいずれかを使用するだけです。



ただし、gifファイルもスペースを占有するため、すべてのアニメーションをプログラムで実行しようとしました。 開始画面のオーディオエディターツールで、 文字でAUDIO EDITORロゴの外観のアニメーションを再生します。 以前、このアニメーションはgifを使用して実装されていましたが、画像の解像度が高いため、少し重くなりました。 したがって、CABasicAnimationを使用して実装することにしました。



 CAGradientLayer *gradient=[CAGradientLayer layer]; gradient.frame=animationLabel.bounds; gradient.colors = @[(id)[UIColor colorWithWhite:1 alpha:1.0].CGColor, (id)[UIColor clearColor].CGColor]; gradient.startPoint = CGPointMake(0.0, 0.5); gradient.endPoint = CGPointMake(0.1, 0.5); animationLabel.layer.mask=gradient; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.99 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ gradient.colors = @[(id)[UIColor colorWithWhite:1 alpha:1.0].CGColor, (id)[UIColor colorWithWhite:1 alpha:1.0].CGColor]; }); CABasicAnimation *startPoint=[CABasicAnimation animationWithKeyPath:@"startPoint"]; startPoint.fromValue= [NSValue valueWithCGPoint:CGPointMake(0.0, 0.5)]; startPoint.toValue= [NSValue valueWithCGPoint:CGPointMake(1.0, 0.5)]; startPoint.duration = 0.9; [startPoint setBeginTime:0.1]; startPoint.removedOnCompletion=NO; CABasicAnimation *endPoint=[CABasicAnimation animationWithKeyPath:@"endPoint"]; endPoint.fromValue= [NSValue valueWithCGPoint:CGPointMake(0.1, 0.5)]; endPoint.toValue= [NSValue valueWithCGPoint:CGPointMake(1.0, 0.5)]; endPoint.duration = 1.0; [endPoint setBeginTime:0.0]; endPoint.removedOnCompletion=NO; CAAnimationGroup *group = [CAAnimationGroup animation]; [group setDuration:1.2]; [group setAnimations:[NSArray arrayWithObjects:startPoint, endPoint, nil]]; [ gradient addAnimation:group forKey:nil];
      
      





GIFのようにロゴを文字ごとに表示するために、グラデーションマスクを使用しました。グラデーションマスクは、時間の経過とともに透明度の初期位置をシフトしました。 最初に、透明色が最初からほとんど変わらないグラデーションレイヤーを作成しました。 次に、グラデーションをロゴテキストのレイヤーのマスクとして設定し、透明にします。 次のステップは、2つのアニメーションが追加されたアニメーショングループを作成することでした。 それらの最初はグラデーションの初期位置をシフトし、2番目は最終で、それにより不透明になりました。 注意点が1つあります。重要な手順は、removeOnCompletionプロパティに負の値を指定することでした。そうしないと、アニメーションが最後に削除され、レイヤーが初期値に戻ります。



オーディオ変換



私たちのアプリケーションは、多くの場合WAVオーディオファイルを使用します。 その構造のため、このフォーマットはプロジェクトで多くのスペースを占有します。 このため、最初はバンドル内のこの形式のすべてのファイルをより軽量のM4Aに完全に置き換えてから、アプリケーション自体でそれらをWAVに変換することが決定されました。 なぜM4Aを使用しないのですか? なぜなら、この形式のファイルを周期的に再生している間、各サイクルの開始時に、ある種の空虚があるかのように遅延が生じるからです。 最後の手順は、最初に起動した後、既に変換されたファイルをアプリケーションディレクトリに保存することです。



 +(void)convertAudio:(NSURL *)url toUrl:(NSURL *)convertedUrl{ AVAudioFile *audioFile = [[AVAudioFile alloc] initForReading:url error:nil]; AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:audioFile.processingFormat frameCapacity:(uint32_t)audioFile.length]; [audioFile readIntoBuffer:buffer error:nil]; NSDictionary *recordSettings = @{ AVFormatIDKey : @(kAudioFormatLinearPCM), AVSampleRateKey : @(audioFile.processingFormat.sampleRate), AVNumberOfChannelsKey : @(audioFile.processingFormat.channelCount), AVEncoderBitDepthHintKey : @16, AVEncoderAudioQualityKey : @(AVAudioQualityMedium), AVLinearPCMIsBigEndianKey: @0, AVLinearPCMIsFloatKey: @0, }; AVAudioFile *writeAudioFile = [[AVAudioFile alloc] initForWriting:convertedUrl settings:recordSettings error:nil]; [writeAudioFile writeFromBuffer:buffer error:nil]; }
      
      





このメソッドでは、ファイルはURLによってバンドルから取得され、convertedUrlによってディレクトリに保存されます。 読み取られたファイルはバッファにロードされ、そこから必要な記録設定で新しいファイルに書き込まれます。 したがって、最初の起動後はより安定した重いWAVを使用しますが、同時にダウンロードとインストールの段階でアプリケーションのサイズが大幅に縮小されます。



サーバーからファイルをアップロードする



サーバーからのファイルのアップロードは、大量のコンテンツを含むアプリケーションに必要なものです。 多数の音楽プリセット、画像セットなど、アプリケーションのサイズを大幅に増加させるものは、後でダウンロードできます。 もちろん、個々のファイルをダウンロードするには多くの時間とトラフィックが必要になるため、必要なすべてのアーカイブがサーバーからロードされ、すでにアプリケーションに展開されてアプリケーションディレクトリに保存されます。 解凍には、SSZipArchiveライブラリが使用されます(ライブラリリポジトリはこちらにあります )。 このライブラリは、ファイルをアーカイブにパックすることとアーカイブをアンパックすることができます。 しかし、ライブラリのメインクラスの1つのメソッドにのみ関心があります。



 + (BOOL)unzipFileAtPath:(NSString *)path toDestination:(NSString *)destination progressHandler:(void (^)(NSString *entry, unz_file_info zipInfo, long entryNumber, long total))progressHandler completionHandler:(void (^)(NSString *path, BOOL succeeded, NSError *error))completionHandler;
      
      





このメソッドは、パスから宛先までファイルをアンパックし、progressHandlerでアンパックしている間、任意のアクション(アンパックの進行状況の表示など)を実行できます。その後、completionHandlerでアンパックが正常に完了したことを示し、失敗した場合はエラーを表示します。



おわりに



最終的に、Mix Waveアプリケーションで判断すると、インストール前に約41 MB、すべてのプリセットをロードした後(281 MB)、説明した方法でアプリケーションのサイズを約7倍削減できました。 結果は悪くありませんが、おそらくより関連する方法があります。 そのようなことを知っている場合は、コメントで共有することをお勧めします。



UPD:グラフィックコンテンツに関する有益なコメントをいただいたDim0vに感謝します 。 それらを以下に示します。



読む
「まず、アプリのスライシングはiOS 9以降を搭載したデバイスで機能します。 iTunes Connectは、ダウンロードしたアーカイブを異なるデバイス用のいくつかのオプションに再構築します。 したがって、たとえば、iPhone 6はアプリからインストールされた場合、@ 2xのリソースのみを引き出し、iPad mini 1は@ 1xのみを引き出します。 したがって、製品がiOS 9以降をサポートしている場合、3倍のリソースのみを残すことに関するアドバイスを聞くとまったく逆の効果があります-iPhoneの場合は何も変わりませんが、+ 。



第二に、ラスター画像をベクトルに変換することに関するアドバイスも意味がありません。 この方法で保存できるのは、開発者のコ​​ンピューター上の場所だけです。 Xcodeは、ビルドを構築するときにベクター画像をラスタライズします。これは、たとえば、デバイス上の「ベクター」画像をスケーリングし、乱暴にピクセル化されたビットマップを確認することで簡単に確認できます。 ベクトルリソースは便利です。デザイナーにエクスポートする方が簡単です。リソースを変更するとき、異なる解像度のすべてのバージョンが「同期」されていることを確認する必要はありません。 ただし、ビルドサイズを縮小する目的で、既存のビットマップイメージを正確にベクトルに変換することは意味がありません。



All Articles