IOS运行时实现自己的KVO
来源:互联网 发布:pptv网络电视怎么看直播 编辑:程序博客网 时间:2024/06/05 16:14
参考文章 http://www.cocoachina.com/ios/20150313/11321.html
本文在此基础上为每段代码添加详细的注释
#import <Foundation/Foundation.h>
typedef void (^ZZXObservingBlock) (id observedObject,NSString *observedKey, id oldValue,id newValue);
@interface NSObject (ZZXKVO)
/*
添加自己的 addobserver
取代NSObject原有的 [self addObserver:(nonnull NSObject *) forKeyPath:(nonnull NSString *) options:(NSKeyValueObservingOptions) context: (nullable void *)];
PGObservingBlock block回调
取代 NSObject原有的- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
*/
- (void)ZZX_addObserver:(NSObject *)observer forkey:(NSString *)key withBlock:(ZZXObservingBlock)block;
- (void)ZZX_removeObserver:(NSObject *)observer forKey:(NSString *)key;
@end
2.然后是具体实现部分
注意点
1. objc_msgSendSuperCasted(&superclazz, _cmd, newValue); 方法里面一定要给发消息给父类执行父类的setter
2. object_setClass(self, clazz); 方法里面一定要给发消息给父类执行父类的setter
#import "NSObject+ZZXKVO.h"
#import <objc/runtime.h>
#import <objc/message.h>
/*
新建类名的前缀
*/
NSString *const ZZXKVOClassPreFix =@"ZZXKVOClassPreFix_";
/*
动态添加数组的标识
*/
NSString *const ZZXKVOAssociatedObservers =@"ZZXKVOAssociatedObservers";
#pragma mark - ZZXObservationInfo
/*
添加的observer信息,存储在数组里
*/
@interface ZZXObservationInfo :NSObject
@property (nonatomic,weak)NSObject *observer;
@property (nonatomic,copy)NSString *key;
@property (nonatomic,copy)ZZXObservingBlock block;
@end
@implementation ZZXObservationInfo
- (instancetype)initWithObserver:(NSObject *)observer Key:(NSString *)key block:(ZZXObservingBlock)block
{
self = [superinit];
if (self) {
_observer = observer;
_key = key;
_block = block;
}
returnself;
}
@end
@implementation NSObject (ZZXKVO)
#pragma mark - Helpers
/*
通过setter的函数名,获取属性的名字,转换时要去掉“set”,":",并小写名字的第一个字母
*/
static NSString * getterForSetter(NSString *setter)
{
if (setter.length <=0 || ![setterhasPrefix:@"set"] || ![setterhasSuffix:@":"]) {
returnnil;
}
NSRange range =NSMakeRange(3, setter.length -4);
NSString *key = [settersubstringWithRange:range];
// lower case the first letter
NSString *firstLetter = [[keysubstringToIndex:1]lowercaseString];
key = [key stringByReplacingCharactersInRange:NSMakeRange(0,1)
withString:firstLetter];
return key;
}
/*
通过属性名,获取setter方法名,setter方法第一个字母大写
*/
static NSString * setterForGetter(NSString *getter)
{
if (getter.length <=0) {
returnnil;
}
// upper case the first letter
NSString *firstLetter = [[gettersubstringToIndex:1]uppercaseString];
NSString *remainingLetters = [gettersubstringFromIndex:1];
// add 'set' at the begining and ':' at the end
NSString *setter = [NSStringstringWithFormat:@"set%@%@:", firstLetter, remainingLetters];
return setter;
}
#pragma mark - Overridden Methods
/*
新建类自己的setter方法
*/
static void kvo_setter(idself,SEL_cmd,id newValue)
{
NSString *setterName =NSStringFromSelector(_cmd);
NSString *getterName =getterForSetter(setterName);
if (!getterName) {
NSString *reason = [NSStringstringWithFormat:@"Object %@ does not have setter %@",self, setterName];
@throw [NSExceptionexceptionWithName:NSInvalidArgumentException
reason:reason
userInfo:nil];
return;
}
id oldValue = [selfvalueForKey:getterName];
structobjc_super superclazz = {
.receiver =self,
.super_class =class_getSuperclass(object_getClass(self))
};
// cast our pointer so the compiler won't complain
void (*objc_msgSendSuperCasted)(void *,SEL,id) = (void *)objc_msgSendSuper;
// call super's setter, which is original class's setter method
//给父类发送setter,通过断点可以看到执行新执行的是新建类的setter然后是父类的setter
objc_msgSendSuperCasted(&superclazz, _cmd, newValue);
// look up observers and call the blocks
//添加数组用于保存各个添加进来的观察者
NSMutableArray *observers =objc_getAssociatedObject(self, (__bridgeconstvoid *)(ZZXKVOAssociatedObservers));
for (ZZXObservationInfo *eachin observers) {
if ([each.keyisEqualToString:getterName]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
each.block(self, getterName, oldValue, newValue);
});
}
}
}
static Class kvo_class(idself,SEL_cmd)
{
returnclass_getSuperclass(object_getClass(self));
}
- (void)ZZX_addObserver:(NSObject *)observer forkey:(NSString *)key withBlock:(ZZXObservingBlock)block
{
// Step 1: Throw exception if its class or superclasses doesn't implement the setter
SEL setterSelector =NSSelectorFromString(setterForGetter(key));
Method setterMethod =class_getInstanceMethod([selfclass], setterSelector);
//如果没有setter,比如属性是只读
if (!setterMethod) {
NSString *reason = [NSStringstringWithFormat:@"Object %@ does not have a setter for key %@",self, key];
@throw [NSExceptionexceptionWithName:NSInvalidArgumentException
reason:reason
userInfo:nil];
return;
}
Class clazz = object_getClass(self);
NSString *clazzName =NSStringFromClass(clazz);
// if not an KVO class yet
//第一次添加观察者时,还没有创建该类,第二次添加观察者时就不用再创建了
if (![clazzNamehasPrefix:ZZXKVOClassPreFix]) {
clazz = [selfmakeKvoClassWithOriginalClassName:clazzName];
//设置self类型为新建的子类,这样设置属性的值时就会调用新建的子类的setter
object_setClass(self, clazz);
}
// add our kvo setter if this class (not superclasses) doesn't implement the setter?
if (![selfhasSelector:setterSelector]) {
constchar *types =method_getTypeEncoding(setterMethod);
class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types);
}
//开始回调value给各个观察者
ZZXObservationInfo *info = [[ZZXObservationInfoalloc]initWithObserver:observerKey:keyblock:block];
NSMutableArray *observers =objc_getAssociatedObject(self, (__bridgeconstvoid *)(ZZXKVOAssociatedObservers));
if (!observers) {
observers = [NSMutableArrayarray];
objc_setAssociatedObject(self, (__bridgeconstvoid *)(ZZXKVOAssociatedObservers), observers,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[observers addObject:info];
}
- (void)ZZX_removeObserver:(NSObject *)observer forKey:(NSString *)key
{
NSMutableArray* observers =objc_getAssociatedObject(self, (__bridgeconstvoid *)(ZZXKVOAssociatedObservers));
ZZXObservationInfo *infoToRemove;
for (ZZXObservationInfo* infoin observers) {
if (info.observer == observer && [info.keyisEqual:key]) {
infoToRemove = info;
break;
}
}
[observers removeObject:infoToRemove];
}
- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
{
//originalClazzName父类名字 kvoClazzName子类名字
NSString *kvoClazzName = [ZZXKVOClassPreFixstringByAppendingString:originalClazzName];
Class clazz = NSClassFromString(kvoClazzName);
if (clazz) {
return clazz;
}
// class doesn't exist yet, make it
Class originalClazz = object_getClass(self);
//新建类
Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String,0);
// grab class method's signature so we can borrow it
Method clazzMethod =class_getInstanceMethod(originalClazz,@selector(class));
constchar *types =method_getTypeEncoding(clazzMethod);
class_addMethod(kvoClazz,@selector(class), (IMP)kvo_class, types);
//这个一定要在新建方法之后再执行,执行之后,类不能再添加方法了
objc_registerClassPair(kvoClazz);
return kvoClazz;
}
- (BOOL)hasSelector:(SEL)selector
{
Class clazz = object_getClass(self);
unsignedint methodCount =0;
Method* methodList =class_copyMethodList(clazz, &methodCount);
for (unsignedint i =0; i < methodCount; i++) {
SEL thisSelector =method_getName(methodList[i]);
if (thisSelector == selector) {
free(methodList);
returnYES;
}
}
free(methodList);
returnNO;
}
@end
#import <Foundation/Foundation.h>
#import "NSObject+ZZXKVO.h"
@interface zzxKvoTest : NSObject
@property (nonatomic,strong)NSString* name;
+ (id)getInstance;
@end
@implementation zzxKvoTest
+ (id)getInstance
{
staticzzxKvoTest *sharedManager;
staticdispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[zzxKvoTestalloc]init];
});
return sharedManager;
}
- (void)setName:(NSString *)name
{
NSLog(@"hehe");
}
@end
[[zzxKvoTestgetInstance]ZZX_addObserver:selfforkey:@"name"withBlock:^(id observedObject,NSString *observedKey,id oldValue, id newValue) {
}];
完成,有不清楚的地方自己运行代码,断点跟踪,注意self的类型变化。
- IOS运行时实现自己的KVO
- iOS KVO的实现原理
- KVO底层实现--写一个自己的KVO
- 自己手动实现KVO
- iOS开发中KVO的内部实现
- iOS的KVO底层实现原理
- iOS中KVO的底层实现原理
- iOS ---- KVO的内部实现原理
- iOS 开发之KVO的底层实现
- iOS ---- KVO的内部实现原理
- iOS中KVO的实现原理
- ios KVO的使用和原理实现
- iOS KVO 实现分析
- iOS 底层实现 - KVO
- iOS KVO 实现原理
- iOS-KVO 实现原理
- 代码自己实现,深入探究KVO的内部实现
- KVO 自己实现 了解RunTime
- 安卓开发,listView相关(五),数据更新,适配器刷新(全部刷新和局部刷新)
- 欢迎使用CSDN-markdown编辑器
- 【MySQL】mysql中函数DISTINCT、group by、CONCAT、GROUP_CONCAT的使用以及mysql group_concat函数被截断的问题
- C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图
- oj题目回顾(1056)C语言习题5.22--输出已交换后的两个值
- IOS运行时实现自己的KVO
- Android log的常见问题和常用方法
- mongodb数据库在Linux上的使用
- Spring事务管理—aop:pointcut expression解析
- 使用nginx搭建https服务器
- LeetCode 1、Two Sum
- IOS 基础-define、const、extern、全局变量
- 理解 Thread.Sleep 函数
- 网页中实现六边形的N种姿势