UIWebView棄用問題

問題描述

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

 ///("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;
    //獲取所有已經(jīng)注冊過的Class的總個數(shù)
    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_t、Ivar
@interface DoraemonDefaultWebViewController ()

@end

@implementation DoraemonDefaultWebViewController

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

@end

DoraemonDefaultWebViewController中沒有設(shè)置UIWebViewDelegate相關(guān)屬性,也無property關(guān)鍵字聲明的屬性,也無_變量、_is變量。因此class_conformsToProtocol、class_copyPropertyListclass_copyIvarList操作是獲取不到UIWebView相關(guān)的屬性的。
其實從class_操作方法名稱也可知道是無法獲取的。

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

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

使用objc_setAssociatedObject產(chǎn)生的副作用如下:

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

  • 2.AssociationsManager會維護一個全局hashMap,根據(jù)object的地址轉(zhuǎn)換成相應(yīng)的key,并在hashMap查詢這個key,結(jié)果記為refs。如果refs不為空執(zhí)行第3步,為空執(zhí)行第4步。

    1. refs中以key查找oldValue,如果oldValue不為空更換為新值newValue, oldValue為空則以key為鍵值,將newValue存儲起來。
    1. 生成新的ObjectAssociationMap refs,并將newValue存儲到refs中。最后根據(jù)object的地址轉(zhuǎn)換成相應(yīng)的key,將新生成的refs插入到全局的hashMap中。
  • 5.在全局hashMap中查找object對應(yīng)的 refs,并在refs中將key對應(yīng)的屬性銷毀。

從上述步驟來看,這個全局hashMap內(nèi)部存儲的子map是動態(tài)可變的(objc_setAssociatedObject方法的執(zhí)行),我們必須要讓當(dāng)前對象執(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對應(yīng)的disguised_object是否在associations存在
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            //在associations查詢到object對應(yīng)的表
            if (i != associations.end()) {
                // secondary table exists
                //取出object對應(yīng)的表refs
                ObjectAssociationMap *refs = i->second;
                //在refs map查找key是否存在
                ObjectAssociationMap::iterator j = refs->find(key);
                //查找到key對應(yīng)的j
                if (j != refs->end()) {
                    //舊值賦給old_association
                    old_association = j->second;
                    //更新新值
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    //之前沒有存儲過key對應(yīng)的value,直接插入map中
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                //在associations沒有找到,從新構(gòu)建一個refs,以disguised_object為key,refs為value存儲到
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                //將new_value存儲到refs中
                (*refs)[key] = ObjcAssociation(policy, new_value);
                //object對應(yīng)的map不為空了
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            //查詢object對應(yīng)的map
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            //associations存在object對應(yīng)的map
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                //在上一步找到的map中,查訊key對應(yīng)的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對應(yīng)的key -> disguised_object
        disguised_ptr_t disguised_object = DISGUISE(object);
        //在associations中以disguised_object為key查找object對應(yīng)的value->map
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        //查找到disguised_object對應(yīng)的i
        if (i != associations.end()) {
            ObjectAssociationMap *refs = i->second;
            //在refs中以key為鍵值查找對應(yīng)的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對應(yīng)的value ->map
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        //查找到了 object對應(yīng)的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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容