UIWebView棄用問題

問題描述

開發(fā)者被告知UIWebView在2020年12底將被棄用,應用商店此后不再接受含有WebView的應用号醉。

 ///("No longer supported; please adopt WKWebView., ios(2.0, 12.0))
@interface UIWebView : UIView <NSCoding, UIScrollViewDelegate>

@end
解決方案
  • 利用IDE提供的快捷鍵全局搜索
  • 利用runtime提供的方法查詢(講解點)
  • 編寫腳本代碼查詢
方案描述
1. 查詢UIWebViewDelegate

查詢工程中的有那些Class實現(xiàn)了UIWebViewDelegate協(xié)議妒御。

static NSArray *LMLiveClassesConformingToProtocol(Protocol *protocol){
    NSMutableArray *conformingClasses = [NSMutableArray new];
    Class *classes = NULL;
    //獲取所有已經注冊過的Class的總個數
    int numClasses = objc_getClassList(NULL, 0);
    if (numClasses > 0 ) {
        classes = (Class *)malloc(sizeof(Class) * numClasses);
        numClasses = objc_getClassList(classes, numClasses);
        for (int index = 0; index < numClasses; index++) {
            Class nextClass = classes[index];
             //校驗是否實現(xiàn)了某個協(xié)議
            if (class_conformsToProtocol(nextClass, protocol)) {
                [conformingClasses addObject:nextClass];
            }
        }
        free(classes);
    }
    return conformingClasses;
}
2.查詢 objc_property_tivar
static NSArray *LMLiveClassAllobjc_propertys(Class className){
    ///  objc_property_t的操作目前只提供
    ///  property_getName(objc_property_t _Nonnull property)
    ///  property_getAttributes(objc_property_t _Nonnull property)
    NSMutableArray *allPropertys = [NSMutableArray new];
    unsigned int propertyNumber = 0;
    objc_property_t *propertys = class_copyPropertyList(className, &propertyNumber);
    for (int i = 0; i < propertyNumber; i++) {
        objc_property_t property_t = propertys[i];
        QMUIPropertyDescriptor *propertyDescriptor = [QMUIPropertyDescriptor descriptorWithProperty:property_t];
        [allPropertys addObject:propertyDescriptor];
    }
    return allPropertys;
}
static NSArray *LMLiveClassAllIvas(Class className){
    ///  ivar的操作目前提供
    ///  ivar_getName(Ivar _Nonnull v)
    ///  ivar_getTypeEncoding(Ivar _Nonnull v)
    NSMutableArray *allIvas = [NSMutableArray new];
    unsigned int allIvarsNumber = 0;
    Ivar *ivars  = class_copyIvarList(className, &allIvarsNumber);
    for (int i = 0; i < allIvarsNumber; i++) {
        Ivar ivar = ivars[i];
        QMUIPropertyDescriptor *propertyDescriptor = [QMUIPropertyDescriptor descriptorWithIvar:ivar];
        [allIvas addObject:propertyDescriptor];
    }
    return allIvas;
}
注意問題

但是通過上述步驟1悲立、2只能解決一部分問題农猬。如下:

  1. Categoty
@interface UIWebView (AFNetworking)
...
@end
@implementation UIWebView (AFNetworking)
...
@end
  1. WebViewDelegateobjc_property_tIvar
@interface DoraemonDefaultWebViewController ()

@end

@implementation DoraemonDefaultWebViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = DoraemonLocalizedString(@"Doraemon內置瀏覽器");
    UIWebView * view = [[UIWebView alloc] initWithFrame:self.view.frame];
    [view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.h5Url]]];
    [self.view addSubview:view];
}

@end

DoraemonDefaultWebViewController中沒有設置UIWebViewDelegate相關屬性教沾,也無property關鍵字聲明的屬性沾瓦,也無_變量_is變量准谚。因此class_conformsToProtocol挫剑、class_copyPropertyListclass_copyIvarList操作是獲取不到UIWebView相關的屬性的。
其實從class_操作方法名稱也可知道是無法獲取的柱衔。

3.objc_setAssociatedObject動態(tài)關聯(lián)的屬性

objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)

使用objc_setAssociatedObject產生的副作用如下:

  • 1.首先校驗要設置的newValue是否為空樊破,不為空執(zhí)行第2步愉棱,否則執(zhí)行第5步

  • 2.AssociationsManager會維護一個全局hashMap,根據object的地址轉換成相應的key,并在hashMap查詢這個key,結果記為refs。如果refs不為空執(zhí)行第3步哲戚,為空執(zhí)行第4步奔滑。

    1. refs中以key查找oldValue,如果oldValue不為空更換為新值newValue, oldValue為空則以key為鍵值顺少,將newValue存儲起來朋其。
    1. 生成新的ObjectAssociationMap refs,并將newValue存儲到refs中。最后根據object的地址轉換成相應的key脆炎,將新生成的refs插入到全局的hashMap中梅猿。
  • 5.在全局hashMap中查找object對應的 refs,并在refs中將key對應的屬性銷毀秒裕。

從上述步驟來看袱蚓,這個全局hashMap內部存儲的子map是動態(tài)可變的(objc_setAssociatedObject方法的執(zhí)行),我們必須要讓當前對象執(zhí)行objc_setAssociatedObject才能在hashMap中獲取到簇爆,對于一個龐大的工程(依賴眾多)來說癞松,這種操作是不可取的。

4.objc_registerClassPair(懂的不多)

  subclass = objc_allocateClassPair(baseClass, subclassName, 0);
  objc_registerClassPair(subclass);
其他 objc_setAssociatedObjectobjc_getAssociatedObject源碼
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    //聲明一個存在oldValue的變量以便后續(xù)value為空時或者更換value時更換
    ObjcAssociation old_association(0, nil);
    //判斷new_value是否為空入蛆,copy與retain方法的執(zhí)行
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        //一個全局變量响蓉,維護一個map,key為object變量的地址某種變形DISGUISE(object),value為一個refs,refs存儲是為object綁定set_associative的值
        AssociationsManager manager;
        //拿到HashMap哨毁,里面為懶加載
        AssociationsHashMap &associations(manager.associations());
        // 獲取object的變形key
        disguised_ptr_t disguised_object = DISGUISE(object);
        //校驗插入新的newVale是否為空
        if (new_value) {
            // break any existing association.
            //查詢object對應的disguised_object是否在associations存在
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            //在associations查詢到object對應的表
            if (i != associations.end()) {
                // secondary table exists
                //取出object對應的表refs
                ObjectAssociationMap *refs = i->second;
                //在refs map查找key是否存在
                ObjectAssociationMap::iterator j = refs->find(key);
                //查找到key對應的j
                if (j != refs->end()) {
                    //舊值賦給old_association
                    old_association = j->second;
                    //更新新值
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    //之前沒有存儲過key對應的value,直接插入map中
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                //在associations沒有找到枫甲,從新構建一個refs,以disguised_object為key,refs為value存儲到
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                //將new_value存儲到refs中
                (*refs)[key] = ObjcAssociation(policy, new_value);
                //object對應的map不為空了
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            //查詢object對應的map
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            //associations存在object對應的map
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                //在上一步找到的map中扼褪,查訊key對應的j,存在就將值賦值給old_association
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    //old_association有值想幻,就將其release
    if (old_association.hasValue()) ReleaseValue()(old_association);
}
id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        AssociationsManager manager;
        //獲取 hasMap associations
        AssociationsHashMap &associations(manager.associations());
        //獲取object對應的key -> disguised_object
        disguised_ptr_t disguised_object = DISGUISE(object);
        //在associations中以disguised_object為key查找object對應的value->map
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        //查找到disguised_object對應的i
        if (i != associations.end()) {
            ObjectAssociationMap *refs = i->second;
            //在refs中以key為鍵值查找對應的value
            ObjectAssociationMap::iterator j = refs->find(key);
            //查找到,取值
            if (j != refs->end()) {
                ObjcAssociation &entry = j->second;
                value = entry.value();
                policy = entry.policy();
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        //這個不敢解釋
        ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
    }
    return value;
}

void _object_remove_assocations(id object) {
    //聲明一個list,將object之前_object_set_associative_reference綁定的值存儲起來
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        //獲取associations
        AssociationsHashMap &associations(manager.associations());
        //associations里面未存儲任何值 ->return操作返回
        if (associations.size() == 0) return;
        //獲取key -> disguised_object
        disguised_ptr_t disguised_object = DISGUISE(object);
        //在associations查找以disguised_object為key话浇,object對應的value ->map
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        //查找到了 object對應的map
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.
            ObjectAssociationMap *refs = i->second;
            //存儲refs中的所有value,以便統(tǒng)一執(zhí)行release操作
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // remove the secondary table.
            delete refs;
            associations.erase(i);
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    for_each(elements.begin(), elements.end(), ReleaseValue());
}
鏈接

https://github.com/Tencent/QMUI_iOS

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末脏毯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子幔崖,更是在濱河造成了極大的恐慌食店,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赏寇,死亡現(xiàn)場離奇詭異吉嫩,居然都是意外死亡,警方通過查閱死者的電腦和手機嗅定,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進店門自娩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人渠退,你說我怎么就攤上這事忙迁∑瓴剩” “怎么了?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵动漾,是天一觀的道長丁屎。 經常有香客問我荠锭,道長旱眯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任证九,我火速辦了婚禮删豺,結果婚禮上,老公的妹妹穿的比我還像新娘愧怜。我一直安慰自己呀页,他們只是感情好,可當我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布拥坛。 她就那樣靜靜地躺著蓬蝶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猜惋。 梳的紋絲不亂的頭發(fā)上丸氛,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天,我揣著相機與錄音著摔,去河邊找鬼缓窜。 笑死,一個胖子當著我的面吹牛谍咆,可吹牛的內容都是我干的禾锤。 我是一名探鬼主播,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼摹察,長吁一口氣:“原來是場噩夢啊……” “哼恩掷!你這毒婦竟也來了?” 一聲冷哼從身側響起供嚎,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤黄娘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后查坪,有當地人在樹林里發(fā)現(xiàn)了一具尸體寸宏,經...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年偿曙,在試婚紗的時候發(fā)現(xiàn)自己被綠了氮凝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡望忆,死狀恐怖罩阵,靈堂內的尸體忽然破棺而出竿秆,到底是詐尸還是另有隱情,我是刑警寧澤稿壁,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布幽钢,位于F島的核電站,受9級特大地震影響傅是,放射性物質發(fā)生泄漏匪燕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一喧笔、第九天 我趴在偏房一處隱蔽的房頂上張望帽驯。 院中可真熱鬧,春花似錦书闸、人聲如沸尼变。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫌术。三九已至,卻和暖如春牌借,著一層夾襖步出監(jiān)牢的瞬間度气,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工走哺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蚯嫌,地道東北人。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓丙躏,卻偏偏與公主長得像择示,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子晒旅,可洞房花燭夜當晚...
    茶點故事閱讀 45,930評論 2 361