問題描述
開發(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_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內置瀏覽器");
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_copyPropertyList
與class_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步奔滑。- 在
refs
中以key查找oldValue
,如果oldValue
不為空更換為新值newValue
,oldValue
為空則以key為鍵值顺少,將newValue
存儲起來朋其。
- 在
- 生成新的
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_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對應的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());
}