iOS-KVO(四) 自定義KVO+Block

iOS-KVO(一) 基本操作
iOS-KVO(二) 使用注意點(diǎn)
iOS-KVO(三) 窺探底層實(shí)現(xiàn)
iOS-KVO(四) 自定義KVO+Block

這一節(jié),將自定義KVO,并且通過Block進(jìn)行返回屬性的變化。只是驗(yàn)證大致流程,并未做一些判斷,比如傳入key是空之類的。

我們還是按照以下流程來講代碼:

KVO主要流程:

  1. 創(chuàng)建一個(gè)中間類并創(chuàng)建class方法,返回父類;
  2. isa指針指向中間類;
  3. 重寫setter方法;
    另外自己另外寫了一個(gè)類(LHObserver),來存放觀察者,觀察的key,以及block。
    以下的代碼全部都在一個(gè)類別中實(shí)現(xiàn):
NSObject+BlockKVO

先簡(jiǎn)單看下 LHObserver這個(gè)類

@interface LHObserver : NSObject

@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *observerKey;
@property (nonatomic, weak) id context;
@property (nonatomic, copy) LH_ObserverHandler observerHandler;

@end

@implementation LHObserver

- (instancetype)initWithObersver:(NSObject *)observer
                     observerKey:(NSString *)observerKey
                 observerHandler:(LH_ObserverHandler)observerHandler
                         context:(nullable id)context
{
    self = [super init];
    
    if ( self ) {
        
        self.observer = observer;
        self.observerKey = observerKey;
        self.observerHandler = observerHandler;
        self.context = context;
        
    }
    
    return self;
}

@end

其中LH_ObserverHandler這個(gè)block為:

/**
 監(jiān)聽屬性變化

 @param observerObject 觀察者
 @param observerKey 觀察的名稱
 @param object 被觀察者
 @param change 監(jiān)聽返回的信息
 @param context 上下文
 */
typedef void(^LH_ObserverHandler)(id observerObject, NSString *observerKey, id object, NSDictionary<NSKeyValueChangeKey,id> *change, id context);

接下來開始KVO的流程代碼

創(chuàng)建一個(gè)中間類

  • 拼接setter方法
/**
 獲取setter方法

 @param observerKey 觀察的名稱
 @return setter SEL
 */
- (SEL)setterSELWithObserverKey:(NSString *)observerKey
{
    //比如observerKey的值為name,那么setter的方法名稱就是 setName:  冒號(hào)不要忘記咯~
    return NSSelectorFromString([NSString stringWithFormat:@"set%@:", [observerKey capitalizedString]]);
}
  • 創(chuàng)建中間類
/**
 創(chuàng)建中間類

 @param originalClass 原先的類
 @return 中間類
 */
- (Class)createKVOClassWithOriginalClass:(Class)originalClass
{
    //獲取原先類的名稱
    NSString *originalClassName = [NSString stringWithUTF8String:object_getClassName(originalClass)];
    //拼接中間類的名稱
    NSString *kvoClassName = [NSString stringWithFormat:@"%@%@", KVO_CLASS_NAME_PREFIX, originalClassName];
    
    //判斷中間類是否存在,如果存在直接返回即可
    Class kvoClass = objc_getClass(kvoClassName.UTF8String);
    
    if ( kvoClass ) {
        return kvoClass;
    }
    
    //不存在的話,就開始初始化與注冊(cè)類
    kvoClass = objc_allocateClassPair([self class], kvoClassName.UTF8String, 0);
    objc_registerClassPair(kvoClass);
    
    //添加class的方法,返回父類
    Method classMethod = class_getInstanceMethod(originalClass, @selector(class));
    class_addMethod(kvoClass, NSSelectorFromString(@"class"), (IMP)getKVOClass, method_getTypeEncoding(classMethod));
    NSLog(@"%d", [self hasSelectorWithClass:kvoClass sel:@selector(class)]);
    
    return kvoClass;
}
/**
 返回父類

 @param self self
 @param _cmd _cmd
 @return 父類
 */
Class getKVOClass(id self, SEL _cmd)
{
    return class_getSuperclass(object_getClass(self));
}

isa指針指向中間類

    //1.創(chuàng)建一個(gè)中間類, 2.并且isa指針指向中間類;
    Class kvoClass = [self createKVOClassWithOriginalClass:[self class]];
    
    //2.isa指針指向中間類;
    object_setClass(self, kvoClass);

重寫setter方法

  • 先判斷當(dāng)前類是否已經(jīng)存在setter方法了,如果有的話就不在添加
/**
 是否已經(jīng)存在sel方法

 @param cls 類
 @param sel sel方法
 @return YES:存在  NO:不存在
 */
-(BOOL)hasSelectorWithClass:(Class)cls sel:(SEL)sel
{
    unsigned int outCount = 0;
    Method *methodList = class_copyMethodList(cls, &outCount);
    for (int i = 0; i < outCount; ++i) {
        Method method = methodList[i];
        SEL selector = method_getName(method);
        if ( selector == sel ) {
            free(methodList);
            return YES;
        }
    }
    free(methodList);
    return NO;
}
  • setter方法的實(shí)現(xiàn)
    //3.重寫setter方法;
    //(1)獲取setter方法 - 上面已經(jīng)先獲取了
    //(2)判斷中間類是否存在setter方法
    if ( ![self hasSelectorWithClass:kvoClass sel:setter] ) {
        //(3)不存在就添加setter方法
        class_addMethod(kvoClass, setter, (IMP)setterIMP, method_getTypeEncoding(setterMethod));
    }

//存儲(chǔ)LHObserver的對(duì)象的可變數(shù)組
    NSMutableArray *observers = objc_getAssociatedObject(self, &LHOBSERVER_ARRAY_ASSOCIATED_KEY);
    if ( !observers ) {
        observers = [NSMutableArray array];
    }
    //初始化LHObserver的實(shí)例對(duì)象
    LHObserver *observerInfo = [[LHObserver alloc] initWithObersver:observer
                                                      observerKey:observerKey
                                                  observerHandler:observerHandler
                                                          context:context];
    [observers addObject:observerInfo];
    
    objc_setAssociatedObject(self, &LHOBSERVER_ARRAY_ASSOCIATED_KEY, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
void setterIMP(id self, SEL _cmd, id newValue)
{
    /*
     1.willChangeValueForKey
     2.[super setter]
     3.didChangeValueForKey
     */

    //獲取getter方法名稱
    NSString *getterFuncName = objc_getAssociatedObject(self, &LH_GETTER_FUNCTION_NAME);
    
    //先將當(dāng)前中間類保存下來
    Class kvoClass = object_getClass(self);
    
    //先將isa指針指向父類,注意之前已經(jīng)實(shí)現(xiàn)過class方法
    Class cls = [self class];
    object_setClass(self, cls);
    
    //獲取舊數(shù)據(jù)
    id oldValue = ((id(*)(id, SEL))objc_msgSend)(self, NSSelectorFromString(getterFuncName));
    
    //調(diào)用父類的setter
    ((id(*)(id, SEL, id))objc_msgSend)(self, _cmd, newValue);
    
    //拼湊數(shù)據(jù)
    NSMutableDictionary *change = [@{} mutableCopy];
    //這里要根據(jù)自己的需求來定,如果對(duì)應(yīng)的value是空的話,直接賦值空字符串
    if ( oldValue ) {
        change[NSKeyValueChangeOldKey] = oldValue;
    } else {
        change[NSKeyValueChangeOldKey] = @"";
    }
    
    if ( newValue ) {
        change[NSKeyValueChangeNewKey] = newValue;
    } else {
        change[NSKeyValueChangeNewKey] = @"";
    }
    
    //再將isa指針指向中間類
    object_setClass(self, kvoClass);
    
    NSMutableArray *observers = objc_getAssociatedObject(self, &LHOBSERVER_ARRAY_ASSOCIATED_KEY);
    if ( !observers || observers.count <= 0 ) {
        return;
    }
    //循環(huán)獲取observerObject,然后執(zhí)行block
    for (LHObserver *observerObject in observers) {
        if ( [observerObject.observerKey isEqualToString:getterFuncName] ) {
            if ( observerObject.observerHandler ) {
                dispatch_async(dispatch_get_global_queue(0, 0), ^{
                    //這里是在子線程
                    observerObject.observerHandler(observerObject.observer, observerObject.observerKey, self, change, observerObject.context);
                });
            }
            break;
        }
    }
}

移除觀察者

/**
 移除觀察者
 
 @param observer 觀察者
 @param observerKey 觀察的名稱
 */
- (void)lh_removeObserver:(NSObject *)observer forObserverKey:(NSString *)observerKey
{
    NSMutableArray *observers = objc_getAssociatedObject(self, &LHOBSERVER_ARRAY_ASSOCIATED_KEY);
    if ( !observers ) {
        return;
    }
    
    LHObserver *tempObserverObject = nil;
    for (LHObserver *observerObject in observers) {
        if ( observerObject.observer == observer && [observerObject.observerKey isEqualToString:observerKey] ) {
            tempObserverObject = observerObject;
            break;
        }
    }
    
    //移除
    if ( tempObserverObject ) {
        [observers removeObject:tempObserverObject];
    }
}
簡(jiǎn)單使用
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

@end

NS_ASSUME_NONNULL_END
#import "ViewController.h"
#import "Person.h"
#import "NSObject+BlockKVO.h"

@interface ViewController ()

@property (nonatomic, strong) Person *p1, *p2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.p1 = [Person new];
    
    [self.p1 setName:@"1"];
    
    [self.p1 lh_addObserver:self
             forObserverKey:@"name"
                    options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
                    context:@"123"
            observerHandler:^(id  _Nonnull observerObject, NSString * _Nonnull observerKey, id  _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change, id  _Nonnull context) {
                NSLog(@"p1 %@, context:%@", change, context);
            }];
    
    
    [self.p1 setName:@"3"];
    
    [self.p1 lh_removeObserver:self forObserverKey:@"name"];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self.p1 setName:@"hui"];
}


- (void)dealloc
{
    NSLog(@"%@ dealloc", [self class]);
}

@end

打印結(jié)果:

2019-07-05 23:52:53.765 KVODemo[5158:175811] {
    new = 3;
    old = 1;
}, context:123

這章主要都是代碼,將之前說的底層原理的流程,自己通過代碼簡(jiǎn)單的實(shí)現(xiàn),代碼還有很多可以改進(jìn)的地方。
上面代碼 傳送門

KVO over!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,666評(píng)論 1 32
  • 面向?qū)ο蟮娜筇匦裕悍庋b、繼承、多態(tài) OC內(nèi)存管理 _strong 引用計(jì)數(shù)器來控制對(duì)象的生命周期。 _weak...
    運(yùn)氣不夠技術(shù)湊閱讀 1,222評(píng)論 0 10
  • 1.設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡(jiǎn)要敘述?設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的邏輯去處理某一種類型...
    龍飝閱讀 2,302評(píng)論 0 12
  • 前幾天去接侄兒放學(xué),看到小朋友們都在玩手機(jī),不禁嘆了口氣,現(xiàn)在孩子真的很早熟,從小學(xué)就開始使用電子設(shè)備。 突然很感...
    禾木記閱讀 455評(píng)論 0 0
  • 南歌子/天上隊(duì)(又一體) 作者:心博、圖片:網(wǎng)絡(luò) 谷靜茅廬矮,波微亂石多。石門三道一條河。小徑通幽,夾岸柳婆娑。 ...
    心博1閱讀 407評(píng)論 0 1

友情鏈接更多精彩內(nèi)容