Objective C limits the frequency of function calls
- 2020-06-01 10:29:57
- OfStack
preface
Recently took a free time to look at 1 algorithm related, brush LeetCode. I really don't feel my head working. Think back to the interesting problem I dealt with some time ago -- limiting the frequency of GCD calls. It was later extended to restrict function calls. So let me summarize 1 by the way.
I wanted to write in detail, but I was too lazy. I only gave the basic ideas and core code here.
Train of thought
For the purpose of limiting the frequency of calls, it is easy to think of throttle, or current limiting. I first learned this basic term from network throttling. Simple to understand is: the data to be processed traffic processing, limit the frequency. If you're not sure, check out this article about throttle in iOS programming
There are roughly three types:
1. Within the specified time, the earliest data shall prevail.
2, 1 within the specified time, the final data shall prevail.
3. If the time is within 1 set time and there is new data, start the time again.
It is easy to compare the last time with the current time in a fixed time. What remains is how to cancel the data that has been generated before. There are two ideas here, one is to use the latest data to overwrite the previous data, and the other is to delete the old data directly and reconstruct the new data.
It may not seem clear, but let's use a practical example.
GCD Throttle
Ingredients required: dispatch_source_t, dispatch_queue_t, dispatch_source_set_timer
We can easily implement Throttle of GCD by dispatch_source_t. Thanks to GCD providing dispatch_source_cancel to cancel source.
The core idea is to delay the invocation of a task for a certain period of time. If a new task comes during this period, it will be canceled; if the time is up, it will execute the task. You need to create an object or an array to save the previous sourcer.
The code is simple:
dispatch_source_t source = scheduledSources[key];
// If it's there, cancel it, get rid of the middle one, call the latest one
if (source) {
dispatch_source_cancel(source);
}
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(source, dispatch_time(DISPATCH_TIME_NOW, threshold * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0);
dispatch_source_set_event_handler(source, ^{
block();
dispatch_source_cancel(source);
[scheduledSources removeObjectForKey:key];
});
dispatch_resume(source);
So that's the basic idea. If you want to write a more generic 1, you just parameterize what you need to change.
Regular message sending Throttle
If you want to resolve regular send messages to Throttle. This is a bit of a hassle. Because RuntTime does not directly provide a cancellation method execution.
Here are some ideas:
Since the GCD Throttle call is already implemented above, wrapping the normal method call in layer 1 in the GCD way is possible. Using Runtime message forwarding, it is forwarded to a custom method for deferred processing. You can see the logic
Add a new method fixed_selector to the class, corresponding to the implementation as
rule.selector
IMP.
Using the Objective-C runtime message forwarding mechanism, will
rule.selector
The corresponding IMP is changed to _objc_msgForward to trigger a call to the forwardInvocation: method.
Replace the implementation of forwardInvocation: with its own implementation of IMP and in the logic of its own implementation
invocation.selector
Set as fixed_selector. And limit the frequency of [invocation invoke] calls.
conclusion