知乎上看到一个问题《一百行以下有那些给力代码》,几乎所有回答都是用100行实现的某个小功能,有趣归有趣,但对实际工作并没有什么帮助。翻了翻自己的M80Kit,里面也有些比较有趣且实用的辅助类都是一百行左右,可以拿出来秀下。
一个从XMPP框架中抽离出来的类,提供同步的一对多delegate机制。
在很多场景下,往往多个对象会依赖于某个核心对象。使用M80MulticastDelegate就能够很好地进行解耦:不需要定义过多正式的protocol或者notification进行通信。巧妙地利用OC的消息转发机制,当发一个不认识的消息给当前MulticaseDelegate时,它就会自动进行转发:遍历已注册的delegate node,找到能够响应当前selector的node并执行。
@interface M80DelegateNode : NSObject
@property (nonatomic,weak) id nodeDelegate;
+ (M80DelegateNode *)node:(id)delegate;
@end
@implementation M80DelegateNode
+ (M80DelegateNode *)node:(id)delegate
{
M80DelegateNode *instance = [[M80DelegateNode alloc] init];
instance.nodeDelegate = delegate;
return instance;
}
@end
@interface M80MulticastDelegate ()
{
NSMutableArray *_delegateNodes;
}
@end
@implementation M80MulticastDelegate
- (id)init
{
if (self = [super init])
{
_delegateNodes = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc {}
- (void)addDelegate:(id)delegate
{
[self removeDelegate:delegate];
M80DelegateNode *node = [M80DelegateNode node:delegate];
[_delegateNodes addObject:node];
}
- (void)removeDelegate:(id)delegate
{
NSMutableIndexSet *indexs = [NSMutableIndexSet indexSet];
for (NSUInteger i = 0; i < [_delegateNodes count]; i ++)
{
M80DelegateNode *node = [_delegateNodes objectAtIndex:i];
if (node.nodeDelegate == delegate)
{
[indexs addIndex:i];
}
}
if ([indexs count])
{
[_delegateNodes removeObjectsAtIndexes:indexs];
}
}
- (void)removeAllDelegates
{
[_delegateNodes removeAllObjects];
}
- (NSUInteger)count
{
return [_delegateNodes count];
}
- (NSUInteger)countForSelector:(SEL)aSelector
{
NSUInteger count = 0;
for (M80DelegateNode *node in _delegateNodes)
{
if ([node.nodeDelegate respondsToSelector:aSelector])
{
count++;
}
}
return count;
}
- (BOOL)hasDelegateThatRespondsToSelector:(SEL)aSelector
{
BOOL hasSelector = NO;
for (M80DelegateNode *node in _delegateNodes)
{
if ([node.nodeDelegate respondsToSelector:aSelector])
{
hasSelector = YES;
break;
}
}
return hasSelector;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
for (M80DelegateNode *node in _delegateNodes)
{
NSMethodSignature *method = [node.nodeDelegate methodSignatureForSelector:aSelector];
if (method)
{
return method;
}
}
//如果发现没有可以响应当前方法的Node,就返回一个空方法
//否则会引起崩溃
return [[self class] instanceMethodSignatureForSelector:@selector(doNothing)];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL selector = [invocation selector];
BOOL hasNilDelegate = NO;
for (M80DelegateNode *node in _delegateNodes)
{
id nodeDelegate = node.nodeDelegate;
if (nodeDelegate == nil)
{
hasNilDelegate = YES;
}
else if ([nodeDelegate respondsToSelector:selector])
{
[invocation invokeWithTarget:nodeDelegate];
}
}
if (hasNilDelegate)
{
[self removeDelegate:nil];
}
}
- (void)doesNotRecognizeSelector:(SEL)aSelector {}
- (void)doNothing {}
一个NSTimer的Wrapper,用于NSTimer的管理。iOS上NSTimer的最大坑是它会retain当前target,这样就导致了target的延迟释放甚至无法释放(repeat为YES的NSTimer和target形成retain-cycle又没有合理时机进行invalidate)。
一种通用的解决方式是用Block来解除retain-cycle,但需要在block中将target设为weak,这是这种方式比较容易出错的地方。
而M80TimerHolder则采用稍微有点耦合但更安全的方式:真正的Target(通常是VC)和NSTimer持有M80TimerHolder,后者通过weak delegate和VC进行通信。对于非repeat的Timer,没有即使不做任何处理都不会有延迟处理和retain-cycle的问题。而对于repeat的Timer只需要在VC的dealloc方法调用M80TimerHolder的stopTimer方法即可。就算不调用,形成retain-cycle的也仅仅是Timer和M80TimerHolder,并不会对APP后续执行造成任何影响,只是多了个空跑的Timer和泄露的M80TimerHolder而已。
具体实现如下:
@interface M80TimerHolder ()
{
NSTimer *_timer;
BOOL _repeats;
}
- (void)onTimer: (NSTimer *)timer;
@end
@implementation M80TimerHolder
- (void)dealloc
{
[self stopTimer];
}
- (void)startTimer: (NSTimeInterval)seconds
delegate: (id<M80TimerHolderDelegate>)delegate
repeats: (BOOL)repeats
{
_timerDelegate = delegate;
_repeats = repeats;
if (_timer)
{
[_timer invalidate];
_timer = nil;
}
_timer = [NSTimer scheduledTimerWithTimeInterval:seconds
target:self
selector:@selector(onTimer:)
userInfo:nil
repeats:repeats];
}
- (void)stopTimer
{
[_timer invalidate];
_timer = nil;
_timerDelegate = nil;
}
- (void)onTimer: (NSTimer *)timer
{
if (!_repeats)
{
_timer = nil;
}
if (_timerDelegate && [_timerDelegate respondsToSelector:@selector(onM80TimerFired:)])
{
[_timerDelegate onM80TimerFired:self];
}
}
@end
顾名思义,这是个代理,解决的问题和M80TimeHolder一样:使得NSTimer不持有target以免形成retain-cycle。原理很简单:NSTimer持有M80WeakProxy,而M80WeakProxy只持有真正target的虚引用,并通过OC的消息转发机制调用target的onTimer方法。(Fork from FLAnimatedImage)
具体实现如下
@interface M80WeakProxy : NSObject
+ (instancetype)weakProxyForObject:(id)object;
@end
@interface M80WeakProxy ()
@property (nonatomic,weak) id target;
@end
@implementation M80WeakProxy
+ (instancetype)weakProxyForObject:(id)object
{
M80WeakProxy *instance = [[M80WeakProxy alloc] init];
instance.target = object;
return instance;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
void *nullPointer = NULL;
[invocation setReturnValue:&nullPointer];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
@end
而真正调用时候只需要这样:
M80WeakProxy *weakProxy = [M80WeakProxy weakProxyForObject:self];
self.displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayDidRefresh:)];
用Block实现KVO,规避KVO register和unregister没有配对调用的问题。同样不超过150行,采用的原理和C++中实现自动锁之类的方式相近,在类初始化时register KVO,析构时unregister KVO以形成配对的调用。
typedef NS_ENUM(NSUInteger, M80ObserveType) {
M80ObserveTypeNone,
M80ObserveTypeOldAndNew,
M80ObserveTypeChange,
};
@interface M80Observer ()
@property (nonatomic,weak) id observeObject;
@property (nonatomic,strong) NSString *keyPath;
@property (nonatomic,copy) id block;
@property (nonatomic,assign) M80ObserveType type;
@end
@implementation M80Observer
- (void)dealloc
{
_block = nil;
[_observeObject removeObserver:self
forKeyPath:_keyPath];
_observeObject = nil;
}
- (instancetype)initWithObject:(id)object
keyPath:(NSString *)keyPath
block:(id)block
options:(NSKeyValueObservingOptions)options
type:(M80ObserveType)type
{
if (self = [super init])
{
_observeObject = object;
_keyPath = keyPath;
_block = [block copy];
_type = type;
[object addObserver:self
forKeyPath:keyPath
options:options
context:NULL];
}
return self;
}
+ (instancetype)observer:(id)object
keyPath:(NSString *)keyPath
block:(M80ObserverBlock)block
{
return [[M80Observer alloc]initWithObject:object
keyPath:keyPath
block:block
options:0
type:M80ObserveTypeNone];
}
+ (instancetype)observer:(id)object
keyPath:(NSString *)keyPath
oldAndNewBlock:(M80ObserverBlockWithOldAndNew)block
{
return [[M80Observer alloc]initWithObject:object
keyPath:keyPath
block:block
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
type:M80ObserveTypeOldAndNew];
}
+ (instancetype)observer:(id)object keyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
changeBlock:(M80ObserverBlockWithChangeDictionary)block
{
return [[M80Observer alloc]initWithObject:object
keyPath:keyPath
block:block
options:options
type:M80ObserveTypeChange];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
switch (_type)
{
case M80ObserveTypeNone:
if (_block)
{
((M80ObserverBlock)_block)();
}
break;
case M80ObserveTypeOldAndNew:
if (_block)
{
((M80ObserverBlockWithOldAndNew)_block)(change[NSKeyValueChangeOldKey],change[NSKeyValueChangeNewKey]);
}
break;
case M80ObserveTypeChange:
if (_block)
{
((M80ObserverBlockWithChangeDictionary)_block)(change);
}
break;
default:
break;
}
}
@end