100行实现的iOS辅助类
知乎上看到一个问题《一百行以下有那些给力代码》,几乎所有回答都是用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来自:http://xiangwangfeng.com/2014/11/21/100%E8%A1%8C%E5%AE%9E%E7%8E%B0%E7%9A%84iOS%E8%BE%85%E5%8A%A9%E7%B1%BB/