Skip to content

Commit

Permalink
1.1.0
Browse files Browse the repository at this point in the history
`MTRule` will discard itself automatically when the `target` is
deallocated.
  • Loading branch information
yulingtianxia committed Dec 11, 2017
1 parent fb95972 commit 77b0ec5
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 78 deletions.
53 changes: 4 additions & 49 deletions MTDemo/MTDemo/ViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,72 +27,27 @@ - (void)viewDidLoad {

self.stub = [Stub new];

// MTRule *rule = [MTRule new];
// rule.target = self.stub;
// rule.selector = @selector(foo:);
// rule.durationThreshold = 1;
// rule.mode = MTPerformModeDebounce;
// MTRule *rule = [[MTRule alloc] initWithTarget:self.stub selector:@selector(foo:) durationThreshold:1];
// rule.messageQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//
// [MTEngine.defaultEngine applyRule:rule];

// 跟上面的用法等价
[self.stub mt_limitSelector:@selector(foo:) oncePerDuration:0.5 usingMode:MTPerformModeDebounce onMessageQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
NSArray<MTRule *> *rules = self.stub.mt_allRules;
self.stub = nil;

for (MTRule *rule in rules) {
NSLog(@"%@", rule);
}
// MTRule *rule1 = [MTRule new];
// rule1.target = Stub.class;
// rule1.selector = @selector(foo:);
// rule1.durationThreshold = 2;
// rule1.mode = MTPerformModeDebounce;
// rule1.messageQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);



// [MTEngine.defaultEngine applyRule:rule1];
//
// [MTEngine.defaultEngine discardRule:rule];
// [MTEngine.defaultEngine discardRule:rule];


// Stub *ss = [Stub new];

// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// while (YES) {
// @autoreleasepool {
// [ss foo:[NSDate date]];
// }
// }
// });

// Test Case For MTPerformModeDebounce
// __block NSTimeInterval lastTime = 0;
// __block NSTimeInterval value = 1;
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// while (YES) {
// @autoreleasepool {
// NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
//
// if (now - lastTime > value && (now - lastTime <= 3 || lastTime == 0)) {
// lastTime = now;
// value += 0.1;
// NSLog(@"message send value:%f", value);
// [s foo:[NSDate date]];
// }
// if (lastTime > 0 && now - lastTime > 3) {
// [s foo:[NSDate date]];
// [MTEngine.defaultEngine discardRule:rule];
// }
// }
// }
// });

}

- (IBAction)tapFoo:(UIButton *)sender {
[self.stub foo:[NSDate date]];
MTEngine.defaultEngine.allRules;
}

- (void)didReceiveMemoryWarning {
Expand Down
2 changes: 1 addition & 1 deletion MessageThrottle.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "MessageThrottle"
s.version = "1.0.6"
s.version = "1.1.0"
s.summary = "A lightweight Objective-C message throttle and debounce library."
s.description = <<-DESC
MessageThrottle is a lightweight, simple library for controlling frequency of forwarding Objective-C messages. You can choose to control existing methods per instance or per class. It's an implementation of function throttle/debounce developed with Objective-C runtime.
Expand Down
6 changes: 3 additions & 3 deletions MessageThrottle/MessageThrottle.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ Class mt_metaClass(Class cls);
/**
target, 可以为实例,类,元类(可以使用 mt_metaClass 函数获取元类)
*/
@property (nonatomic, weak) id target;
@property (nonatomic, weak, readonly) id target;

/**
节流消息的 SEL
*/
@property (nonatomic) SEL selector;
@property (nonatomic, readonly) SEL selector;

/**
消息节流时间的阈值,单位:秒
Expand All @@ -63,7 +63,7 @@ Class mt_metaClass(Class cls);

- (instancetype)initWithTarget:(id)target selector:(SEL)selector durationThreshold:(NSTimeInterval)durationThreshold NS_DESIGNATED_INITIALIZER;

- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;

/**
应用规则,会覆盖已有的规则
Expand Down
99 changes: 74 additions & 25 deletions MessageThrottle/MessageThrottle.m
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ Class mt_metaClass(Class cls)
}
}

@interface MTDealloc : NSObject

@property (nonatomic, weak) MTRule *rule;
@property (nonatomic, copy) NSString *methodDescription;
@property (nonatomic) Class cls;

@end

@implementation MTDealloc

- (void)dealloc
{
SEL selector = NSSelectorFromString(@"discardRule:whenTargetDealloc:");
((void (*)(id, SEL, MTRule *, MTDealloc *))[MTEngine.defaultEngine methodForSelector:selector])(MTEngine.defaultEngine, selector, self.rule, self);
}

@end

@interface MTRule ()

@property (nonatomic) NSTimeInterval lastTimeRequest;
Expand All @@ -68,17 +86,6 @@ - (instancetype)initWithTarget:(id)target selector:(SEL)selector durationThresho
return self;
}

- (instancetype)init
{
self = [super init];
if (self) {
_mode = MTPerformModeDebounce;
_lastTimeRequest = 0;
_messageQueue = dispatch_get_main_queue();
}
return self;
}

- (BOOL)apply
{
return [MTEngine.defaultEngine applyRule:self];
Expand All @@ -95,6 +102,8 @@ @interface MTEngine ()

@property (nonatomic) NSMutableDictionary<NSString *, MTRule *> *rules;

- (void)discardRule:(MTRule *)rule whenTargetDealloc:(MTDealloc *)mtDealloc;

@end

@implementation MTEngine
Expand Down Expand Up @@ -132,7 +141,7 @@ - (BOOL)applyRule:(MTRule *)rule
__block BOOL shouldApply = YES;
if (mt_checkRuleValid(rule)) {
[self.rules enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, MTRule * _Nonnull obj, BOOL * _Nonnull stop) {
if (rule.selector == obj.selector
if (sel_isEqual(rule.selector, obj.selector)
&& mt_object_isClass(rule.target)
&& mt_object_isClass(obj.target)) {
Class clsA = rule.target;
Expand All @@ -146,6 +155,7 @@ - (BOOL)applyRule:(MTRule *)rule
if (shouldApply) {
self.rules[mt_methodDescription(rule.target, rule.selector)] = rule;
mt_overrideMethod(rule.target, rule.selector);
mt_discardRuleWhenTargetDealloc(rule);
}
}
else {
Expand All @@ -171,6 +181,21 @@ - (BOOL)discardRule:(MTRule *)rule
return shouldDiscard;
}

- (void)discardRule:(MTRule *)rule whenTargetDealloc:(MTDealloc *)mtDealloc
{
pthread_mutex_lock(&mutex);

NSString *description = mtDealloc.methodDescription;
if (self.rules[description] != nil) {
self.rules[description] = nil;
if (MTEngine.defaultEngine.rules[mt_methodDescription(mtDealloc.cls, rule.selector)]) {
return;
}
mt_revertHook(mtDealloc.cls, rule.selector);
}
pthread_mutex_unlock(&mutex);
}

#pragma mark - Private Helper

static BOOL mt_checkRuleValid(MTRule *rule)
Expand Down Expand Up @@ -334,19 +359,8 @@ static void mt_overrideMethod(id target, SEL selector)
class_replaceMethod(cls, selector, msgForwardIMP, originType);
}

static void mt_recoverMethod(id target, SEL selector)
static void mt_revertHook(Class cls, SEL selector)
{
Class cls;
if (mt_object_isClass(target)) {
cls = target;
}
else {
cls = object_getClass(target);
if (MTEngine.defaultEngine.rules[mt_methodDescription(cls, selector)]) {
return;
}
}

if (class_getMethodImplementation(cls, @selector(forwardInvocation:)) == (IMP)mt_forwardInvocation) {
IMP originalForwardImp = class_getMethodImplementation(cls, NSSelectorFromString(MTForwardInvocationSelectorName));
if (originalForwardImp) {
Expand All @@ -363,14 +377,29 @@ static void mt_recoverMethod(id target, SEL selector)
return;
}
const char *originType = (char *)method_getTypeEncoding(originMethod);

SEL fixedOriginalSelector = mt_aliasForSelector(cls, selector);
if (class_respondsToSelector(cls, fixedOriginalSelector)) {
IMP originalImp = class_getMethodImplementation(cls, fixedOriginalSelector);
class_replaceMethod(cls, selector, originalImp, originType);
}
}

static void mt_recoverMethod(id target, SEL selector)
{
Class cls;
if (mt_object_isClass(target)) {
cls = target;
}
else {
cls = object_getClass(target);
if (MTEngine.defaultEngine.rules[mt_methodDescription(cls, selector)]) {
return;
}
}
mt_revertHook(cls, selector);
}

static void mt_executeOrigForwardInvocation(id slf, SEL selector, NSInvocation *invocation)
{
SEL origForwardSelector = NSSelectorFromString(MTForwardInvocationSelectorName);
Expand All @@ -395,6 +424,26 @@ static void mt_executeOrigForwardInvocation(id slf, SEL selector, NSInvocation *
}
}

const void *kMTDeallocKey = &kMTDeallocKey;

static void mt_discardRuleWhenTargetDealloc(MTRule *rule)
{
if (mt_object_isClass(rule.target)) {
return;
}
else {
Class cls = object_getClass(rule.target);
MTDealloc *mtDealloc = objc_getAssociatedObject(rule.target, kMTDeallocKey);
if (!mtDealloc) {
mtDealloc = [MTDealloc new];
mtDealloc.rule = rule;
mtDealloc.methodDescription = mt_methodDescription(rule.target, rule.selector);
mtDealloc.cls = cls;
objc_setAssociatedObject(rule.target, kMTDeallocKey, mtDealloc, OBJC_ASSOCIATION_RETAIN);
}
}
}

@end

@implementation NSObject (MessageThrottle)
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ You should call `discard` method When you don't need limit `foo:` method.
[rule discard];
```

**NOTE: `MTRule` is self-managed. If the `target` of rule is a object instance, `MTRule` will discard itself automatically when the `target` is deallocated.**

`MTRule` represents the rule of a message throttle, which contains strategy and frequency of sending messages.

You can assign an instance or (meta)class to `target` property. When you assign an instance to `target`, MessageThrottle will only restrict messages send to this instance. If you want to restrict a class method, just using `mt_metaClass()` to get it's meta class, and assign the meta class to `target`. Rules with instance `target` won't conflict with each other, and have a higher priority than rules with class `target`.
Expand Down

0 comments on commit 77b0ec5

Please sign in to comment.