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 {}</pre>
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</pre>
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
_observeObject = nil; }forKeyPath:_keyPath];
(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
{ switch (_type) {ofObject:(id)object change:(NSDictionary *)change context:(void *)context
} } @end</pre>來自: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/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;