プロキシーパターンをSwift5で実装する
※この記事は「全デザインパターンをSwift5で実装する」https://qiita.com/satoru_pripara/items/3aa80dab8e80052796c6 の一部です。
The Proxy(プロクシ)
0. プロクシの意義
ある特定のオブジェクトに直接アクセスさせず、間接的にアクセスするようにするパターンをプロクシパターンと言う(Proxyは代理というような意味)。
具体的には、
・ バーチャルプロクシ
・ リモートプロクシ
・ プロテクティブプロクシ
の三種がある。
注意点は、プロクシを経由せずに直接目的のオブジェクトにアクセスできるような抜け道を用意してはならないという事である。それではプロクシパターンの意味がなくなってしまう。
1. Virtual Proxy(バーチャルプロクシ)
オブジェクト生成にコストがかかる場合、その生成のタイミングを本当にオブジェクトが必要になるまで遅らせるパターンの事を言う。
Swiftでは、変数の前にlazy
修飾詞をつける事で比較的簡単に実現できる。
public protocol RemoteImage: CustomStringConvertible {
init(url: URL)
var image: UIImage? {get}
var url: URL {get}
var hasContent: Bool {get}
}
extension RemoteImage {
public var description: String {
let description = self.hasContent ? "Image available. Retrieved from \(self.url.absoluteString)" : "No image available yet!"
return description
}
}
public class ImageProxy: RemoteImage {
public required init(url: URL) {
self.url = url
}
//lazy修飾詞をつける
public lazy var image: UIImage? = { [unowned self] in
var result: UIImage?
if let img = try? UIImage(data:
Data(contentsOf: self.url)
) {
result = img
self.hasContent = true
}
return result
}()
public let url: URL
public var hasContent: Bool = false
}
プロクシを実際に利用してみると以下のようになる。
guard let imageURL = URL(string: "https://developer.apple.com/swift/images/swift-og.png") else {
fatalError("Could not create URL")
}
let imageProxy = ImageProxy(url: imageURL)
print(imageProxy)// No image available yet!
let image = imageProxy.image
print(imageProxy)//Image available. Retrieved from https://developer.apple.com/swift/images/swift-og.png
プロクシを生成した段階ではimage
プロパティは実際に生成されておらず、実際にimage
プロパティにアクセスした時初めて生成されている事がわかる。
この例のように、ネットワークを介してデータをダウンロードして画像を生成すると言う重い処理がある場面では、最初にオブジェクトを生成するのでなく実際にアクセスした段階で生成する事でリソースの節約につながる。
2. Remote Proxy(リモートプロクシ)
リモートプロクシは、ネットワーク接続などコストのかかる処理を実際に必要になるタイミングまで延期するプロクシパターンを言う。
具体的には、ネットワーク接続に必要な情報(URL,クロージャなど)を渡す処理と、実際にネットワーク接続を行う処理を分離し、後者を前者とは別のタイミングで行えるようにする。
import Foundation
public protocol RemoteData {
func data(url: URL, completionHandler: @escaping(Error?, Data?) -> Void) -> RemoteData
func run()
}
public class RemoteDataProxy: RemoteData {
fileprivate var callback: ((Error?, Data?) -> Void)?
fileprivate var url: URL?
public init() {}
//URL,コンプリーションハンドラを渡す処理
public func data(url: URL, completionHandler: @escaping(Error?, Data?) -> Void) -> RemoteData {
self.url = url
self.callback = completionHandler
return self
}
//実際にネットワーク接続を行う処理
public func run() {
if let callback = self.callback,
let url = self.url {
URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data, error == nil else {
print("Could not download data from URL \(url.absoluteString). Reason: \(error!.localizedDescription)")
callback(error, nil)
return
}
print("Data successfully fetched from URL \(url.absoluteString)")
callback(nil, data)
}.resume()
print("Downloading data from URL \(url.absoluteString)")
} else {
print("run() called before invoking data(url: completionHandler:)")
}
}
}
そして下記のようにdata(func:completionHandler:)
メソッドをrun()
メソッドを別のタイミングで呼ぶことで、コストのかかるネットワーク接続処理を自由なタイミングまで延期できる。もし実際のネットワーク接続処理が必要なくなったら行わなくて済むことになるため、やはりリソースの削減につながる。
import Foundation
import PlaygroundSupport
guard let dataURL = URL(string: "https://developer.apple.com/swift/images/swift-og.png") else {
fatalError("Could not create URL")
}
//URL、クロージャなど接続処理に必要な情報を渡す。この時点では接続は行われない
let dataProxy = RemoteDataProxy().data(url: dataURL) {(error, data) in
guard error == nil else {
print("Could not retrieve data from URL \(dataURL.absoluteString)")
return
}
print("\(data?.count ?? 0) bytes retrieved from URL \(dataURL.absoluteString)")
}
//Playgroundで非同期処理を許可する
PlaygroundPage.current.needsIndefiniteExecution = true
//延期されたネットワーク接続処理
dataProxy.run()
3. Protective Proxy(プロテクティブプロクシ)
個人情報などセンシティブな情報にアクセスさせる際、権限が無い者に見られては困るため、必ず認証を経てから行いたい場合がある。
このように目的のオブジェクトへのアクセスを制限し、認証を経てからでないとできないようにするパターンをプロテクティブプロクシという。
先に実装したImageProxy
クラスを利用する形で、さらに認証機能を追加したSecureImageProxy
クラスを実装する。
認証機能は、新しく作成したAuthenticator
クラスを利用する。
public protocol Authenticating {
var isAuthenticated: Bool {get}
func authenticate(user: String) -> Bool
}
public class Authenticator: Authenticating {
static public let shared = Authenticator()
//認証が行われたか否かを表すBool型変数
public var isAuthenticated: Bool = false
//接続が許可されているユーザー名の一覧
fileprivate let userWhiteList = ["John", "Mary", "Steve"]
fileprivate let syncQueue = DispatchQueue(label: "com.leakka.authQueue")
fileprivate init() {}
//許可されたユーザーか否かを確認するメソッド
public func authenticate(user: String) -> Bool {
var result = false
self.syncQueue.sync {
result = self.userWhiteList.contains(user) ? true : false
if result {
print("Authorized!")
self.isAuthenticated = true
} else {
print("Error: Unauthorized!")
self.isAuthenticated = false
}
}
return result
}
}
さらにプロキシクラスは以下のようになる。
//private修飾詞に変更
private class ImageProxy: RemoteImage {
//中略
}
ImageProxy
クラスの公開範囲をprivateに変更している。
これは、後述のSecureImageProxy
でなく直接ImageProxy
を使用し、認証を回避するというような事態を避けるためである。
//認証用のプロクシクラスを追加
public class SecureImageProxy: RemoteImage {
//認証が完了していれば画像を返す
public var image: UIImage? {
get {
return Authenticator.shared.isAuthenticated ? self.imageProxy.image : nil
}
}
public let url: URL
public var hasContent: Bool = false
//ImageProxyクラスをprivateで保持
fileprivate lazy var imageProxy: ImageProxy = ImageProxy(url: self.url)
public required init(url: URL) {
self.url = url
}
}
実際に使用してみると、以下のようになる。
import Foundation
import PlaygroundSupport
guard let imageURL = URL(string: "https://developer.apple.com/swift/images/swift-og.png") else {
fatalError("Could not create URL")
}
let secureImageProxy = SecureImageProxy(url: imageURL)
print(secureImageProxy)// No image available yet!
Authenticator.shared.authenticate(user: "Jim")//Error: Unauthorized!
if secureImageProxy.image != nil {
print("Proxy has a valid image.")
}
Authenticator.shared.authenticate(user: "John")//Authorized!
if secureImageProxy.image != nil {
print("Proxy has a valid image.")//Proxy has a valid image.
}
PlaygroundPage.current.needsIndefiniteExecution = true
誤ったユーザー名では認証に失敗し画像にアクセスできない。
正しいユーザー名で認証に成功した後のみ、画像にアクセスできていることがわかる。
参考文献: https://www.amazon.com/Design-Patterns-Swift-implement-Improve-ebook/dp/B07MDD3FQJ
Author And Source
この問題について(プロキシーパターンをSwift5で実装する), 我々は、より多くの情報をここで見つけました https://qiita.com/satoru_pripara/items/6b2a1f756813fbc287f1著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .