本文大部分內(nèi)容翻譯至《Pro Design Pattern In Swift》By Adam Freeman读恃,一些地方做了些許修改,并將代碼升級(jí)到了Swift2.0,翻譯不當(dāng)之處望多包涵灼芭。
代理模式(The Proxy Pattern)
為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問厂庇。在某些情況下区端,一個(gè)對(duì)象不適合或者不能直接引用另一個(gè)對(duì)象沈善,而代理對(duì)象可以在客戶端和目標(biāo)對(duì)象之間起到中介的作用。
示例工程
OS X Command Line Tool 工程:
main.swift
import Foundation
func getHeader(header:String) {
let url = NSURL(string: "http://www.apress.com")
let request = NSURLRequest(URL: url!)
NSURLSession.sharedSession().dataTaskWithRequest(request,
completionHandler: {data, response, error in
if let httpResponse = response as? NSHTTPURLResponse {
if let headerValue = httpResponse.allHeaderFields[header] as? NSString {
print("\(header): \(headerValue)")
}
}
}).resume()
}
let headers = ["Content-Length", "Content-Encoding"]
for header in headers {
getHeader(header)
}
NSFileHandle.fileHandleWithStandardInput().availableData
main.swift中對(duì)Apress的主頁進(jìn)行HTTP請(qǐng)求并打印Content-Length 和 Content-Encoding 的值炼绘,運(yùn)行程序可以看到下面輸出:
Content-Encoding: gzip
Content-Length: 27003
你也可能看見Content-Length先輸出嗅战。這是因?yàn)镕oundation框架的HTTP請(qǐng)求是異步( asynchronous)和并發(fā)的(concurrent),這意味著任何一個(gè)請(qǐng)求都可能先完成然后第一個(gè)輸出結(jié)果。
理解代理模式解決的問題
代理模式主要用來解決三個(gè)不同的問題俺亮。
理解遠(yuǎn)程對(duì)象問題
遠(yuǎn)程對(duì)象問題就是當(dāng)你需要處理網(wǎng)絡(luò)資源時(shí)驮捍,例如一個(gè)網(wǎng)頁或者網(wǎng)絡(luò)服務(wù)。main.swift中的代碼去獲取一個(gè)網(wǎng)頁脚曾,但是它并沒有將提供的機(jī)制分離出來东且。在代碼中沒有抽象和封裝,這導(dǎo)致了如果我們修改實(shí)現(xiàn)的話會(huì)影響到其它的代碼本讥。
理解昂貴花銷問題
像HTTP請(qǐng)求那樣的需要昂貴開銷的任務(wù)珊泳。例如需要大量計(jì)算,大量?jī)?nèi)存消耗拷沸,需要消耗設(shè)備電量(GPS),帶寬要求色查,運(yùn)行時(shí)間等。
對(duì)于HTTP請(qǐng)求來說撞芍,主要的代價(jià)是運(yùn)行時(shí)間和帶寬秧了,和服務(wù)器生成并返回response。main.swift中并沒有對(duì)HTTP請(qǐng)求做任何優(yōu)化序无,也沒有考慮過用戶验毡,網(wǎng)絡(luò),或者服務(wù)器接受請(qǐng)求等帶來的沖突愉镰。
理解訪問權(quán)限問題
限制訪問一個(gè)對(duì)象通常存在于多用戶的應(yīng)用中米罚。你不能改變想保護(hù)對(duì)象的定義因?yàn)槟銢]有代碼權(quán)限或者對(duì)象有其它的依賴钧汹。
理解代理模式
代理模式可以用在當(dāng)一個(gè)對(duì)象需要用來代表其他的資源丈探。如下面的圖,資源可以是一些抽象拔莱,例如一個(gè)網(wǎng)頁碗降,或者其他對(duì)象。
解決遠(yuǎn)程對(duì)象問題
用代理模式來代表遠(yuǎn)程對(duì)象或是資源是從分布式系統(tǒng)開始的塘秦,例如CORBA讼渊。CORBA提供了一個(gè)暴露了和服務(wù)器對(duì)象相同方法的本地對(duì)象。本地對(duì)象是代理尊剔,并且調(diào)用了一個(gè)和遠(yuǎn)程對(duì)象相關(guān)聯(lián)的方法爪幻。CORBA十分注意代理對(duì)象和遠(yuǎn)程對(duì)象的映射并處理參數(shù)和結(jié)果。
CORBA并沒有流行起來,但是隨著HTTP成為了傳輸選擇和RESTful服務(wù)的流行挨稿,代理模式變得很重要仇轻。代理模式可以讓操作遠(yuǎn)程資源變得十分容易。
代理對(duì)象隱藏了如何訪問遠(yuǎn)程資源的細(xì)節(jié)奶甘,僅僅提供了它的數(shù)據(jù)篷店。
解決昂貴開銷問題
代理可以通過將運(yùn)算和使用去耦來減小開銷。在示例工程中臭家,我們可以將不同的HTTP頭值請(qǐng)求放在一次請(qǐng)求中疲陕。
解決訪問權(quán)限問題
代理可以被當(dāng)作一個(gè)對(duì)象的外包裝,增加一個(gè)額外的邏輯來強(qiáng)制執(zhí)行一些約束钉赁。
實(shí)現(xiàn)代理模式
實(shí)現(xiàn)代理模式根據(jù)解決的問題不同而不同蹄殃。
實(shí)現(xiàn)解決遠(yuǎn)程對(duì)象問題
實(shí)現(xiàn)用代理模式去訪問一個(gè)遠(yuǎn)程對(duì)象的關(guān)鍵是要分離出請(qǐng)求對(duì)象需要遠(yuǎn)程對(duì)象提供的特征。
1.定義協(xié)議
Proxy.swift
protocol HttpHeaderRequest {
func getHeader(url:String, header:String) -> String?
}
2.定義代理實(shí)現(xiàn)類
Proxy.swift
import Foundation
protocol HttpHeaderRequest {
func getHeader(url:String, header:String) -> String?
}
class HttpHeaderRequestProxy : HttpHeaderRequest {
private let semaphore = dispatch_semaphore_create(0)
func getHeader(url: String, header: String) -> String? {
var headerValue:String?
let nsUrl = NSURL(string: url)
let request = NSURLRequest(URL: nsUrl!)
NSURLSession.sharedSession().dataTaskWithRequest(request,
completionHandler: {data, response, error in
if let httpResponse = response as? NSHTTPURLResponse {
headerValue = httpResponse.allHeaderFields[header] as? String
}
dispatch_semaphore_signal(self.semaphore)
}).resume()
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER)
return headerValue
}
}
HttpHeaderRequestProxy類實(shí)現(xiàn)了HttpHeaderRequest 協(xié)議橄霉。getHeader方法用了Foundation框架去發(fā)動(dòng)HTTP請(qǐng)求窃爷,同時(shí)增加了GCD信號(hào)量用來覆蓋異步請(qǐng)求的問題。
Tip:在真正的項(xiàng)目中我并不建議在同步的方法中去覆蓋異步操作姓蜂,并不僅僅是因?yàn)镾wift閉包使得操作異步任務(wù)很容易按厘。
3.使用遠(yuǎn)程對(duì)象代理
main.swift
import Foundation
let url = "http://www.apress.com"
let headers = ["Content-Length", "Content-Encoding"]
let proxy = HttpHeaderRequestProxy()
for header in headers {
if let val = proxy.getHeader(url, header:header) {
print("\(header): \(val)")
}
}
NSFileHandle.fileHandleWithStandardInput().availableData
實(shí)現(xiàn)解決昂貴花銷問題
有很多種方法可以優(yōu)化昂貴花銷的操作,比較常用的如緩存钱慢,延遲加載逮京,或者如享元模式的其他設(shè)計(jì)模式。
示例工程中昂貴的操作就是HTTP請(qǐng)求束莫,減小HTTP請(qǐng)求次數(shù)會(huì)得到一個(gè)本質(zhì)的改善效果懒棉。顯然示例中的最佳優(yōu)化方法是對(duì)于想獲得多個(gè)不同的HTTP頭文件時(shí)只發(fā)起一次HTTP請(qǐng)求。
Proxy.swift
import Foundation
protocol HttpHeaderRequest {
func getHeader(url:String, header:String) -> String?
}
class HttpHeaderRequestProxy : HttpHeaderRequest {
private let queue = dispatch_queue_create("httpQ", DISPATCH_QUEUE_SERIAL)
private let semaphore = dispatch_semaphore_create(0)
private var cachedHeaders = [String:String]()
func getHeader(url: String, header: String) -> String? {
var headerValue:String?
dispatch_sync(self.queue){[weak self] in
if let cachedValue = self!.cachedHeaders[header] {
headerValue = "\(cachedValue) (cached)"
}else {
let nsUrl = NSURL(string: url)
let request = NSURLRequest(URL: nsUrl!)
NSURLSession.sharedSession().dataTaskWithRequest(request,
completionHandler: {data, response, error in
if let httpResponse = response as? NSHTTPURLResponse {
let headers = httpResponse.allHeaderFields as! [String: String]
for (name, value) in headers {
self!.cachedHeaders[name] = value
}
headerValue = httpResponse.allHeaderFields[header] as? String
}
dispatch_semaphore_signal(self!.semaphore)
}).resume()
dispatch_semaphore_wait(self!.semaphore, DISPATCH_TIME_FOREVER)
}
}
return headerValue
}
}
getHeader方法創(chuàng)建了一個(gè)緩存字典用來滿足隨后的請(qǐng)求览绿,運(yùn)行程序我們可以得到下面的結(jié)果:
Content-Length: 26992
Content-Encoding: gzip (cached)
注意:HttpHeaderRequestProxy 類增加了一個(gè)GCD同步隊(duì)列來確保一次請(qǐng)求就更新字典的緩存數(shù)據(jù)策严。
延遲操作
另一個(gè)常用的方法就是盡可能長(zhǎng)的延遲執(zhí)行昂貴花銷的操作。這樣做的好處是昂貴的花銷可能被取消饿敲,壞處是耦合性強(qiáng)妻导。
Proxy.swift
import Foundation
protocol HttpHeaderRequest {
init(url:String)
func getHeader(header:String, callback:(String, String?) -> Void )
func execute()
}
class HttpHeaderRequestProxy : HttpHeaderRequest {
let url:String
var headersRequired:[String: (String, String?) -> Void]
required init(url: String) {
self.url = url
self.headersRequired = Dictionary<String, (String, String?) -> Void>()
}
func getHeader(header: String, callback: (String, String?) -> Void) {
self.headersRequired[header] = callback;
}
func execute() {
let nsUrl = NSURL(string: url)
let request = NSURLRequest(URL: nsUrl!)
NSURLSession.sharedSession().dataTaskWithRequest(request,
completionHandler: {data, response, error in
if let httpResponse = response as? NSHTTPURLResponse {
let headers = httpResponse.allHeaderFields as! [String: String]
for (header, callback) in self.headersRequired {
callback(header, headers[header])
}
}
}).resume()
}
}
如果用戶在調(diào)用execute方法之前因某種原因放棄了,那么HTTP請(qǐng)求就不會(huì)執(zhí)行怀各。
main.swift
import Foundation
let url = "http://www.apress.com"
let headers = ["Content-Length", "Content-Encoding"]
let proxy = HttpHeaderRequestProxy(url: url)
for header in headers {
proxy.getHeader(header, callback: {header, val in
if (val != nil) {
print("\(header): \(val!)")
}
});
}
proxy.execute()
NSFileHandle.fileHandleWithStandardInput().availableData
執(zhí)行程序倔韭,得到下面結(jié)果:
Content-Encoding: gzip
Content-Length: 26992
實(shí)現(xiàn)解決訪問權(quán)限問題
創(chuàng)建授權(quán)資源
Auth.swift
class UserAuthentication {
var user:String?
var authenticated:Bool = false
static let sharedInstance = UserAuthentication()
private init() {
// do nothing - stops instances being created
}
func authenticate(user:String, pass:String) {
if (pass == "secret") {
self.user = user
self.authenticated = true
} else {
self.user = nil
self.authenticated = false
}
}
}
UserAuthentication用單例模式來提供用戶認(rèn)證,事實(shí)上用戶的密碼是secret的時(shí)候認(rèn)證成功瓢对。在真實(shí)的項(xiàng)目中寿酌,認(rèn)證是很復(fù)雜的,可能會(huì)有自己的代理硕蛹。
創(chuàng)建代理對(duì)象
接下來就是創(chuàng)建代理對(duì)象:
Proxy.swift
import Foundation
protocol HttpHeaderRequest {
init(url:String)
func getHeader(header:String, callback:(String, String?) -> Void )
func execute()
}
class AccessControlProxy : HttpHeaderRequest {
private let wrappedObject: HttpHeaderRequest
required init(url:String) {
wrappedObject = HttpHeaderRequestProxy(url: url)
}
func getHeader(header: String, callback: (String, String?) -> Void) {
wrappedObject.getHeader(header, callback: callback)
}
func execute() {
if (UserAuthentication.sharedInstance.authenticated) {
wrappedObject.execute()
} else {
fatalError("Unauthorized")
}
}
}
private class HttpHeaderRequestProxy : HttpHeaderRequest {
let url:String
var headersRequired:[String: (String, String?) -> Void]
required init(url: String) {
self.url = url
self.headersRequired = Dictionary<String, (String, String?) -> Void>()
}
func getHeader(header: String, callback: (String, String?) -> Void) {
self.headersRequired[header] = callback;
}
func execute() {
let nsUrl = NSURL(string: url)
let request = NSURLRequest(URL: nsUrl!)
NSURLSession.sharedSession().dataTaskWithRequest(request,
completionHandler: {data, response, error in
if let httpResponse = response as? NSHTTPURLResponse {
let headers = httpResponse.allHeaderFields as! [String: String]
for (header, callback) in self.headersRequired {
callback(header, headers[header])
}
}
}).resume()
}
}
注意到 HttpHeaderRequestProxy 類前面的private醇疼,因?yàn)閷?duì)于代理類來說子類也沒有任何意義硕并。
應(yīng)用代理
main.swift
import Foundation
let url = "http://www.apress.com"
let headers = ["Content-Length", "Content-Encoding"]
let proxy = AccessControlProxy(url: url)
for header in headers {
proxy.getHeader(header){header, val in
if (val != nil) {
print("\(header): \(val!)")
}
}
}
UserAuthentication.sharedInstance.authenticate("bob", pass: "secret")
proxy.execute()
NSFileHandle.fileHandleWithStandardInput().availableData
運(yùn)行程序:
Content-Encoding: gzip
Content-Length: 26992
代理模式的變形
代理類可以用來引用計(jì)數(shù)( reference counting),當(dāng)資源要求主動(dòng)式管理或者當(dāng)你需要引用數(shù)到一定的時(shí)候做一些分類的時(shí)候變得很有用秧荆。為了說明這個(gè)問題鲤孵,我們創(chuàng)建了另一個(gè)示例項(xiàng)目ReferenceCounting:
NetworkRequest.swift
import Foundation
protocol NetworkConnection {
func connect()
func disconnect()
func sendCommand(command:String)
}
class NetworkConnectionFactory {
class func createNetworkConnection() -> NetworkConnection {
return NetworkConnectionImplementation()
}
}
private class NetworkConnectionImplementation : NetworkConnection {
typealias me = NetworkConnectionImplementation
static let sharedQueue:dispatch_queue_t = dispatch_queue_create("writeQ",DISPATCH_QUEUE_SERIAL)
func connect() { me.writeMessage("Connect") }
func disconnect() { me.writeMessage("Disconnect") }
func sendCommand(command:String) {
me.writeMessage("Command: \(command)")
NSThread.sleepForTimeInterval(1)
me.writeMessage("Command completed: \(command)")
}
private class func writeMessage(msg:String) {
dispatch_async(self.sharedQueue){ _ in
print(msg)
}
}
}
NetworkConnection協(xié)議定義的方法用來操作一個(gè)假象的服務(wù)器。connect用來建立和服務(wù)器的連接辰如,sendCommand方法用來操作服務(wù)器普监,當(dāng)任務(wù)完成調(diào)用disconnect 方法斷開連接。
NetworkConnectionImplementation類實(shí)現(xiàn)了這個(gè)協(xié)議并且輸出調(diào)試日志琉兜。用單例模式創(chuàng)建了一個(gè)GCD串行隊(duì)列凯正,這樣兩個(gè)對(duì)象同時(shí)去輸出日志就不會(huì)出現(xiàn)問題。
同時(shí)用工廠模式來提供對(duì)NetworkConnectionImplementation類的實(shí)例的入口豌蟋,因?yàn)槲覀兛梢钥匆奛etworkConnectionImplementation前面的private關(guān)鍵字使得其他文件無法訪問廊散。
main.swift
import Foundation
let queue = dispatch_queue_create("requestQ", DISPATCH_QUEUE_CONCURRENT)
for count in 0 ..< 3 {
let connection = NetworkConnectionFactory.createNetworkConnection()
dispatch_async(queue){ _ in
connection.connect()
connection.sendCommand("Command: \(count)")
connection.disconnect()
}
}
NSFileHandle.fileHandleWithStandardInput().availableData
運(yùn)行程序:
Connect
Connect
Connect
Command: Command: 0
Command: Command: 1
Command: Command: 2
Command completed: Command: 1
Disconnect
Command completed: Command: 2
Disconnect
Command completed: Command: 0
Disconnect
實(shí)現(xiàn)引用計(jì)數(shù)代理
在這種情況中,引用計(jì)數(shù)代理可以用來網(wǎng)絡(luò)連接的生命周期梧疲,所以可以服務(wù)不同的請(qǐng)求允睹。
NetworkRequest.swift
import Foundation
protocol NetworkConnection {
func connect()
func disconnect()
func sendCommand(command:String)
}
class NetworkConnectionFactory {
static let sharedWrapper = NetworkRequestProxy()
private init(){
// do nothing
}
}
private class NetworkConnectionImplementation : NetworkConnection {
typealias me = NetworkConnectionImplementation
static let sharedQueue:dispatch_queue_t = dispatch_queue_create("writeQ",DISPATCH_QUEUE_SERIAL)
func connect() { me.writeMessage("Connect") }
func disconnect() { me.writeMessage("Disconnect") }
func sendCommand(command:String) {
me.writeMessage("Command: \(command)")
NSThread.sleepForTimeInterval(1)
me.writeMessage("Command completed: \(command)")
}
private class func writeMessage(msg:String) {
dispatch_async(self.sharedQueue){ _ in
print(msg)
}
}
}
class NetworkRequestProxy : NetworkConnection {
private let wrappedRequest:NetworkConnection
private let queue = dispatch_queue_create("commandQ", DISPATCH_QUEUE_SERIAL)
private var referenceCount:Int = 0
private var connected = false
init() {
wrappedRequest = NetworkConnectionImplementation()
}
func connect() { /* do nothing */ }
func disconnect() { /* do nothing */ }
func sendCommand(command: String) {
self.referenceCount++
dispatch_sync(self.queue){[weak self]in
if (!self!.connected && self!.referenceCount > 0) {
self!.wrappedRequest.connect()
self!.connected = true
}
self!.wrappedRequest.sendCommand(command)
self!.referenceCount--
if (self!.connected && self!.referenceCount == 0) {
self!.wrappedRequest.disconnect()
self!.connected = false
}
}
}
}
我修改了工廠模式類,現(xiàn)在用單例模式返回一個(gè)NetworkRequestProxy 類的實(shí)例幌氮。 NetworkRequestProxy實(shí)現(xiàn)了NetworkRequest協(xié)議同時(shí)它是NetworkConnectionImplementation對(duì)象的一個(gè)外層類缭受。
這里的引用計(jì)數(shù)代理的目的是為了將 connect和disconnect方法剝離請(qǐng)求并且保持連接直到等待的命令操作完成為止。我們用串行隊(duì)列來保證每個(gè)時(shí)間點(diǎn)只有一個(gè)命令在運(yùn)行该互,并且保證了引用計(jì)數(shù)不會(huì)受并發(fā)訪問的影響米者。
main.swift
import Foundation
let queue = dispatch_queue_create("requestQ", DISPATCH_QUEUE_CONCURRENT)
for count in 0 ..< 3 {
let connection = NetworkConnectionFactory.sharedWrapper
dispatch_async(queue){ _ in
connection.connect()
connection.sendCommand("Command: \(count)")
connection.disconnect()
}
}
NSFileHandle.fileHandleWithStandardInput().availableData
運(yùn)行程序:
Connect
Command: Command: 0
Command completed: Command: 0
Command: Command: 1
Command completed: Command: 1
Command: Command: 2
Command completed: Command: 2
Disconnect
從輸出結(jié)果我們可以看出,command現(xiàn)在在一次連接中串行的執(zhí)行宇智。代理減少了并發(fā)訪問的次數(shù)蔓搞,但是同時(shí)串行化了每一個(gè)操作,這意味著會(huì)運(yùn)行時(shí)間變長(zhǎng)了随橘。