問題描述
開發(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_t與ivar
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只能解決一部分問題。如下:
- Categoty
@interface UIWebView (AFNetworking)
...
@end
@implementation UIWebView (AFNetworking)
...
@end
- 無
WebViewDelegate與objc_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_copyPropertyList與class_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步。- 在
refs中以key查找oldValue,如果oldValue不為空更換為新值newValue,oldValue為空則以key為鍵值,將newValue存儲起來。
- 在
- 生成新的
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_setAssociatedObject與objc_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());
}