跳过正文
  1. Posts/

UIWebView获取详细浏览记录

··4 分钟

2018.8.1

需求
#

获取UIWebView的前进后退的浏览记录,举个例子,比如从A->B->C->B,此时B上一页是A,下一页是C,需要获取A、C的URL信息。

WKWebView暴露了这个属性,WKBackForwardList *backForwardList。可以很容易的取到,无奈项目还是使用的UIWebView,于是有了后面的探索。

首先在UIWebView提供的API里找,相关的API只有canGoBackcanGoForward,命名很直观,是否可以后退和前进,这条路走不通。 想到是否可以通过JS的Window.history获取到,查了下API,唯一一个有点像的length属性,智能取到浏览记录中的所有URL的数量,不区分前后,比如在上面提到的例子中,在B页面取到的length是3,而且不能取出具体的URL。

无奈只有上源码了,我下载的是“WebKit-7604.1.38.0.7”和“WebCore-7604.1.38.0.7”

Window.History.length实现本质
#

在WebCore里面找到了JS方法Window.History.length实现的本质,上代码:

// WebCore History.cpp
unsigned History::length() const
{
    if (!m_frame)
        return 0;
    auto* page = m_frame->page();
    if (!page)
        return 0;
    return page->backForward().count();
}
// WebCore BackForwardController.cpp
int BackForwardController::count() const
{
    // count = 当前页之前的页面总数 + 当前页之后的页面总数 + 1
    return m_client->backListCount() + 1 + m_client->forwardListCount();
}

WebCore里面backListCount()forwardListCount()定义是虚函数,具体实现在WebKit可以找到。

// WebKit BackForwardList.mm
int BackForwardList::backListCount()
{
    return m_current == NoCurrentItemIndex ? 0 : m_current;
}

int BackForwardList::forwardListCount()
{
    return m_current == NoCurrentItemIndex ? 0 : (int)m_entries.size() - (m_current + 1);
}

还有一个方法我们需要关注一下,后面会用到。

// WebKit BackForwardList.mm
// 获取之前最多几条历史记录
void BackForwardList::backListWithLimit(int limit, Vector<Ref<HistoryItem>>& list)
{
    list.clear();
    if (m_current != NoCurrentItemIndex) {
        unsigned first = std::max(static_cast<int>(m_current) - limit, 0);
        for (; first < m_current; ++first)
            list.append(m_entries[first].get());
    }
}

获取History
#

WebView的中,我们可以找到WebBackForwardList的定义。

/*!
    @property backForwardList
    @abstract The backforward list for this WebView.
*/    
@property (nonatomic, readonly, strong) WebBackForwardList *backForwardList;

WebBackForwardList是对BackForwardList的一层封装,阅读一下它的.h文件,不难找到获取浏览记录的方法。

/*!
    @method backListWithLimit:
    @abstract Returns a portion of the list before the current entry.
    @param limit A cap on the size of the array returned.
    @result An array of items before the current entry, or nil if there are none.  The entries are in the order that they were originally visited.
*/
- (NSArray *)backListWithLimit:(int)limit;

上面的方法可以获取到一个WebHistoryItem数组,WebHistoryItem保存了浏览记录的详细信息。

获取WebBackForwardList
#

UIWebView没有暴露获取WebView或者WebBackForwardList的方法,但是我们可以用KVO曲线救国,于是我们需要找到WebView的私有变量名。用runtime可以做到,为了简化这个过程,我写了一个工具类来辅助搜索,具体可以看这篇文章-runtime实现私有变量搜索。简单来说就是用runtime获取类成员变量列表,然后用BFS来搜索我们要找的类。

// 以UIWebView为根节点,BFS搜索WebView
[BFSSearchClass searchClass:@"WebView" inClass:@"UIWebView"];

// 搜索结果如下
// Class Name:类名,Ivar Name:变量名,Super Class:父类
Class Name:WebView,Ivar Name:_webView
Class Name:UIWebDocumentView,Ivar Name:Super Class
Class Name:UIWebBrowserView,Ivar Name:browserView
Class Name:UIWebViewInternal,Ivar Name:_internal
Root Class:UIWebView

有了上面的结论,我们就可以用KVC来获取到浏览记录了。

- (void)printWebViewHistory:(UIWebView *)aWebView {
    id webviewInternal = [aWebView valueForKey:@"_internal"];
    id browserView = [webviewInternal valueForKey:@"browserView"];
    id webView = [browserView valueForKey:@"_webView"];
    id backForwardList = [webView performSelector:@selector(backForwardList)];
    // WebHistoryItem存储的具体某条浏览记录信息
    NSArray *historyItems = [backForwardList performSelector:@selector(backListWithLimit:) withObject:@10];
    for (id item in historyItems) {
        // 获取浏览记录的url 
        NSString *url = [item performSelector:@selector(URLString)];
        NSLog(@"%@", url);
    }
}

相关文章

runtime实现私有变量搜索

··2 分钟
本文 Demo 地址 2018.7.31 需求 # 在开发功能时,为了满足产品变态的需求,难免有系统类提供的API不够用的时候,这时候私有变量就可以发挥它光和热了。怎么通过一个类,一层一层的找到特定类型的私有成员变量? 受益于Objective-C的动态语言特性,就算苹果UIKit不开源,但是在runtime面前,类的结构还是暴露无遗。我的思路是逐层手动打印成员变量信息,如果是UI控件可以用Reveal来加快进度,配合KVC机制,获取私有变量就如同探囊取物一般。 在多次遇到这个问题后,我决定实现一个工具类来简化这个过程,毕竟能自动化的就尽量不要手动。

iOS横竖屏总结

··3 分钟
2018.7.23 使用Auto Layout来进行布局就不用自己去监听横竖屏事件了,只需要绘制多套布局即可。但是项目有很多页面是自己手动计算的,于是只有想办法再旋转屏幕时重新布局。

记一个实现UIWindow子类的小坑

··1 分钟
2018.7.19 问题描述 # 项目中为了实现一个全局遮罩界面,使用了一个UIWindow的子类MyWindow,MyWindow为了实现回调定义了代理MyWindowDelegate。代码大致如下:

一个autoreleasepool的使用场景

··1 分钟
2018.7.18 今天在学习大佬博客的时候看到一个问题,下面代码会有什么问题? // largeNumber是一个很大的数 for (int i = 0; i < largeNumber; i++) { NSString *str = [NSString stringWithFormat:@"hello -%04d", i]; str = [str stringByAppendingString:@" - world"]; NSLog(@"%@", str); } 刚开始没看出什么问题,就是普通的循环,每次循环创建一个局部变量NSString。于是写了个Demo验证了下,在观察内存的时候发现了端倪,在循环过程中,内存不断飙升。

UITabbar自定义Badge

··1 分钟
2018.7.10 tabBarItem的Badge默认样式是带数字的,但是产品要求只要一个小红点,不需要数字,这就需要我们自定义Badge了。 用Reveal分析UITabBar,发现每个按钮是一个UITabBarButton,层级如下: