We first use by the creation of timer, lead to the intermediary mode, and then extend the introduction of Rxswift intermediary mode use.
First, let’s look at the following ways to create a timer:
1. Create a common timer and add it to the runloop
let timer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerFire), userInfo: nibName, repeats: true)
RunLoop.current.add(timer, forMode: .common)
Copy the code
2.CADisplayLink
let displayLink = CADisplayLink(target: self, selector: #selector(timerFire))
displayLink.preferredFramesPerSecond = 1
displayLink.add(to: RunLoop.current, forMode: .default)
Copy the code
3.GCDTimer
gcdTimer = DispatchSource.makeTimerSource() gcdTimer? .schedule(deadline:DispatchTime.now(), repeating: DispatchTimeInterval.seconds(1)) gcdTimer? .setEventHandler(handler: {print("hello GCD") }) gcdTimer? .resume()Copy the code
4. RxSwift timer
timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
_= observable? .subscribe(onNext:{(number)in
}).disposed(by: disposeBag)
Copy the code
Note: the creation mode of 1,2 must be added to the runloop. When runloopMode is set to default, the timing accuracy is affected by UI operations; when runloopMode is set to.common, the timing accuracy is not affected by UI operations.
When you create a timer using the first method, if you need self to hold the timer for any other operation, you get a circular reference because when you initialize, you pass self as target to the timer, and self holds the timer.
We will use the mediator pattern to circumvent this circular reference problem by creating an XQProxy class, as follows
class XQProxy: NSObject {
weak var target : NSObjectProtocol?
var sel : Selector?
var xqTimer : Timer?
override init() {
super.init()}func xq_scheduleTimer(timeInterval interval:TimeInterval ,target innerTagert:Any ,selector aselector:Selector , userInfo :Any? .repeat yesOrNo:Bool) {
self.target = innerTagert as? NSObjectProtocol
self.sel = aselector
self.xqTimer = Timer.init(timeInterval: interval, target: self, selector: aselector, userInfo: userInfo, repeats: yesOrNo)
RunLoop.current.add(self.xqTimer! , forMode: .common)guard self.target? .responds(to:self.sel)==true else {
return
}
let method = class_getInstanceMethod(self.classForCoder, #selector(xq_timerFire))
class_replaceMethod(self.classForCoder, self.sel! , method_getImplementation(method!) , method_getTypeEncoding(method!) )}@objc fileprivate func xq_timerFire (a) {
if self.target ! =nil {
self.target! .perform(self.sel)
}else{
self.xqTimer? .invalidate()self.xqTimer = nil}}}Copy the code
The external call code is as follows to enable the scheduled task
xqProxy.xq_scheduleTimer(timeInterval: 1, target: self, selector: #selector(timerFunc), userInfo: nil.repeat: true)
Copy the code
Self ->XQProxy->timer->XQProxy–/weak/–self When XQProxy executes xq_timerFire, it checks whether the corresponding target still exists. If not, it stops time and sets it to nil to break the mutual holding relationship between Time and proxy.
Conclusion: XQProxy is introduced as the intermediary. XQProxy uses weak to hold target, which does not affect the normal release of target. XQProxy holds the timer and caller and can control the call flow, thus achieving the purpose of mediation in the middle
Deallocating analysis for RxSwift
Let’s look at the process of calling DEALLocating in RxSwift
- External calling code
_ = rx.deallocating
.subscribe(onNext:{() in
print("To be released soon.")})Copy the code
- Deallocating implementation is as follows, to create a proxy, and invoke the proxy. MessageSent returns a sequence, so we first analyze the self. The registerMessageInterceptor (deallocSelector) to perform the operation,
Note: The reason we define deallocSelector is because we can’t write # Selector (dealloc) directly in ARC, so we can’t overwrite dealloc, so we need to generate the method as a string
private let deallocSelector = NSSelectorFromString("dealloc")
public var deallocating: Observable<()> {
return self.synchronized {
do {
let proxy: DeallocatingProxy = try self.registerMessageInterceptor(deallocSelector)
return proxy.messageSent.asObservable()
}
catch let e {
return Observable.error(e)
}
}
}
Copy the code
- RegisterMessageInterceptor method, the most important step is to perform the RX_ensure_observing, which hides the exchange operation method
fileprivate func registerMessageInterceptor<T: MessageInterceptorSubject>(_ selector: Selector) throws -> T {
var error: NSError?
let targetImplementation = RX_ensure_observing(self.base, selector, &error)
subject.targetImplementation = targetImplementation
return subject
}
Copy the code
- RX_ensure_observing is used to call the lock and ensure the security of the execution. The oc code is used because the runtime is used. See the implementation of self ensurePrepared: Target forObserving:selector error:error directly
IMP __nullable RX_ensure_observing(id __nonnull target, SEL __nonnull selector, NSErrorParam error) {
__block IMP targetImplementation = nil;
@synchronized(target) {
@synchronized([target class]) {
[[RXObjCRuntime instance] performLocked:^(RXObjCRuntime * __nonnull self) {
targetImplementation = [self ensurePrepared:target
forObserving:selector error:error]; }]; }}return targetImplementation;
}
Copy the code
- EnsurePrepared method is the core of the implementation is as follows, call [the self swizzleDeallocating: deallocSwizzingTarget error: error]
-(IMP __nullable)ensurePrepared:(id __nonnull)target forObserving:(SEL __nonnull)selector error:(NSErrorParam)error {
if (selector == deallocSelector) {
if(! [self swizzleDeallocating:deallocSwizzingTarget error:error]) {returnnil; }}}Copy the code
- Click on swizzleDeallocating and you’ll see the following macro definition, which is used to generate the corresponding method at compile time, making it more efficient
SWIZZLE_INFRASTRUCTURE_METHOD(
void,
swizzleDeallocating,
,
deallocSelector,
DEALLOCATING_BODY
)
Copy the code
- The macro definition converts method_name to -(BOOL)method_name:(Class __nonnull) Class parameters,
#define SWIZZLE_INFRASTRUCTURE_METHOD(return_value, method_name, parameters, method_selector, body, ...) \
SWIZZLE_METHOD(return_value, -(BOOL)method_name:(Class __nonnull)class parameters error:(NSErrorParam)error \
{ \
SEL selector = method_selector; , body, NO_BODY, __VA_ARGS__)
Copy the code
- The definition of SWIZZLE_METHOD is as follows. \ is removed for easier viewing. The essence of which is to generate two functions newImplementationGenerator and replacementImplementationGenerator, And call [the self ensureSwizzledSelector: selector ofClass: class newImplementationGenerator: newImplementationGenerator replacementImplementationGenerator:replacementImplementationGenerator error:error];
#define SWIZZLE_METHOD(return_value, method_prototype, body, invoked_body, ...)
method_prototype
__unused SEL rxSelector = RX_selector(selector);
IMP (^newImplementationGenerator)(void) = ^() {
__block IMP thisIMP = nil;
id newImplementation = ^return_value(__unsafe_unretained id self DECLARE_ARGUMENTS(__VA_ARGS__)) {
body(__VA_ARGS__)
struct objc_super superInfo = {
.receiver = self,
.super_class = class_getSuperclass(class)
};
return_value (*msgSend)(struct objc_super *, SEL DECLARE_ARGUMENTS(__VA_ARGS__))
= (__typeof__(msgSend))objc_msgSendSuper;
@try {
return msgSend(&superInfo, selector ARGUMENTS(__VA_ARGS__));
}
@finally { invoked_body(__VA_ARGS__) }
};
thisIMP = imp_implementationWithBlock(newImplementation);
return thisIMP;
};
IMP (^replacementImplementationGenerator)(IMP) = ^(IMP originalImplementation) {
__block return_value (*originalImplementationTyped)(__unsafe_unretained id, SEL DECLARE_ARGUMENTS(__VA_ARGS__) )
= (__typeof__(originalImplementationTyped))(originalImplementation);
__block IMP thisIMP = nil;
id implementationReplacement = ^return_value(__unsafe_unretained id self DECLARE_ARGUMENTS(__VA_ARGS__) ) {
body(__VA_ARGS__)
@try {
return originalImplementationTyped(self, selector ARGUMENTS(__VA_ARGS__));
}
@finally { invoked_body(__VA_ARGS__) }
};
thisIMP = imp_implementationWithBlock(implementationReplacement);
return thisIMP;
};
return [self ensureSwizzledSelector:selector
ofClass:class
newImplementationGenerator:newImplementationGenerator
replacementImplementationGenerator:replacementImplementationGenerator
error:error];
}
Copy the code
- In the newly generated method, the body(VA_ARGS) is called, which is defined by the DEALLOCATING_BODY macro, calling [Observer Deallocating];
#define DEALLOCATING_BODY(...) \
id<RXDeallocatingObserver> observer = objc_getAssociatedObject(self, rxSelector); \
if(observer ! = nil && observer.targetImplementation == thisIMP) { \ [observer deallocating]; The \}Copy the code
- In the ensureSwizzledSelector method, class_addMethod(class, selector, newImplementation, encoding) adds a new method implementation for the class, where class is the current class, Selector is deallocSelector, and newImplementation is the newImplementation that is defined, and it first calls [observer deallocating] in step 9.
-(BOOL)ensureSwizzledSelector:(SEL __nonnull)selector
ofClass:(Class __nonnull)class
newImplementationGenerator:(IMP(^)(void))newImplementationGenerator
replacementImplementationGenerator:(IMP (^)(IMP originalImplementation))replacementImplementationGenerator
error:(NSErrorParam)error {
IMP newImplementation = newImplementationGenerator();
if (class_addMethod(class, selector, newImplementation, encoding)) {
// new method added, job done
[self registerInterceptedSelector:selector implementation:newImplementation forClass:class];
return YES;
}
return YES;
}
Copy the code
- In Step 2, the protocol that the proxy object follows, DeallocatingProxy, defines DealLocating, calling self.messagesent.on (.next(())), which is the ReplaySubject object. According to the RxSwift core process, calling on (.next) is equivalent to calling the external subscribed onNext method
fileprivate final class DeallocatingProxy : MessageInterceptorSubject, RXDeallocatingObserver {
typealias Element = ()
let messageSent = ReplaySubject<()>.create(bufferSize: 1)
@objc func deallocating() {
self.messageSent.on(.next(()))
}
}
Copy the code
In conclusion, RxSwift uses the DeallocatingProxy class to create a ReplaySubject. The dealloc method of the original class is exchanged and updated. When called, the external subscription replaySubject.onNext is triggered, and then the object is released. DeallocatingProxy controls the release process to notify the external mechanism.