上节课内容补充,runtime 内存平移
- (void)viewDidLoad{ //两个隐藏参数(id self , SEL _cmd)
[super viewDidLoad]; //结构体 {objc class}
/**
上面一行代码相当于:
struct objc_super k_objc_super;//结构体临时变量
k_objc_super.receiver = person;
k_objc_super.super_class = LGPerson.class;
objc_msgSendSuper(&k_objc_super,@selector(sayhello));
*/
Class cls = [LGPersonP class];//这里的隐藏参数于viewdidload的参数无关
void *kc = &cls;
[(__bridge id)kc saySomething];
}
问题1:为什么[super viewDidLoad]
传过来的是ViewController
而不是UIViewController
?
打断点真机运行:
进入libobjc.A.dylib objc_msgSendSuper2
:
疑问的点: 结构体 {self , superclass / currentClass}
以上是源码注释的解释,但我们还需要真机运行测试一下看看究竟是否如解释的这般。
经过真机测试我们可以看到,这里确实是传的currentclass
。
method_swizzling
问题一:method_swizzling 在哪里调用?
1、可放在+(void)load{}
方法中;
2、尽量放在单例里面,这样保证了只会执行一次。
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[LGRuntimeTool lg_bestMethodSwizzlingWithClass:self oriSEL:@selector(helloword) swizzledSEL:@selector(lg_studentInstanceMethod)];
});
}
方法在父类存在,子类不存在,会崩溃; 方法在父类不存在,子类不存在,会出现死循环;
+ (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
method_exchangeImplementations(oriMethod, swiMethod);
}
+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
// 交换自己没有实现的方法:
// 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
// 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL
//oriSEL:personInstanceMethod
BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
}
+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"传入的交换类不能为空");
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
if (!oriMethod) { // 避免动作没有意义
// 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
NSLog(@"来了一个空的 imp");
}));
}
// 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
// 交换自己没有实现的方法:
// 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
// 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL
//oriSEL:personInstanceMethod
BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
}
KVC 原理
苹果官方文档内容摘录:
Search Pattern for the Basic Getter
The default implementation of valueForKey:
, given a key
parameter as input, carries out the following procedure, operating from within the class instance receiving the valueForKey:
call.
-
Search the instance for the first accessor method found with a name like
get<Key>
,<key>
,is<Key>
, or_<key>
, in that order. If found, invoke it and proceed to step 5 with the result. Otherwise proceed to the next step. -
If no simple accessor method is found, search the instance for methods whose names match the patterns
countOf<Key>
andobjectIn<Key>AtIndex:
(corresponding to the primitive methods defined by theNSArray
class) and<key>AtIndexes:
(corresponding to theNSArray
methodobjectsAtIndexes:
).If the first of these and at least one of the other two is found, create a collection proxy object that responds to all
NSArray
methods and return that. Otherwise, proceed to step 3.The proxy object subsequently converts any
NSArray
messages it receives to some combination ofcountOf<Key>
,objectIn<Key>AtIndex:
, and<key>AtIndexes:
messages to the key-value coding compliant object that created it. If the original object also implements an optional method with a name likeget<Key>:range:
, the proxy object uses that as well, when appropriate. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were anNSArray
, even if it is not. -
If no simple accessor method or group of array access methods is found, look for a triple of methods named
countOf<Key>
,enumeratorOf<Key>
, andmemberOf<Key>:
(corresponding to the primitive methods defined by theNSSet
class).If all three methods are found, create a collection proxy object that responds to all
NSSet
methods and return that. Otherwise, proceed to step 4.This proxy object subsequently converts any
NSSet
message it receives into some combination ofcountOf<Key>
,enumeratorOf<Key>
, andmemberOf<Key>:
messages to the object that created it. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were anNSSet
, even if it is not. -
If no simple accessor method or group of collection access methods is found, and if the receiver's class method
accessInstanceVariablesDirectly
returnsYES
, search for an instance variable named_<key>
,_is<Key>
,<key>
, oris<Key>
, in that order. If found, directly obtain the value of the instance variable and proceed to step 5. Otherwise, proceed to step 6. -
If the retrieved property value is an object pointer, simply return the result.
If the value is a scalar type supported by
NSNumber
, store it in anNSNumber
instance and return that.If the result is a scalar type not supported by NSNumber, convert to an
NSValue
object and return that. -
If all else fails, invoke
valueForUndefinedKey:
. This raises an exception by default, but a subclass ofNSObject
may provide key-specific behavior.
Search Pattern for the Basic Setter
The default implementation of setValue:forKey:
, given key
and value
parameters as input, attempts to set a property named key
to value
(or, for non-object properties, the unwrapped version of value
, as described in Representing Non-Object Values) inside the object receiving the call, using the following procedure:
- Look for the first accessor named
set<Key>:
or_set<Key>
, in that order. If found, invoke it with the input value (or unwrapped value, as needed) and finish. - If no simple accessor is found, and if the class method
accessInstanceVariablesDirectly
returnsYES
, look for an instance variable with a name like_<key>
,_is<Key>
,<key>
, oris<Key>
, in that order. If found, set the variable directly with the input value (or unwrapped value) and finish. - Upon finding no accessor or instance variable, invoke
setValue:forUndefinedKey:
. This raises an exception by default, but a subclass ofNSObject
may provide key-specific behavior.
苹果文档:developer.apple.com/develop/
Documentation Archive: developer.apple.com/library/arc…
自定义KVC
@interface NSObject (LGKVC)
// LG KVC 自定义入口
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key;
@end
NSOBject+LGGGKVC
#import "NSObject+LGKVC.h"
#import <objc/runtime.h>
@implementation NSObject (LGKVC)
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key{
// KVC 自定义
// 1: 判断什么 key
if (key == nil || key.length == 0) {
return;
}
// 2: setter set<Key>: or _set<Key>,
// key 要大写
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
if ([self lg_performSelectorWithMethodName:setKey value:value]) {
NSLog(@"*********%@**********",setKey);
return;
}else if ([self lg_performSelectorWithMethodName:_setKey value:value]) {
NSLog(@"*********%@**********",_setKey);
return;
}else if ([self lg_performSelectorWithMethodName:setIsKey value:value]) {
NSLog(@"*********%@**********",setIsKey);
return;
}
// 3: 判断是否响应 accessInstanceVariablesDirectly 返回YES NO 奔溃
// 3:判断是否能够直接赋值实例变量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4: 间接变量
// 获取 ivar -> 遍历 containsObjct -
// 4.1 定义一个收集实例变量的可变数组
NSMutableArray *mArray = [self getIvarListName];
// _<key> _is<Key> <key> is<Key>
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
// 4.2 获取相应的 ivar
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
// 4.3 对相应的 ivar 设置值
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}
// 5:如果找不到相关实例
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}
- (nullable id)lg_valueForKey:(NSString *)key{
// 1:刷选key 判断非空
if (key == nil || key.length == 0) {
return nil;
}
// 2:找到相关方法 get<Key> <key> countOf<Key> objectIn<Key>AtIndex
// key 要大写
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
return [self performSelector:NSSelectorFromString(getKey)];
}else if ([self respondsToSelector:NSSelectorFromString(key)]){
return [self performSelector:NSSelectorFromString(key)];
}else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
for (int i = 0; i<num-1; i++) {
num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
}
for (int j = 0; j<num; j++) {
id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
[mArray addObject:objc];
}
return mArray;
}
}
#pragma clang diagnostic pop
// 3:判断是否能够直接赋值实例变量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4.找相关实例变量进行赋值
// 4.1 定义一个收集实例变量的可变数组
NSMutableArray *mArray = [self getIvarListName];
// _<key> _is<Key> <key> is<Key>
// _name -> _isName -> name -> isName
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
return object_getIvar(self, ivar);;
}
return @"";
}
#pragma mark - 相关方法
- (BOOL)lg_performSelectorWithMethodName:(NSString *)methodName value:(id)value{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
return YES;
}
return NO;
}
- (id)performSelectorWithMethodName:(NSString *)methodName{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(methodName) ];
#pragma clang diagnostic pop
}
return nil;
}
- (NSMutableArray *)getIvarListName{
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i];
const char *ivarNameChar = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];
NSLog(@"ivarName == %@",ivarName);
[mArray addObject:ivarName];
}
free(ivars);
return mArray;
}