众所周知,在面试的时候Runtime、数据结构等等都是面试常问的题目,当然,不少朋友会吐槽面试问题常常脱离实际开发,毕竟那些只有一两人两三人组成的小开发组的项目,整个项目往往只有一两次使用到Runtime的机会,甚至有的项目根本就是从头到尾都没用到过。
当然嘛,就算没用到过,但这方面的知识储备还是需要的,不然面试根本就没啥问题好问,全都是UI层次、业务逻辑的问题,别说面试官,面试者都可能会觉得蛋疼的紧吧?
话题扯远了,言归正传,Runtime还有使用到的机会,那么链表这一概念在移动端开发到底会在什么时候被应用到呢?在厕所中沉思的时候我突然想到这个问题,移动端开发到底有什么功能开发会使用到链表的概念,非链表不能,又或者是使用链表会达到最好效果的呢?
身体放空的同时心灵也得到了升华,我终于想到了一个可能性,那就是Web的历史追溯。
在PC使用谷歌浏览器的时候在一次偶然的巧合下我发现了谷歌浏览器让人眼前一亮的功能,具体模拟使用场景是这样的:
1、松鼠症的我堆积了大量的标签页,其中一些标签页是有下一级的Web链接及上一级的Web链接
2、不小心错误关闭了其中一个标签页,那个标签页是我找了很久的资料,标签页的前进跟后退都是同一个网站的重要资料
3、通过谷歌浏览器的打开最近关闭的标签页我重新打开了标签页,并且,上一级的Web链接以及下一级的Web链接依然存在。
这是很棒的功能,让人眼前一亮,而遗憾的是手机上的浏览器大多没有这样的功能(至少我日常使用的都没见到),那么作为一个开发者,我们要如何将这样的功能挪移到手机上呢?
我想到了链表,Web一级一级的连接方式无疑是链表的最佳诠释,当页面被关闭时将完成的链表保存下来,当页面被从新打开时从链表读取数据似乎是很好的解决办法。
为了佐证我的想法,首先先写一个Web Demo.
let web = WKWebView(frame: CGRect.zero)
web.navigationDelegate = self
view.addSubview(web)
class ViewController: UIViewController,WKNavigationDelegate {
......
//实现代理方法来获取URL地址,亦可通过监听Loading状态,这些都随便,需要注意的是,度娘会做相关网址转译,这些转译需要根据自己选是否筛选。
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 {
//商业项目中我们不应该直接添加URLString,而是一个完整的数据模型,不过在此,我们简化一些操作。
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数据取出即可。
func goBack() {
let urlString = list?.next?.url ?? ""
list = list?.next
let request = URLRequest(url: URL(string: urlString)!)
web?.load(request)
}
但是这样在实际使用上是会出很大问题的,首先是每次历史回溯都将数据替代了,要是想回到原先的界面就只能使用WebView的goBack API,原本是最尾末的页面但因为关闭而重新打开却成了最初始的,这样很容易造成业务逻辑上的混乱。
其二,如果是同时具有上一级以及下一级URL(即不处于链表的首末尾)的Web,这样的逻辑并不能使得Web goForward,功能并没有完全实现。
除此之外还有等等问题在实际项目会有很多不同之处,需要一一调整。
在此,我认为可以这么设计,一个链表不够就再加一个,两个链表分别代表当前页面之前的数据以及这个页面之后的数据,当前页面可以选择性地放在两个链表的首末。
//当页面处于正常状态而非是被打开的最近关闭标签页时按正常流程走
//当页面处于正常状态而调用webView的goBack API 及 goForward API时改动双链表
//当调用goBack API 时,将goBack之前的页面从before List删除,添加到 after List,并成为after List的 First Node.
//当调用goForward API 时,逆转而使
//如此,确保双链表模拟的完整链表的正确性以及当前页面的url在完整链表中位置的正确性
//当页面是最近被关闭的标签页时检查页面的双链表,确定页面的url在链表中的位置
//根据链表是否有值判断当前页面是否存在上一级的页面或者下一级的页面
//同样的,根据调用的API对双链表做出修正。
......
//双链表
var before:ListNode? //当前网页之前的链表数据,当前网页处于链表头部
var after:ListNode? //当前网页之后的链表数据
//监听按钮点击以挪移链表
@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 {
}
}
如此在正常状态不断挪移链表,在最近关闭的标签页中获取数据时方能获取到正确数据。
目前正在写一个完整的Web项目以正确模拟商业项目如此使用可能会出现的问题,所以本文仅是探讨如此的功能实现,不能避免逻辑上可能存在的问题。