IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    100行实现的iOS辅助类

    Xiang Wangfeng发表于 2014-11-21 00:00:00
    love 0

    知乎上看到一个问题《一百行以下有那些给力代码》,几乎所有回答都是用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


沪ICP备19023445号-2号
友情链接