眾所周知,在面試的時候Runtime、數(shù)據(jù)結(jié)構(gòu)等等都是面試常問的題目,當然,不少朋友會吐槽面試問題常常脫離實際開發(fā),畢竟那些只有一兩人兩三人組成的小開發(fā)組的項目,整個項目往往只有一兩次使用到Runtime的機會,甚至有的項目根本就是從頭到尾都沒用到過。
當然嘛,就算沒用到過,但這方面的知識儲備還是需要的,不然面試根本就沒啥問題好問,全都是UI層次、業(yè)務邏輯的問題,別說面試官,面試者都可能會覺得蛋疼的緊吧?
話題扯遠了,言歸正傳,Runtime還有使用到的機會,那么鏈表這一概念在移動端開發(fā)到底會在什么時候被應用到呢?在廁所中沉思的時候我突然想到這個問題,移動端開發(fā)到底有什么功能開發(fā)會使用到鏈表的概念,非鏈表不能,又或者是使用鏈表會達到最好效果的呢?
身體放空的同時心靈也得到了升華,我終于想到了一個可能性,那就是Web的歷史追溯。
在PC使用谷歌瀏覽器的時候在一次偶然的巧合下我發(fā)現(xiàn)了谷歌瀏覽器讓人眼前一亮的功能,具體模擬使用場景是這樣的:
1、松鼠癥的我堆積了大量的標簽頁,其中一些標簽頁是有下一級的Web鏈接及上一級的Web鏈接
2、不小心錯誤關(guān)閉了其中一個標簽頁,那個標簽頁是我找了很久的資料,標簽頁的前進跟后退都是同一個網(wǎng)站的重要資料
3、通過谷歌瀏覽器的打開最近關(guān)閉的標簽頁我重新打開了標簽頁,并且,上一級的Web鏈接以及下一級的Web鏈接依然存在。
這是很棒的功能,讓人眼前一亮,而遺憾的是手機上的瀏覽器大多沒有這樣的功能(至少我日常使用的都沒見到),那么作為一個開發(fā)者,我們要如何將這樣的功能挪移到手機上呢?
我想到了鏈表,Web一級一級的連接方式無疑是鏈表的最佳詮釋,當頁面被關(guān)閉時將完成的鏈表保存下來,當頁面被從新打開時從鏈表讀取數(shù)據(jù)似乎是很好的解決辦法。
為了佐證我的想法,首先先寫一個Web Demo.
let web = WKWebView(frame: CGRect.zero)
web.navigationDelegate = self
view.addSubview(web)
class ViewController: UIViewController,WKNavigationDelegate {
......
//實現(xiàn)代理方法來獲取URL地址,亦可通過監(jiān)聽Loading狀態(tài),這些都隨便,需要注意的是,度娘會做相關(guān)網(wǎng)址轉(zhuǎn)譯,這些轉(zhuǎn)譯需要根據(jù)自己選是否篩選。
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
print(navigationAction.request.url?.absoluteString)
decisionHandler(WKNavigationActionPolicy.allow)
}
}
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
print(navigationResponse.response.url)
decisionHandler(WKNavigationResponsePolicy.allow)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print(webView.url)
}
public class ListNode {
//商業(yè)項目中我們不應該直接添加URLString,而是一個完整的數(shù)據(jù)模型,不過在此,我們簡化一些操作。
public var url: String
public var next: ListNode?
public init(_ url: String) {
self.url = url
self.next = nil
}
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let node = ListNode(webView.url!.absoluteString )
if let l = list {
node.next = l
list = node
} else {
list = node
}
}
如此,我們便獲得了一個完整的鏈表,里面記錄了標簽頁從新建之始記錄的所有URL地址,并且,這是從尾到頭的,若想要進行歷史回溯便一層一層地將list.next中的url數(shù)據(jù)取出即可。
func goBack() {
let urlString = list?.next?.url ?? ""
list = list?.next
let request = URLRequest(url: URL(string: urlString)!)
web?.load(request)
}
但是這樣在實際使用上是會出很大問題的,首先是每次歷史回溯都將數(shù)據(jù)替代了,要是想回到原先的界面就只能使用WebView的goBack API,原本是最尾末的頁面但因為關(guān)閉而重新打開卻成了最初始的,這樣很容易造成業(yè)務邏輯上的混亂。
其二,如果是同時具有上一級以及下一級URL(即不處于鏈表的首末尾)的Web,這樣的邏輯并不能使得Web goForward,功能并沒有完全實現(xiàn)。
除此之外還有等等問題在實際項目會有很多不同之處,需要一一調(diào)整。
在此,我認為可以這么設(shè)計,一個鏈表不夠就再加一個,兩個鏈表分別代表當前頁面之前的數(shù)據(jù)以及這個頁面之后的數(shù)據(jù),當前頁面可以選擇性地放在兩個鏈表的首末。
//當頁面處于正常狀態(tài)而非是被打開的最近關(guān)閉標簽頁時按正常流程走
//當頁面處于正常狀態(tài)而調(diào)用webView的goBack API 及 goForward API時改動雙鏈表
//當調(diào)用goBack API 時,將goBack之前的頁面從before List刪除,添加到 after List,并成為after List的 First Node.
//當調(diào)用goForward API 時,逆轉(zhuǎn)而使
//如此,確保雙鏈表模擬的完整鏈表的正確性以及當前頁面的url在完整鏈表中位置的正確性
//當頁面是最近被關(guān)閉的標簽頁時檢查頁面的雙鏈表,確定頁面的url在鏈表中的位置
//根據(jù)鏈表是否有值判斷當前頁面是否存在上一級的頁面或者下一級的頁面
//同樣的,根據(jù)調(diào)用的API對雙鏈表做出修正。
......
//雙鏈表
var before:ListNode? //當前網(wǎng)頁之前的鏈表數(shù)據(jù),當前網(wǎng)頁處于鏈表頭部
var after:ListNode? //當前網(wǎng)頁之后的鏈表數(shù)據(jù)
//監(jiān)聽按鈕點擊以挪移鏈表
@IBAction func goBack(_ sender: AnyObject) {
if web!.canGoBack {
if let b = before {
let node = b
before = b.next
if let a = after {
node.next = a
}
after = node
}
} else {
}
}
@IBAction func goForward(_ sender: AnyObject) {
if web!.canGoForward {
if let a = after {
let node = a
after = a.next
if let b = before {
node.next = b
}
before = node
}
} else {
}
}
如此在正常狀態(tài)不斷挪移鏈表,在最近關(guān)閉的標簽頁中獲取數(shù)據(jù)時方能獲取到正確數(shù)據(jù)。
目前正在寫一個完整的Web項目以正確模擬商業(yè)項目如此使用可能會出現(xiàn)的問題,所以本文僅是探討如此的功能實現(xiàn),不能避免邏輯上可能存在的問題。