ReactiveObjC 源碼閱讀筆記 (-rac_signalForSelector:)

常用方法響應(yīng) -rac_signalForSelector:

使用:

    [[[self rac_signalForSelector:@selector(viewWillAppear:)] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(RACTuple * _Nullable x) {
        
    }];

源碼

  • NSObject+RACSelectorSignal
- (RACSignal *)rac_signalForSelector:(SEL)selector {
    NSCParameterAssert(selector != NULL);

    return NSObjectRACSignalForSelector(self, selector, NULL);
}

static RACSignal *NSObjectRACSignalForSelector(NSObject *self, SEL selector, Protocol *protocol) {
     // rac_alias_ + 方法名 = 方法別名
    SEL aliasSelector = RACAliasForSelector(selector);

    @synchronized (self) {
        // 方法別名關(guān)聯(lián)對(duì)象于置,存儲(chǔ)subject褥芒,存在就直接返回,供外部訂閱做入,不存在,去創(chuàng)建
        RACSubject *subject = objc_getAssociatedObject(self, aliasSelector);
        if (subject != nil) return subject;
        /**
          創(chuàng)建名為 Class_RACSelectorSignal 的子類
          重寫子類一系統(tǒng)的方法同衣,進(jìn)行掩飾
          使 isa 指針指向新創(chuàng)建的子類
          返回新創(chuàng)建的 Class_RACSelectorSignal竟块,下面的操作都針對(duì) Class_RACSelectorSignal。
          具體看 RACSwizzleClass()
        */
        Class class = RACSwizzleClass(self);
        NSCAssert(class != nil, @"Could not swizzle class of %@", self);
        // 創(chuàng)建 subject耐齐,存到以方法別名為名的關(guān)聯(lián)對(duì)象中浪秘。下次可直接取出使用蒋情。
        subject = [[RACSubject subject] setNameWithFormat:@"%@ -rac_signalForSelector: %s", RACDescription(self), sel_getName(selector)];
        objc_setAssociatedObject(self, aliasSelector, subject, OBJC_ASSOCIATION_RETAIN);

        [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
            [subject sendCompleted];
        }]];

        Method targetMethod = class_getInstanceMethod(class, selector);
        if (targetMethod == NULL) {
            // 監(jiān)聽的方法不存在
            const char *typeEncoding;
            if (protocol == NULL) {
                typeEncoding = RACSignatureForUndefinedSelector(selector);
            } else {
                // Look for the selector as an optional instance method.
                struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);

                if (methodDescription.name == NULL) {
                    // Then fall back to looking for a required instance
                    // method.
                    methodDescription = protocol_getMethodDescription(protocol, selector, YES, YES);
                    NSCAssert(methodDescription.name != NULL, @"Selector %@ does not exist in <%s>", NSStringFromSelector(selector), protocol_getName(protocol));
                }

                typeEncoding = methodDescription.types;
            }

            RACCheckTypeEncoding(typeEncoding);

            // Define the selector to call -forwardInvocation:.
            // 添加這個(gè)方法,實(shí)現(xiàn)為_objc_msgForward
            if (!class_addMethod(class, selector, _objc_msgForward, typeEncoding)) {
                // 如果添加失敗耸携,直接返一個(gè)ErrorSignal棵癣。
                NSDictionary *userInfo = @{
                    NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"A race condition occurred implementing %@ on class %@", nil), NSStringFromSelector(selector), class],
                    NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Invoke -rac_signalForSelector: again to override the implementation.", nil)
                };

                return [RACSignal error:[NSError errorWithDomain:RACSelectorSignalErrorDomain code:RACSelectorSignalErrorMethodSwizzlingRace userInfo:userInfo]];
            }
        } else if (method_getImplementation(targetMethod) != _objc_msgForward) {
            // 方法存在,且方法的實(shí)現(xiàn)不是 _objc_msgForward
            // Make a method alias for the existing method implementation.
            const char *typeEncoding = method_getTypeEncoding(targetMethod);

            RACCheckTypeEncoding(typeEncoding);
            /**
            添加一個(gè)方法夺衍,方法名是 rac_alias_originMethod狈谊,實(shí)現(xiàn)是目標(biāo)方法 originMethod的實(shí)現(xiàn)。
            然后重寫目標(biāo)方法的實(shí)現(xiàn)沟沙,改為_objc_msgForward河劝。
            相當(dāng)于,下次矛紫,再調(diào)用目標(biāo)方法的時(shí)候赎瞎,會(huì)直接走消息轉(zhuǎn)發(fā)流程,然后RAC在消息轉(zhuǎn)發(fā)流程中颊咬,先觸發(fā) signal 響應(yīng)务甥,然后再執(zhí)行 rac_alias_originMethod,也就是原方法的實(shí)現(xiàn)喳篇。以達(dá)到監(jiān)聽方法的目的敞临。
            */
            BOOL addedAlias __attribute__((unused)) = class_addMethod(class, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
            NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), class);

            // Redefine the selector to call -forwardInvocation:.
            class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod));
        }

        return subject;
    }
}

// 修改 Class
static Class RACSwizzleClass(NSObject *self) {
    /**
     因?yàn)?class方法被重寫,所以這兩個(gè)值有可能被替換
     statedClass:Class杭隙,對(duì)外表示的類
     baseClass:真正的類哟绊,isa 指向的類 Class 或 Class_RACSelectorSignal
    */
    Class statedClass = self.class;
    Class baseClass = object_getClass(self);

    // The "known dynamic subclass" is the subclass generated by RAC.
    // It's stored as an associated object on every instance that's already
    // been swizzled, so that even if something else swizzles the class of
    // this instance, we can still access the RAC generated subclass.
    Class knownDynamicSubclass = objc_getAssociatedObject(self, RACSubclassAssociationKey);
    if (knownDynamicSubclass != Nil) return knownDynamicSubclass;

    NSString *className = NSStringFromClass(baseClass);

    if (statedClass != baseClass) {
        // If the class is already lying about what it is, it's probably a KVO
        // dynamic subclass or something else that we shouldn't subclass
        // ourselves.
        //
        // Just swizzle -forwardInvocation: in-place. Since the object's class
        // was almost certainly dynamically changed, we shouldn't see another of
        // these classes in the hierarchy.
        //
        // Additionally, swizzle -respondsToSelector: because the default
        // implementation may be ignorant of methods added to this class.
        // 這里是兼容系統(tǒng)KVO,因?yàn)橄到y(tǒng)KVO也會(huì)修改 isa痰憎。如果已經(jīng)使用過系統(tǒng)KVO票髓,則直接交換相關(guān)方法,不需要再重新新類铣耘。
        @synchronized (swizzledClasses()) {
            if (![swizzledClasses() containsObject:className]) {
                // 未交換方法
                RACSwizzleForwardInvocation(baseClass);
                RACSwizzleRespondsToSelector(baseClass);
                RACSwizzleGetClass(baseClass, statedClass);
                RACSwizzleGetClass(object_getClass(baseClass), statedClass);
                RACSwizzleMethodSignatureForSelector(baseClass);
                [swizzledClasses() addObject:className];
            }
        }

        return baseClass;
    }
    // 未修改 isa 指針洽沟,創(chuàng)建名為  Class_RACSelectorSignal 的子類
    const char *subclassName = [className stringByAppendingString:RACSubclassSuffix].UTF8String;
    Class subclass = objc_getClass(subclassName);

    if (subclass == nil) {
        subclass = objc_allocateClassPair(baseClass, subclassName, 0);
        if (subclass == nil) return nil;
        /**
        修改 Class_RACSelectorSignal -forwardInvocation: 方法實(shí)現(xiàn)
        rac_alias_selector 方法存在時(shí),執(zhí)行方法蜗细,subject sendNext:
        當(dāng)方法未找到時(shí)裆操,subject sendError:
        */
        RACSwizzleForwardInvocation(subclass);
        // 修改 Class_RACSelectorSignal -respondsToSelector: 方法實(shí)現(xiàn),獲取參數(shù) selector 方法炉媒,當(dāng)方法存在踪区,并且方法實(shí)現(xiàn)不是 _objc_msg
        /**
        修改 Class_RACSelectorSignal -respondsToSelector: 方法實(shí)現(xiàn)
        獲取參數(shù) selector方法,當(dāng)方法存在吊骤,且方法實(shí)現(xiàn)不是 _objc_msgForward
        獲取 Rac_alias_selector 對(duì)應(yīng)的關(guān)聯(lián)對(duì)象缎岗,不為空是,返回YES白粉。
        
        如果方法不存在传泊,或方法實(shí)現(xiàn)是 _objc_msgForward 時(shí)
        返回原方法實(shí)現(xiàn)鼠渺。
        */
        RACSwizzleRespondsToSelector(subclass);
        // 修改 Class_RACSelectorSignal 的 -class 方法實(shí)現(xiàn),返回 Class
        RACSwizzleGetClass(subclass, statedClass);
        // 修改 Class_RACSelectorSignal 的 +class 方法實(shí)現(xiàn)眷细,返回 Class
        RACSwizzleGetClass(object_getClass(subclass), statedClass);
        // 重寫 -methodSignatureForSelectorMethod: 實(shí)現(xiàn)拦盹,當(dāng)方法存在時(shí),返回方法簽名溪椎,方法不存在時(shí)普舆,調(diào)用 super -methodSignatureForSelectorMethod: 方法,也就是 Class 的方法校读。
        RACSwizzleMethodSignatureForSelector(subclass);
        // 注冊(cè)子類
        objc_registerClassPair(subclass);
    }
    // 修改 isa 指針
    object_setClass(self, subclass);
    objc_setAssociatedObject(self, RACSubclassAssociationKey, subclass, OBJC_ASSOCIATION_ASSIGN);
    return subclass;
}

// 修改-forwardInvocation: 實(shí)現(xiàn)
static void RACSwizzleForwardInvocation(Class class) {
    SEL forwardInvocationSEL = @selector(forwardInvocation:);
    Method forwardInvocationMethod = class_getInstanceMethod(class, forwardInvocationSEL);

    // Preserve any existing implementation of -forwardInvocation:.
    void (*originalForwardInvocation)(id, SEL, NSInvocation *) = NULL;
    if (forwardInvocationMethod != NULL) {
        originalForwardInvocation = (__typeof__(originalForwardInvocation))method_getImplementation(forwardInvocationMethod);
    }

    // Set up a new version of -forwardInvocation:.
    //
    // If the selector has been passed to -rac_signalForSelector:, invoke
    // the aliased method, and forward the arguments to any attached signals.
    //
    // If the selector has not been passed to -rac_signalForSelector:,
    // invoke any existing implementation of -forwardInvocation:. If there
    // was no existing implementation, throw an unrecognized selector
    // exception.
    id newForwardInvocation = ^(id self, NSInvocation *invocation) {
        BOOL matched = RACForwardInvocation(self, invocation);
        if (matched) return;

        if (originalForwardInvocation == NULL) {
            [self doesNotRecognizeSelector:invocation.selector];
        } else {
            originalForwardInvocation(self, forwardInvocationSEL, invocation);
        }
    };

    class_replaceMethod(class, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@");
}

// rac_alias_selector 是否能響應(yīng)
static BOOL RACForwardInvocation(id self, NSInvocation *invocation) {
    SEL aliasSelector = RACAliasForSelector(invocation.selector);
    RACSubject *subject = objc_getAssociatedObject(self, aliasSelector);

    Class class = object_getClass(invocation.target);
    BOOL respondsToAlias = [class instancesRespondToSelector:aliasSelector];
    // 別名方法的實(shí)現(xiàn)奔害,是原方法的實(shí)現(xiàn),因?yàn)橛锌赡鼙O(jiān)聽一個(gè)未實(shí)現(xiàn)的方法地熄,所以別名方法有可能無法響應(yīng)。所以這里判斷一下芯杀。
    if (respondsToAlias) {
        invocation.selector = aliasSelector;
        [invocation invoke];
    }

    if (subject == nil) return respondsToAlias;

    [subject sendNext:invocation.rac_argumentsTuple];
    return YES;
}

// 修改-respondsToSelector: 方法實(shí)現(xiàn)
static void RACSwizzleRespondsToSelector(Class class) {
    SEL respondsToSelectorSEL = @selector(respondsToSelector:);

    // Preserve existing implementation of -respondsToSelector:.
    Method respondsToSelectorMethod = class_getInstanceMethod(class, respondsToSelectorSEL);
    BOOL (*originalRespondsToSelector)(id, SEL, SEL) = (__typeof__(originalRespondsToSelector))method_getImplementation(respondsToSelectorMethod);

    // Set up a new version of -respondsToSelector: that returns YES for methods
    // added by -rac_signalForSelector:.
    //
    // If the selector has a method defined on the receiver's actual class, and
    // if that method's implementation is _objc_msgForward, then returns whether
    // the instance has a signal for the selector.
    // Otherwise, call the original -respondsToSelector:.
    id newRespondsToSelector = ^ BOOL (id self, SEL selector) {
        Method method = rac_getImmediateInstanceMethod(class, selector);

        if (method != NULL && method_getImplementation(method) == _objc_msgForward) {
            SEL aliasSelector = RACAliasForSelector(selector);
            if (objc_getAssociatedObject(self, aliasSelector) != nil) return YES;
        }

        return originalRespondsToSelector(self, respondsToSelectorSEL, selector);
    };

    class_replaceMethod(class, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), method_getTypeEncoding(respondsToSelectorMethod));
}

RAC 監(jiān)聽方法前后端考,類結(jié)構(gòu)圖

方法監(jiān)聽前后
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市揭厚,隨后出現(xiàn)的幾起案子却特,更是在濱河造成了極大的恐慌,老刑警劉巖筛圆,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裂明,死亡現(xiàn)場離奇詭異,居然都是意外死亡太援,警方通過查閱死者的電腦和手機(jī)闽晦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來提岔,“玉大人仙蛉,你說我怎么就攤上這事〖蠲桑” “怎么了荠瘪?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赛惩。 經(jīng)常有香客問我哀墓,道長,這世上最難降的妖魔是什么喷兼? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任篮绰,我火速辦了婚禮,結(jié)果婚禮上褒搔,老公的妹妹穿的比我還像新娘阶牍。我一直安慰自己喷面,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布走孽。 她就那樣靜靜地躺著惧辈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪磕瓷。 梳的紋絲不亂的頭發(fā)上盒齿,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音困食,去河邊找鬼边翁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛硕盹,可吹牛的內(nèi)容都是我干的符匾。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼瘩例,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼啊胶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起垛贤,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤焰坪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后聘惦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體某饰,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年善绎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了黔漂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡禀酱,死狀恐怖瘟仿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情比勉,我是刑警寧澤劳较,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站浩聋,受9級(jí)特大地震影響观蜗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜衣洁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一墓捻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦砖第、人聲如沸撤卢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽放吩。三九已至,卻和暖如春羽杰,著一層夾襖步出監(jiān)牢的瞬間渡紫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工考赛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惕澎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓颜骤,卻偏偏與公主長得像唧喉,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子忍抽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容