iOS runtime 應(yīng)用場景總結(jié)
場景1. 動態(tài)分類關(guān)聯(lián)屬性
場景2. hook/Method Swizzling
場景3. 遍歷類屬性方法叭喜,映射解析以及字典與模型的轉(zhuǎn)換, 例如YYModel
場景4. 修改isa指針(研究中)
場景5. 實(shí)現(xiàn)消息轉(zhuǎn)發(fā)機(jī)制的補(bǔ)救
一般選擇第二步 forwardingTargetForSelector 來做崩潰防護(hù),原因如下:
resolveInstanceMethod 需要在類的本身上動態(tài)添加它本身不存在的方法壤追;
forwardInvocation可以通過NSInvocation的形式將消息轉(zhuǎn)發(fā)給多個(gè)對象,但是其開銷較大跨琳,需要創(chuàng)建新的NSInvocation對象泽论,并且forwardInvocation的函數(shù)經(jīng)常被使用者調(diào)用夺蛇,來做多層消息轉(zhuǎn)發(fā)選擇機(jī)制,不適合多次重寫副渴;
forwardingTargetForSelector可以將消息轉(zhuǎn)發(fā)給一個(gè)對象奈附,開銷較小,可以NSObject的該方法重寫煮剧,做以下幾步的處理:
1). 動態(tài)創(chuàng)建一個(gè)樁類
2). 動態(tài)為樁類添加對應(yīng)的Selector斥滤,用一個(gè)通用的返回0的函數(shù)來實(shí)現(xiàn)該SEL的IMP
3). 將消息直接轉(zhuǎn)發(fā)到這個(gè)樁類對象上。
場景6. 實(shí)現(xiàn) NSCoding 的自動歸檔和解檔(暫不支持嵌套勉盅,可用于詳情模型的本地存儲)
- (void)encodeWithCoder:(NSCoder *)aCoder {
// 一個(gè)臨時(shí)數(shù)據(jù), 用來記錄一個(gè)類成員變量的個(gè)數(shù)
unsigned int ivarCount = 0;
// 獲取一個(gè)類所有的成員變量
Ivar *ivars = class_copyIvarList(self.class, &ivarCount);
// 變量成員變量列表
for (int i = 0; i < ivarCount; i ++) {
// 獲取單個(gè)成員變量
Ivar ivar = ivars[i];
// 獲取成員變量的名字并將其轉(zhuǎn)換為 OC 字符串
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 獲取該成員變量對應(yīng)的值
id value = [self valueForKey:ivarName];
// 歸檔, 就是把對象 key-value 對 encode
[aCoder encodeObject:value forKey:ivarName];
}
// 釋放 ivars
free(ivars);
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
// 因?yàn)闆]有 superClass 了
self = [self init];
if (self != nil) {
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(self.class, &ivarCount);
for (int i = 0; i < ivarCount; i ++) {
Ivar ivar = ivars[i];
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 解檔, 就是把 key-value 對 decode
id value = [aDecoder decodeObjectForKey:ivarName];
// 賦值
[self setValue:value forKey:ivarName];
}
free(ivars);
}
return self;
}
場景7.分類重寫 kvc 方法佑颇,防崩潰
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"setValue: forUndefinedKey:, 動態(tài)創(chuàng)建Key: %@",key);
objc_setAssociatedObject(self, CFBridgingRetain(key), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(nullable id)valueForUndefinedKey:(NSString *)key{
NSLog(@"valueForUndefinedKey:, 獲取未知鍵 %@ 的值", key);
// return nil;
return objc_getAssociatedObject(self, CFBridgingRetain(key));
}
-(void)setNilValueForKey:(NSString *)key{
NSLog(@"Invoke setNilValueForKey:, 不能給非指針對象(如NSInteger)賦值 nil");
return;//給一個(gè)非指針對象(如NSInteger)賦值 nil, 直接忽略
}
場景 8:獲取類的成員變量,屬性菇篡,方法漩符,協(xié)議
- (void)enumerateIvars:(void(^)(Ivar v, NSString *name, _Nullable id value))block{
unsigned int count;
Ivar *ivars = class_copyIvarList(self.class, &count);
for(NSInteger i = 0; i < count; i++){
Ivar ivar = ivars[i];
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [self valueForKey:ivarName];//kvc讀值
if (block) {
block(ivar, ivarName, value);
}
}
free(ivars);
}
- (void)enumeratePropertys:(void(^)(objc_property_t property, NSString *name, _Nullable id value))block{
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList(self.class, &count);
for (int i = 0; i < count; i++) {
objc_property_t property_t = properties[i];
const char *name = property_getName(property_t);
NSString *propertyName = [NSString stringWithUTF8String:name];
id value = [self valueForKey:propertyName];
if (block) {
block(property_t, propertyName, value);
}
}
free(properties);
}
- (void)enumerateMethods:(void(^)(Method method, NSString *name))block{
unsigned int count = 0;
Method *methodList = class_copyMethodList(self.class, &count);
for (unsigned int i = 0; i < count; i++) {
Method method = methodList[i];
SEL mthodName = method_getName(method);
// NSLog(@"MethodName(%d): %@", i, NSStringFromSelector(mthodName));
if (block) {
block(method, NSStringFromSelector(mthodName));
}
}
free(methodList);
}
- (void)enumerateProtocols:(void(^)(Protocol *proto, NSString *name))block{
unsigned int count = 0;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList(self.class, &count);
for (int i = 0; i < count; i++) {
Protocol *protocal = protocolList[i];
const char *protocolName = protocol_getName(protocal);
// NSLog(@"protocol(%d): %@", i, [NSString stringWithUTF8String:protocolName]);
if (block) {
block(protocal, [NSString stringWithUTF8String:protocolName]);
}
}
free(protocolList);
}
【附】:賦值和取值(沒用到過,因?yàn)?KVC 更方便)
//獲取實(shí)例進(jìn)行賦值操作
Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
object_setIvar(self.textField, ivar, "請輸入");
//獲取當(dāng)前類對應(yīng)特征名稱的實(shí)例變量,得到該實(shí)例變量的數(shù)值
Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
id object = object_getIvar(self.textField, ivar);
場景 9. 動態(tài)創(chuàng)建/獲取 class驱还,selector 等以及視圖子元素動態(tài)化重構(gòu)嗜暴,極大地提高代碼復(fù)用率
NSClassFromString(@"MyClass");
NSSelectorFromString(@"showShareActionSheet");
場景 10. 動態(tài)創(chuàng)建Class
objc_allocateClassPair可以動態(tài)創(chuàng)建Class,
objc_registerClassPair進(jìn)行注冊動態(tài)創(chuàng)建的Class
修改對象的Class
object_setClass可以修改對象的Class,即修改了isa指針指向的Class對象
場景 11. 動態(tài)調(diào)整 accessoryView 位置议蟆,使其上下居中(默認(rèn)不居中)
import UIKit
@objc extension UITableViewCell{
override public class func initializeMethod() {
super.initializeMethod();
if self != UITableViewCell.self {
return
}
let onceToken = "Hook_\(NSStringFromClass(classForCoder()))";
DispatchQueue.once(token: onceToken) {
let oriSel = NSSelectorFromString("layoutSubviews")
let repSel = #selector(self.hook_layoutSubviews)
_ = hookInstanceMethod(of: oriSel, with: repSel);
}
}
private func hook_layoutSubviews() {
hook_layoutSubviews()
positionAccessoryView()
}
}
@objc public extension UITableViewCell{
///調(diào)整AccessoryView位置(默認(rèn)垂直居中)
func positionAccessoryView(_ dx: CGFloat = 0, dy: CGFloat = 0) {
var accessory: UIView?
if let accessoryView = self.accessoryView {
accessory = accessoryView
} else if self.accessoryType != .none {
for subview in self.subviews {
if subview != self.textLabel && subview != self.detailTextLabel
&& subview != self.backgroundView && subview != self.selectedBackgroundView
&& subview != self.imageView && subview != self.contentView
&& subview.isKind(of: UIButton.self) {
accessory = subview
break
}
}
}
if accessory != nil {
accessory!.center = CGPoint(x: accessory!.center.x + dx, y: self.bounds.midY + dy)
}
}
}
場景 12. 防止數(shù)組越界和字典賦 nil 造成的崩潰(支持語法糖保護(hù))
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_target = [NNForwardingTarget new];;
if (isOpenCashProtector) {
swizzleInstanceMethod(NSClassFromString(@"__NSArrayI"),
@selector(objectAtIndex:), NSSelectorFromString(@"safe_objectAtIndex:"));
swizzleInstanceMethod(NSClassFromString(@"__NSArrayI"),
@selector(objectAtIndexedSubscript:), @selector(safe_objectAtIndexedSubscript:));
swizzleInstanceMethod(NSClassFromString(@"__NSArrayM"),
@selector(objectAtIndex:), NSSelectorFromString(@"safe_objectAtIndex:"));
swizzleInstanceMethod(NSClassFromString(@"__NSArrayM"),
@selector(objectAtIndexedSubscript:), NSSelectorFromString(@"safe_objectAtIndexedSubscript:"));
swizzleInstanceMethod(NSClassFromString(@"__NSArrayM"),
@selector(addObject:), NSSelectorFromString(@"safe_addObject:"));
swizzleInstanceMethod(NSClassFromString(@"__NSArrayM"),
@selector(insertObject:atIndex:), NSSelectorFromString(@"safe_insertObject:atIndex:"));
//NSClassFromString(@"__NSDictionaryM"),objc_getClass("__NSDictionaryM")
swizzleInstanceMethod(NSClassFromString(@"__NSDictionaryM"),
@selector(setObject:forKey:), NSSelectorFromString(@"safe_setObject:forKey:"));
swizzleInstanceMethod(NSClassFromString(@"__NSDictionaryM"),
@selector(setObject:forKeyedSubscript:), @selector(safe_setObject:forKeyedSubscript:));
swizzleInstanceMethod(NSClassFromString(@"__NSDictionaryM"),
@selector(removeObjectForKey:), @selector(safe_removeObjectForKey:));
swizzleInstanceMethod(self.class,
@selector(forwardingTargetForSelector:), @selector(safe_forwardingTargetForSelector:));
}
});
}
@implementation NSArray (CashProtector)
- (id)safe_objectAtIndex:(NSUInteger)index{
if (index >= self.count) {
if (isOpenAssert) NSAssert(index < self.count, @"index越界");
return nil;
}
return [self safe_objectAtIndex:index];
}
- (id)safe_objectAtIndexedSubscript:(NSUInteger)index {
NSUInteger count = self.count;
if (count == 0 || index >= count) {
if (isOpenAssert) NSAssert(index < self.count, @"index越界");
return nil;
}
return [self safe_objectAtIndexedSubscript:index];
}
@end
@implementation NSMutableArray (CashProtector)
- (id)safe_objectAtIndex:(NSUInteger)index{
if (index >= self.count) {
// DDLog(@"index越界");
if (isOpenAssert) NSAssert(index < self.count, @"index越界");
return nil;
}
return [self safe_objectAtIndex:index];
}
- (id)safe_objectAtIndexedSubscript:(NSUInteger)index {
NSUInteger count = self.count;
if (count == 0 || index >= count) {
return nil;
}
return [self safe_objectAtIndexedSubscript:index];
}
- (void)safe_addObject:(id)anObject{
if(!anObject){
if (isOpenAssert) NSAssert(anObject, @"anObject不能為nil");
return ;
}
[self safe_addObject:anObject];
}
- (void)safe_insertObject:(id)anObject atIndex:(NSUInteger)index{
if(!anObject){
if (isOpenAssert) NSAssert(anObject, @"anObject不能為nil");
return ;
}
[self safe_insertObject:anObject atIndex:index];
}
@end
@implementation NSMutableDictionary (CashProtector)
- (void)safe_setObject:(id)anObject forKey:(id <NSCopying>)aKey{
if (isOpenAssert) NSAssert(anObject && aKey, @"anObject和aKey不能為nil");
if (anObject && aKey) {
[self safe_setObject:anObject forKey:aKey];
}
}
- (void)safe_setObject:(id)anObject forKeyedSubscript:(id <NSCopying>)aKey {
// if (isOpenAssert) NSAssert(anObject && aKey, @"anObject和aKey不能為nil");
if (anObject && aKey) {
[self safe_setObject:anObject forKeyedSubscript:aKey];
}
}
- (void)safe_removeObjectForKey:(id <NSCopying>)aKey {
if (isOpenAssert) NSAssert(aKey, @"aKey不能為nil");
if (aKey) {
[self safe_removeObjectForKey:aKey];
}
}
@end