100行实现的iOS辅助类

jopen 9年前

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