知乎上看到一个问题《一百行以下有那些给力代码》,几乎所有回答都是用100行实现的某个小功能,有趣归有趣,但对实际工作并没有什么帮助。翻了翻自己的M80Kit,里面也有些比较有趣且实用的辅助类都是一百行左右,可以拿出来秀下。
M80MulticastDelegate
一个从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 {}
M80TimerHolder
一个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
M80WeakProxy
顾名思义,这是个代理,解决的问题和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 :)];
M80Observer
用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