tomoyaonishiのブログ

iOSのことを中心に・・・その他もあるよ!

Swift: AutoLayoutでUIVisualEffectviewをアニメーションさせてみた

iOS8からUIVisualEffectViewというものが追加されました。このビューは様々なエフェクトを自動で表示することができます。Appleの公式すりガラス処理を実現することができます。 今回は、Swiftを使って、このクラスの使い方とAutoLayoutによるアニメーションをまとめます。


スライドにもまとめています。

UIVisualEffectView

様々なエフェクトを自動で表示することができるビューです。Appleのすりガラス処理もしてくれます。具体的にどんなエフェクトにするかを決めるのはUIVisualEffectという別のクラスです。インスタンス生成時にこのクラスを渡します。

UIVisualEffect

エフェクトの種類を指定するクラスです。利用するのはそのサブクラスでUIBlurEffect, UIVibrancyEffectの2つが現在のところ用意されています。UIBlurEffectがすりガラス処理です。 UIBlurEffectにはスタイルが3つあります。非常に明るいブラー、明るいブラー、暗いブラーの3つです。

enum UIBlurEffectStyle : Int {
     case ExtraLight
     case Light
     case Dark
}

UIVisualEffectViewの生成方法

実際の生成コードは以下のようになります。

let effect = UIBlurEffect(style: UIBlurEffectStyle.Light)
let effectView = UIVisualEffectView(effect: effect)
// set frame
view.addSubview(effectView)

これだけのコードでApple公式のすりガラス処理が入るビューを利用することができます。UIImage+ImageEffectのように画像ではなく、動的に描画されるのでビューを動かしても問題ありません。また、動作も早いです。

AutoLayoutでアニメーション

今回はさらに、このビューをAutoLayoutを使ってアニメーションさせてみます。 frameでのアニメーションは一般的なので問題無いと思いますが、AutoLayoutでのアニメーションはどのようにやるのかわかりますか?

AutoLayoutではframe, boundsは基本的には触りません。むしろ勝手にいじるとAutoLayoutの整合性が保てなくなりクラッシュすることもあります。

では、どうするか。AutoLayoutの実態であるNSLayoutConstraintクラス(AutoLayoutではこれを制約という)を操作します。 NSLayoutConstraintクラスにconstantというプロパティがありますが、このプロパティはその名の通り、距離を表します。例えば、画面の左端から10ptあけてビューを表示するという制約(NSLayoutConstraint)があったとき、constantは10です。ここでconstantに100を入れると、画面左端から100ptの場所に移動します。frameは意識しなくてよいです。例えば、幅は100ptで固定するという制約があった場合、そのconstantに200を入れると幅が自動で200ptになります。ここでもframe, boundsを直接操作する必要はありません。 このようにAutoLayoutでは、制約のconstantを変更することでレイアウトを動的に変えることができます。もちろん、制約の再生成でもよいですが。

では、具体的にアニメーションのコードを見てみましょう。一部省略しています。 まずはUIVisualEffectViewを生成し、コード上でAutoLayoutを適用します。

// viewDidLayoutSubviewsかviewDidAppear内で
// View
let effect = UIBlurEffect(style: UIBlurEffectStyle.Light)
let effectView: UIVisualEffectView = UIVisualEffectView(effect: effect) effectView.clipsToBounds = false
// falseにすると AutoResizingMaskをAutoLayoutの制約に自動変換しないようになる
effectView.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(effectView)

// AutoLayout
// NSDictionaryOfVariableBindings関数はどこにいったのかよくわからないので自分で辞書を生成
let views = ["effectView" : effectView]
let metrics = ["marginZero" : 0, "marginTop" : 100]

// 水平方向の制約を追加:superviewに対してぴったり張り付く
let horizontalConstraints: AnyObject[] =
NSLayoutConstraint.constraintsWithVisualFormat("|-marginZero-[effectView]-marginZero-|",
                                                options: NSLayoutFormatOptions(0),
                                                metrics: metrics,
                                                views: views)
view.addConstraints(horizontalConstraints)

// 垂直方向の制約を追加:superviewに対して上は100ptあける、下はぴったり張り付く
let verticalConstraints: AnyObject[] =
NSLayoutConstraint.constraintsWithVisualFormat("V:|-marginTop-[effectView]-marginZero-|",
                                                options: NSLayoutFormatOptions(0),
                                                metrics: metrics,
                                                views: views)
view.addConstraints(verticalConstraints) 

// アニメーションのために上からの制約を保持
let constraint : AnyObject = verticalConstraints[0]
marginTopConstraint = constraint as? NSLayoutConstraint

これでUIVisualEffectViewは画面に対して、上から100あけて、左、下、右は画面にピッタリくっつくように表示されます。AutoLayoutなので横向き、iPad、どんなサイズの画面でも同じように表示されます。frameもboundsもいじっていません。

次に、アニメーション部分です。Storyboard上では、ブラーの効果がわかりやすくするため画面いっぱい広がるように適当な画像を表示しておき、ボタンを1つおいてIBActionで接続しておきます。そのボタンを押すたびにUIVisualEffectViewがアニメーションします。

var flag : Bool = false
@IBAction func didTapButton(sender: UIButton) {
    if let constraint = marginTopConstraint {
        UIView.animateWithDuration(1.0,
            delay: 0.0,
            usingSpringWithDamping: 0.5,
            initialSpringVelocity: 0.1,
            options: UIViewAnimationOptions(0),
            animations: {
          // frameのアニメーションと同じ考えだとアニメーションできない 
          // constantを変更するだけでは足りない 
         constraint.constant = self.flag ? 150 : 500
          // 画面の再描画を呼び出す必要あり 
         self.view.layoutIfNeeded()
            },
         completion: nil)
         flag = !flag 
    }
}

これを実行すればびよんびよんとアニメーションするはずです。 基本的にはAutoLayoutはStoryboard上で設定するはずです。その場合は、IBOutletと同じようにNSLayoutConstraintを接続すれば変数が作れます。

個人的にはframeによるレイアウトはiOS7のころから終わっていると思っています。Xcode5にも16:9, 4:3などのレイアウト設定がありました。Androidはとっくの昔に絶対座標での指定は非推奨になっています。Xcode6のことはあまり詳しく書けませんが、さらに多種多様な画面サイズを試すことができるようになっているので基本的にはAutoLayoutでやりましょうというスタンスがよいと思っています。

メソッド、クラス、変数、定数宣言時に使えそうなものまとめ

iOS開発でメソッド、クラス、変数、定数の宣言に使えそうなものをまとめました。 Appleのヘッダーファイルを見ているとよく出てくる

NS_AVAILABLE_IOS

みたいなやつで個人的に使えそうだと思ったものをまとめます。


資料はこちら


NS_CLASS_DEPRECATED_IOS

指定したOSバージョンからそのクラスを非推奨にします。

NS_CLASS_DEPRECATED_IOS(7_0, 7_1)
@interface TestClass : NSObject

@end

iOS7.0まではOK、iOS7.1からは非推奨(Deprecated)という意味になります。 実際にXcodeでiOS7.1の環境でTestClassを使ってコンパイルしようとするとワーニングが出るようになります。

NS_CLASS_AVAILABLE_IOS

特定のOSバージョンからそのクラスを使えるようにします。

NS_CLASS_AVAILABLE_IOS(7_1)
@interface TestClass : NSObject

@end

TestClassはiOS7.1から利用可能になります。

NS_AVAILABLE_IOS

指定したOSバージョンからクラス、メソッド、定数を使えるようにします。

@interface TestClass : NSObject

- (void)method NS_AVAILABLE_IOS(7_1);

@end

methodメソッドはiOS7.1以降の環境でないと呼べません。定数の後ろに宣言することで定数にも同じ制限がかかります。

NS_DEPRECATED_IOS

指定したOSバージョンからクラス、メソッド、定数を非推奨にします。

FOUNDATION_EXPORT 
CGFloat const kTestClassConstant NS_DEPRECATED_IOS(6_0, 7_0);

iOS7.0以上でこの定数を利用しようとするとコンパイラがワーニングを出してくれます。

NS_UNAVAILABLE

いかなる環境でもそのクラス、メソッド、定数を使えないようにします。

@interface TestClass : NSObject

- (void)unavailableMethod NS_UNAVAILABLE;

@end

unavailableMethodは絶対に呼ぶことはできません。利用しようとするとコンパイラはワーニングではなく、エラーを出します。

NS_REQUIRES_SUPER

サブクラスでオーバライドした際にスーパークラスメソッドを呼ぶようにします。 なにげにかなり便利です。

@interface TestClass : NSObject

- (void)overrideMethod NS_REQUIRES_SUPER;

@end

TestClassをサブクラス化し、overrideMethodをオーバーライドした時に、[super overrideMethod]の形で呼び出さないとコンパイラがワーニングを出します。

NS_REQUIRES_NIL_TERMINATION

可変長の引数の最後に必ずnilを指定するようにします。

@interface TestClass : NSObject

- (void)method:(NSString *)first, ... NS_REQUIRES_NIL_TERMINATION;

@end

methodの引数の最後にnilをしてしなければならないようになります。UIAlertViewのあれですね。

NS_ENUM

定数を列挙します。

typedef NS_ENUM(NSUInteger, TestClassOptions) {    
    TestClassTypeA,
    TestClassTypeB,
    TestClassTypeC,
    TestClassUnknown = NSUIntegerMax
};

NS_OPTIONS

ビット演算によるオプションを列挙します。

typedef NS_OPTIONS(NSUInteger, TestClassOptions) {
    TestClassOptionsNone    = 0,
    TestClassOptionsA          = 1 << 0,
    TestClassOptionsB          = 1 << 1,
    TestClassOptionsC          = 1 << 2,
    TestClassOptionsAll        = A | B | C
};

instancetype

返り値のインスタンスの型がそのクラスであることをチェックします。

@interface TestClass : NSObject

- (instancetype)init;

@end

initの返り値をTestClassとそのスーパークラス以外の型に入れようとするとコンパイラがワーニングを出します。

__unused

未使用であることを宣言します。

static CGFloat const __unused kConstant = 10.0;

unusedを付けないとコンパイラが未使用の定数とワーニングを出してきますが、unusedをつけることで黙らせることができます。統一性のために宣言だけしたい変数や将来使う定数、変数のために使うと便利かもしれません。

__strong

強参照でインスタンスが解放されないようにします。 変数宣言で使えます。

__weak

弱参照で参照先が解放されたとき自動でnilをセットします。 変数宣言で使えます。

__block

ブロックの中で書き換えできるようにします。 変数宣言で使えます。

__unsafe_unretained(非ARC)

参照先が解放されても自動でnilをセットせず、 retainもしないためクラッシュする可能性があることを示します。 変数宣言で使えます。

AVFoundationのキャプチャ機能について

AVFoundationによるキャプチャ機能をまとめたいと思います。


まず、普段あまり使うことがないAVFoundationの簡単な紹介です。このフレームワークAppleが用意する「音声・動画などの時間ベースのメディアの再生や作成、編集の細かい作業を行うための超強力な低レベルObjective-C API」です。

しかし、AVFoundationはおそらくAppleが用意するフレームワークの中でも1,2を争う巨大なフレームワークです。その機能を全て紹介することは難しいため、今回はその中でも音声・画像・動画のキャプチャ機能について実際のコードを絡めて紹介したいと思います。 AVFoundationを利用することでUIImagePickerControllerよりもさらにカスタマイズ可能なカメラやレコーダーを開発することができます。

なお、この記事ではかなり噛み砕いて説明しているため初心者の方でも問題無いですが、AVFoundationの機能を使って開発を行うためには、Objective-Cの基礎文法、ブロック、Foundation, CoreFoundation, retainCount、サブスレッドやキューの作成などの知識があるほうがよいです。


こちらの資料にも簡単にまとめてあるので、こちらを見てもどうぞ。


AVFoundationにおけるキャプチャの全体像

全体像としては以下のようになっています。

f:id:tomoyaonishi:20140629011434j:plain

真ん中の赤で示しているAVCaptureSessionというものがキャプチャに関連する入出力を管理するクラスです。 次に緑で示しているAVCaptureDeviceというものが前面カメラや背面カメラ、マイクといったデバイスそのものを表現するクラスです。 AVCaptureDeviceをAVCaptureSessionにそのまま接続することはできません。AVCaptureSessionに接続するためのクラスが紫のAVCaptureDeviceInputというクラスです。その名の通り入力を表現します。 青で示しているAVCaptureOutputというものが出力方法を表現するクラスです。画像、動画、フレームデータ、音声データ、メターデータなど様々な出力クラスが用意されています。 AVCaptureOutputとAVCaptureSessionの間の黒い矢印はAVCaptureConnectionというクラスです。入力データの向きを変更したりできます。

AVCaptureSession

このクラスはキャプチャに関する入力と出力の管理を行います。

self.session = [[AVCaptureSession alloc] init];

で生成することができます。

セッションに対してキャプチャクオリティの設定ができます。

@property(nonatomic, copy) NSString *sessionPreset;

AVCaptureSessionPresetHigh, AVCaptureSessionPresetMedium, AVCaptureSessionPresetLow, AVCaptureSessionPresetPhotoなどを設定することができます。 AVCaptureSessionPresetPhotoは静止画のキャプチャに対してのみ有効でそのデバイスの最大解像度でキャプチャできます。フレームデータや動画のキャプチャに対しては有効になりません。

セッションの設定を変更する際は

- (void)beginConfiguration;

を呼び、変更し終わった後に、

- (void)commitConfiguration;

を呼ぶようにしてください。

後述するInputやOutputを追加し準備ができた段階で

- (void)startRunning

を呼ぶとセッションが動き出し実際にデータが取得できるようになります。忘れないようにしてください。 またこのメソッド非常に遅いため非同期でも問題ないなら非同期で呼ぶことをおすすめします。

AVCaptureDevice

このクラスはカメラやマイクといったデバイスそのものを表現します。

背面カメラを取得するのであれば

self.camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

となります。

マイクを取得するのであれば

self.microphone = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];

となります。

背面カメラか前面カメラの判定は以下のプロパティを使用します。

@property(nonatomic, readonly) AVCaptureDevicePosition position;

任意のポジションのカメラを取得したい場合は

for (AVCaptureDevice *camera in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
        if (camera.position == AVCaptureDevicePositionFront) {
    // 前面カメラ
       }
       else if (camera.position == AVCaptureDevicePositionBack) {
    // 背面カメラ
       }
}

といったコードになります。

iPhoneのカメラには様々な設定をすることができます。 露出モード

@property(nonatomic) AVCaptureExposureMode exposureMode;

フォーカスモード

@property(nonatomic) AVCaptureFocusMode focusMode;

ホワイトバランスモード

@property(nonatomic) AVCaptureWhiteBalanceMode whiteBalanceMode;

フォーカス位置

@property(nonatomic) CGPoint focusPointOfInterest;

などなど他にもフラッシュやトーチなど多くの設定ができます。 また、各プロパティに値をセットすることができるか確認するメソッドがあるので、必ず確認してから値をセットするようにしてください。 例えばiPadでフラッシュをONにしようとするとクラッシュします。

AVCaptureDeviceに対して何かしらの設定を行う場合はデバイスをロックしなければなりません。

- (void)lockForConfiguration:(NSError **)error

でロックし、設定が終わり次第

- (void)unlockForConfiguration

でロックを解除します。

AVCaptureDeviceInput

このクラスは指定したデバイスをセッションに入力するときに使います。

self.cameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.camera error:NULL];

で生成することができます。

[self.session addInput:self.cameraInput]

とすることでセッションにカメラが入力機器として接続された状態になります。

この状態でセッションのstartRunningメソッドを呼ぶことでカメラからのデータがセッションに入ります。

AVCaptureOutput

このクラスには様々なサブクラスが用意されています。

  • AVCaptureStillImageOutput(静止画)
  • AVCaptureMovieFileOutput(動画ファイル)
  • AVCaptureAudioFileOutput (音声ファイル)
  • AVCaptureVideoDataOutput(動画フレームデータ)
  • AVCaptureAudioDataOutput(音声データ)

任意の出力方法を選びセッションに追加することで画像や動画、データがキャプチャできます。

AVCaptureStillImageOutput

カメラからの入力から静止画をキャプチャします。

self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init];

で生成できます。 iOS7以降は手ぶれ補正をオンにすることができます。

@property(nonatomic)  BOOL automaticallyEnablesStillImageStabilizationWhenAvailable

あとはセッションに追加するだけです。

[self.session addOutput:self.stillImageOutput];

画像をキャプチャする

- (void)captureStillImageAsynchronouslyFromConnection:(AVCaptureConnection *)connection completionHandler:(void (^)(CMSampleBufferRef imageDataSampleBuffer, NSError *error))handler;

を呼ぶと非同期で静止画がキャプチャされ、結果がimageDataSampleBufferという形で取得できます。 このままではUIKitで使いづらいため、NSDataに変換するメソッドが用意されています。

(NSData *)jpegStillImageNSDataRepresentation:(CMSampleBufferRef)jpegSampleBuffer

です。 NSDataにはExifなどのメタデータも含まれています。

AVCaptureMovieFileOutput

このクラスは簡単に動画をキャプチャすることができます。

self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];

で生成できます。 撮影時間は以下のプロパティで取得できます。

@property(nonatomic, readonly) CMTime recordedDuration;

その他様々なプロパティが用意されています。

あとはセッションに追加するだけです。

[self.session addOutput:self.movieFileOutput];

動画のキャプチャを開始を開始するには以下のメソッドを呼びます。

[self.movieFileOutput startRecordingToOutputFileURL:self.movieURL            recordingDelegate:self];

停止するには以下のメソッドを呼びます

[self.movieFileOutput stopRecording];

開始停止のメソッドを呼ぶと対応したAVCaptureFileOutputRecordingDelegateメソッドが呼ばれます。

キャプチャを停止した場合、以下のデリゲートメソッドが呼ばれます。

(void)captureOutput:(AVCaptureFileOutput *)captureOutput                 didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error

outputFileURLに動画ファイルが保存されています。

AVCaptureAudioFileOutput

AVCaptureMovieFileOutputと同じようなものなので省きます。

AVCaptureVideoDataOutput

このクラスは入力機器から入ってくる映像の各フレームデータをそのまま取得できます。

self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];

で生成します。

また、映像の設定として以下の辞書をセットします。

self.videoDataOutput.videoSettings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };

実際にフレームを取得し始めるには

self.videoDataQueue = dispatch_queue_create("jp.co.xxx.videoDataQueue", DISPATCH_QUEUE_SERIAL);
[self.videoDataOutput setSampleBufferDelegate:self queue:self.videoDataQueue];

と、サブスレッド用のシリアルキューを用意しそこで処理をさせます。フレームデータは1秒に何度も取得するためメインスレッドを止めないような設計になっています。

あとはセッションに追加するだけです。

[self.session addOutput:self.videoDataOutput];

各フレームデータはAVCaptureVideoDataOutputSampleBufferDelegateの

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection

でsampleBufferという形で取得できます。sampleBufferからUIImageに変換することもできます。 ただし、このメソッド内で重い処理をしないようにしましょう。1秒間に30回前後呼ばれます。

AVCaptureAudioDataOutput

AVCaptureVideoDataOutputと同じようなものなので省きます。

すべてのoutputについて生成後、セッションに追加するのを忘れないようにしてください。また、セッションを走らせなければ実際にデータは取得できません。そこも忘れないようにしてください。

AVCaptureVideoPreviewLayer

このクラスはカメラからの映像をユーザに見せるプレビュー画面を簡単に作成できます。

AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
previewLayer.frame = self.view.bounds;

で生成でき、あとは任意のビューに追加するだけです。

以上の知識のみで自由度の高いカメラアプリを開発することができます。細かなTipsはたくさんありますが、今回はここで終わりにしたいと思います。

push通知からバックグラウンドでダウンロード

今回はpush通知を受信後バックグラウンドでアプリを起動させ、サーバから情報を取得する方法を紹介します。

そのまえに前回の記事を読むことを推奨します。

サイレント通知もしくは、通常のpush通知を受信した際にバックグラウンドで起動するという設定を行う必要があります。

プロジェクトを作成し、Capabilitiesタブへ

f:id:tomoyaonishi:20140628230323p:plain

次にBackgroundModesのスイッチをONにし、RemoteNotificationsにチェックを入れます。

f:id:tomoyaonishi:20140628230339p:plain

.plistファイルを編集することでも同じことが可能です、好みで。

f:id:tomoyaonishi:20140628230349p:plain

以上で設定は終了です。

次にAppDelegateを編集します。

push通知を受け取った時にサーバから情報をダウンロードする

最後に、push通知の情報に

content-available:1

を追加します。 これがないとバックグラウンドで起動されません。

{ "aps": 
          { "content-available": 1, 
            "alert": "content test", 
            "badge": 0, 
            "sound": "default" 
          }
}

これでpush通知が届いたらOSがアプリを起動してくれます。

Task Completionの状態と同じですね。30秒という時間制限が違うだけです。なので、NSURLConnectionとかでダウンロード処理ができます。 僕はバックグラウンドだからNSURLSessionのバックグラウンドモードを使わないといけないと思っていてハマりました。

SilentNotificationとBackgroundFetchについて

iOS7でアプリがバックグラウンドで動作できるAPIがいくつか追加されました

BackgroundFetch

これを有効にするとOSが定期的に良いタイミングでバックグラウンドでアプリを起動させ、AppDelegateの

application:performFetchWithCompletionHandler:

を実行してくれます。

このメソッドの中で30秒間自由に動くことができます。もちろんファイルのダウンロードやデータベースの更新も可能です。

良いタイミングというのは、ユーザのアプリ起動履歴をOSが監視し、アプリの起動時間帯などを考慮し適切なタイミングで呼ばれるということです。また、ユーザが明示的にAppSwitcherから削除した場合はこのメソッドは呼ばれなくなります。 したがって開発者が時間を決めて定期的にアプリを動かしたいというニーズは満たされません。おまけ程度に考えておきましょう。

SilentNotification

今までのRemoteNotification(push通知)では、受信した際にアプリが起動していなければ必ずロック画面などに通知されていました。 SilentNotificationではロック画面や通知センターに通知を表示することなく、アプリに対してだけ「ひっそりと」通知を行うことが可能になったということです。 ユーザにはpush通知が届いたことはわかりませんから、受信した際にひっそりと最新の情報をダウンロードしたりできるわけです。

注意したいのは、Silentでない通常のpush通知でも、受信した際に最新の情報をダウンロードしたりすることができることです。

push通知を受信後にバックグラウンドで動ける通知のことをSilentNotificationだと考えている方が多いようですが、そうではありません。単にユーザに気づかせないpush通知がSilentNotificationです。 push通知受信後にバックグラウンドで動くことができるのは

content-available:1

というフラグが立っているpush通知です。Silentか通常かは関係ありません。

content-available:1というフラグが立っており、アプリ側で設定をしていれば Silentも通常も受信後に

application:didReceiveRemoteNotification:fetchCompletionHandler:

が呼ばれ30秒間動くことができます。 content-availableフラグがなければこのメソッドが呼ばれることはありません。

application:didReceiveRemoteNotification:

が呼ばれます。こちらのメソッドの場合バックグラウンドで動くことはできません。

ちなみにこのSilentNotificationはAPNS側で制限がかかるようで、何度も送れないようです。

content-available:1のSilentNotificationと通常のpush通知の例として以下のようなことが考えられます。

例えば、天気予報アプリで最新の情報があることをpush通知で知らせます。しかし天気の最新情報ごときで毎回ユーザに通知していたらアンインストールされます。 そこでSilentNotificationの登場です。ユーザには通知されませんが、アプリに通知されますので、バックグラウンドでひっそりと最新情報を取得し、画面を更新しておくことができます。そうすれば、次にユーザが起動した際にはすでに最新情報が見ることができます。

通常のpush通知の例として、Twitterの@tweetです。こちらはユーザに届いたことをすぐに通知すべきです。さらにユーザがアプリを起動するまでにその内容をダウンロードしておきたいです。そういった場合にはSilentNotificationではなく、通常のpush通知を使いましょう。

どちらも最新情報のダウンロードを取得することが主な目的だと思いますが、アプリはバックグラウンド状態で完全に起動しているため、基本的にはなんでもできるはずです。状態としてはTask Completionと同じですね。