眾所周知,在iOS13中使用KVC設(shè)置UITextField占位符會導(dǎo)致崩潰忧设,出于好奇刁标,今天我對崩潰原因進行了一番簡單探索,現(xiàn)將探索過程記錄如下:
[textfield setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
上面這段代碼在iOS13之前一直工作良好址晕,更新到iOS13后莫名其妙就崩潰了膀懈,需要通過設(shè)置attributedPlaceholder屬性來代替。很多人覺得是iOS13對KVC的支持變?nèi)趿私骼矣X得不太可能启搂,應(yīng)該是蘋果爸爸偷偷把私有實例變量名(或?qū)傩悦┙o改了。廢話不多說刘陶,開始驗證胳赌,首先下載一個Xcode11,分別在iOS13和iOS12系統(tǒng)中運行以下代碼易核,通過runtime獲取并打印UITextField的所有屬性:
unsigned int count;
objc_property_t *ptr = class_copyPropertyList([textfield class], &count);
for (NSUInteger i = 0; i < count; i++) {
NSLog(@"%s", property_getName(ptr[i]));
}
free(ptr);
運行結(jié)果顯示匈织,無論是在iOS12還是iOS13環(huán)境下,都沒有“placeholderLabel”相關(guān)的屬性牡直,所以_placeholderLabel不是UITextField的屬性缀匕。既然能用KVC訪問,不是屬性碰逸,就應(yīng)該是實例變量咯乡小,繼續(xù)驗證,我們通過runtime獲取并打印UITextField的所有實例變量:
unsigned int count2;
Ivar *ptr2 = class_copyIvarList([textfield class], &count2);
for (NSUInteger i = 0; i < count2; i++) {
NSLog(@"%s", ivar_getName(ptr2[i]));
}
free(ptr2);
果然饵史,我們在萬花叢中如愿找到了_placeholderLabel满钟,遺憾的是,無論在iOS12還是iOS13環(huán)境下胳喷,UITextField都存在這個實例變量湃番。顯然,我一開始的推測被啪啪打臉了吭露。
一條路走不通吠撮,馬上調(diào)整思路,去找其他線索讲竿,不防先來仔細研究一下蘋果給的崩潰信息
結(jié)合崩潰堆棧和拋出的異常信息繼續(xù)分析泥兰,首先,崩潰是發(fā)生在-[UITextField valueForKey:]题禀,也就是說鞋诗,UITextField獲取_placeholderLabel時其實就已經(jīng)崩潰了;再來看異常信息迈嘹,“access to UITextField's _placeholderLabel ivar is prohibited. This is an application bug”削彬,_placeholderLabel被禁止使用,這是一個應(yīng)用程序bug,不知道大家之前有沒有遇到這種異常吃警,我是從來沒遇到過糕篇,通常都是valueForUndefinedKey:異常。
接下來我們驗證一下蘋果是否修改了valueForKey:的邏輯酌心,我們找一個和_placeholderLabel同級別的實例變量_textContentView,同樣按照按照KVC方式去設(shè)置其背景色
[textfield setValue:[UIColor redColor] forKeyPath:@"_textContentView.backgroundColor"];
在iOS13環(huán)境下運行代碼挑豌,沒有崩潰安券,而且textfield背景色被修改成了紅色,說明這段代碼已經(jīng)生效了氓英。至此侯勉,valueForKey:也得以沉冤得雪,退出了背鍋群铝阐!
還有一種可能址貌,蘋果在通過valueForKey:獲取實例時,對值為“_placeholderLabel”的key作了特殊處理徘键,當發(fā)現(xiàn)key為“_placeholderLabel”時练对,拋出上文中提到的異常。怎么證明呢吹害,我們知道KVC獲取實例時螟凭,對下劃線并不敏感,我們可以去掉下劃線來試試它呀。
[textfield setValue:[UIColor redColor] forKeyPath:@"placeholderLabel.textColor"];
iOS13環(huán)境下運行B菽小!纵穿!巧了不是下隧!巧了不是!可以正常工作了谓媒。忙活半天淆院,總算找到原因了。天下武功出少林篙耗,天下iOS奇淫技巧出runtime迫筑,接下來,又輪到萬能的runtime上場啦宗弯,添加以下代碼脯燃,老代碼不用作任何修改,又可以天下太平了:
@implementation UITextField (KVC)
+ (void)load {
Method origin = class_getInstanceMethod([self class], @selector(valueForKey:));
Method swizzing = class_getInstanceMethod([self class], @selector(swizzing_valueForKey:));
if (class_addMethod([self class], @selector(valueForKey:), method_getImplementation(swizzing), method_getTypeEncoding(swizzing))) {
class_replaceMethod([self class], @selector(swizzing_valueForKey:), method_getImplementation(origin), method_getTypeEncoding(origin));
}
method_exchangeImplementations(origin, swizzing);
}
- (id)swizzing_valueForKey:(NSString *)key {
if ([key isEqualToString:@"_placeholderLabel"]) {
Ivar ivar = class_getInstanceVariable([self class], [key UTF8String]);
id value = object_getIvar(self, ivar);
return value;
} else {
return [self swizzing_valueForKey:key];
}
}
當然蒙保,這段代碼主要是用來驗證猜想辕棚,不建議在開發(fā)中使用。蘋果的意圖很明顯,就是為了讓我們使用公開的attributedPlaceholder屬性來代替私有API逝嚎。
后記
這篇文章并沒有多少開發(fā)干貨扁瓢,主要是為了記錄一下自己探索的過程,希望能對今后定位同類型問題時提供一種思路补君。另外引几,文中如果有任何紕漏或錯誤,歡迎大家批評指正挽铁!