DZNEmptyDataSetの詳細を知らないかもしれません

23070 ワード

前言:
このフレームワークについて、ソースコードを分析するのは、UItableViewおよびUIcollectionViewにデータが存在するかどうかを自動的に検出し、リフレッシュインタフェースに応答し、システム方法とフレームワークのパッケージと処理技術を両立させる方法を知りたいだけです.これらの問題を持ってソースコードを見てみましょう.
フレームワークについて
  • Githubソースアドレス:https://github.com/dzenbot/DZNEmptyDataSet
  • バージョン:1.8.1
  • このリンクをクリックして、このフレームワーク
  • の使用方法を確認します.
    ファイルディレクトリ
  • UIScrollView+EmptyDataSet.h
  • UIScrollView+EmptyDataSet.m

  • 下のヘッダファイルをざっと見ると、コアの部分は主にDZNEmptyDataSetSourceとDZNEmptyDataSetDelegateの2つのプロトコルを実現していることがわかります.両方のプロトコルのプロトコルメソッドは@optionalタイプです.
    @interface UIScrollView (EmptyDataSet)
    
    @property (nonatomic, weak) IBOutlet id  emptyDataSetSource;
    @property (nonatomic, weak) IBOutlet id  emptyDataSetDelegate;
    /** YES if any empty dataset is visible. */
    @property (nonatomic, readonly, getter = isEmptyDataSetVisible) BOOL emptyDataSetVisible;
    
    /**
     *  UITableView  UICollectionView [-reloadData]         。
     *                     。
     */
    - (void)reloadEmptyDataSet;
    @end
    

    DZNEmptyDataSetSource
  • このプロトコルは、主にデータソースが空の場合の空白インタフェース要素の設定に作用する.
  • には、Title、description、image、imageTintColor、imageAnimation、buttonTitle、buttonImageなどの属性の設定が含まれる.
  • このプロトコルは、ユーザーがニーズに応じて適切なスタイルを設定するのに便利であるとともに、**カスタムインタフェースのインタフェースも提供します.
  • /**
     *                Title.
     *         ,      (NSAttributedString *)  。
     */
    - (NSAttributedString *)titleForEmptyDataSet:(UIScrollView *)scrollView;
    
    /**
     *                description  。
     *         ,      (NSAttributedString *)  。
     */
    - (NSAttributedString *)descriptionForEmptyDataSet:(UIScrollView *)scrollView;
    
    /**
     *              。
     */
    - (UIImage *)imageForEmptyDataSet:(UIScrollView *)scrollView;
    
    /**
     *                 ,   nil.
     */
    - (UIColor *)imageTintColorForEmptyDataSet:(UIScrollView *)scrollView;
    
    /**
     *                。
     */
    - (CAAnimation *) imageAnimationForEmptyDataSet:(UIScrollView *) scrollView;
    
    /**
     *                ,         "    "   。
     *              ,       。
     *    UIControlState    。         。
     */
    - (NSAttributedString *)buttonTitleForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state;
    
    /**
     *                。
     *    UIControlState    。         。
     */
    - (UIImage *)buttonImageForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state;
    
    /**
     *                  。     。
     *    UIControlState    。         。
     */
    - (UIImage *)buttonBackgroundImageForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state;
    
    /**
     *              。   [UIColor clearColor]
     */
    - (UIColor *)backgroundColorForEmptyDataSet:(UIScrollView *)scrollView;
    
    /**
     *               View, View        ,             。
     *    UIControlState    。         。
     *        ,           。
     * -offsetForEmptyDataSet   -spaceHeightForEmptyDataSet
     */
    - (UIView *)customViewForEmptyDataSet:(UIScrollView *)scrollView;
    
    /**
     *                  ,    CGPointZero
     */
    - (CGPoint)offsetForEmptyDataSet:(UIScrollView *)scrollView DEPRECATED_MSG_ATTRIBUTE("Use -verticalOffsetForEmptyDataSet:");
    - (CGFloat)verticalOffsetForEmptyDataSet:(UIScrollView *)scrollView;
    
    /**
     *            ,   11px。
     */
    - (CGFloat)spaceHeightForEmptyDataSet:(UIScrollView *)scrollView;
    

    冗談を言って、私はこのフレームワークが持っているスタイルを使ったことがありません.多くの場合、私たちのニーズはカスタマイズを主としています.もちろん、これは私たちがソースコードを読むことに影響しません.このフレームワークの優秀なところを楽しく解読するのもいいです.
    DZNEmptyDataSetDelegate
  • プロトコルは、主に空白のインタフェースを処理するエージェントに作用する.エージェントのレスポンスコールバックを取得します.
  • /**
     *          EmptyDataSetView         ,   YES。
     */
    - (BOOL)emptyDataSetShouldFadeIn:(UIScrollView *)scrollView;
    
    /**
     *          EmptyDataSetView        。   YES。
     */
    - (BOOL)emptyDataSetShouldDisplay:(UIScrollView *)scrollView;
    
    /**
     *                     ,   YES。
     */
    - (BOOL)emptyDataSetShouldAllowTouch:(UIScrollView *)scrollView;
    
    /**
     *                 ,   NO。
     */
    - (BOOL)emptyDataSetShouldAllowScroll:(UIScrollView *)scrollView;
    
    /**
     *                       ,   NO。
     */
    - (BOOL)emptyDataSetShouldAnimateImageView:(UIScrollView *)scrollView;
    
    /**
     *          emptyDataSetView   
     *         textfield  searchBar   resignFirstResponder  。
     */
    - (void)emptyDataSetDidTapView:(UIScrollView *)scrollView DEPRECATED_MSG_ATTRIBUTE("Use emptyDataSet:didTapView:");
    
    /**
     *          ,           
     * @param scrollView               。
     */
    - (void)emptyDataSetDidTapButton:(UIScrollView *)scrollView DEPRECATED_MSG_ATTRIBUTE("Use emptyDataSet:didTapButton:");
    
    /**
     *          empty dataset view     。
     *         textfield  searchBar   resignFirstResponder  。
     */
    - (void)emptyDataSet:(UIScrollView *)scrollView didTapView:(UIView *)view;
    
    /**
     *          ,           
     */
    - (void)emptyDataSet:(UIScrollView *)scrollView didTapButton:(UIButton *)button;
    
    /**
     *          ,emptyDataView      。
     */
    - (void)emptyDataSetWillAppear:(UIScrollView *)scrollView;
    
    /**
     *          ,emptyDataView      。
     */
    - (void)emptyDataSetDidAppear:(UIScrollView *)scrollView;
    
    /**
     *          ,emptyDataView      。
     */
    - (void)emptyDataSetWillDisappear:(UIScrollView *)scrollView;
    
    /**
     *          ,emptyDataView      。
     */
    - (void)emptyDataSetDidDisappear:(UIScrollView *)scrollView;
    

    これらのエージェントメソッドはすべてemptyDataSetをメソッド接頭辞として使用していることに気づいたかどうか分かりませんが、UItable Viewを書くエージェントメソッドは非常に頻繁であると信じています.では、このようなプログラミング仕様にも気づくことができます.このようなメリットは、自動補完ヒントの機能を利用して、私たちが望んでいる方法を迅速にインデックスできることです.これらの細部には咀嚼のセンスがたくさんあり、より規範的なコードを書くために、私たちの高度な重視を引き起こさなければなりません.
    ここを見て、プロトコル方法をどのように実現して目的を実現するかはもう大きな問題ではありませんが、これはまだ十分ではありません.開けるとmファイルは、インタフェースの方法が氷山の一角にすぎないと感じ、実現ファイルにもっと大きな宝物が隠されていて、細かく味わい続けています.
    @interface UIView (DZNConstraintBasedLayoutExtensions)
    - (NSLayoutConstraint *)equallyRelatedConstraintWithView:(UIView *)view attribute:(NSLayoutAttribute)attribute;
    @end
    
    @interface DZNEmptyDataSetView : UIView
    //...
    @end
    
    #pragma mark - UIScrollView+EmptyDataSet
    static char const * const kEmptyDataSetSource =     "emptyDataSetSource";
    static char const * const kEmptyDataSetDelegate =   "emptyDataSetDelegate";
    static char const * const kEmptyDataSetView =       "emptyDataSetView";
    
    #define kEmptyImageViewAnimationKey @"com.dzn.emptyDataSet.imageViewAnimation"
    
    @interface UIScrollView () 
    @property (nonatomic, readonly) DZNEmptyDataSetView *emptyDataSetView;
    @end
    

    実装ファイルには、主に以上の3つのクラスが含まれています.最初の2つのクラスを「自動無視」してください.重要ではありません.主な機能は、フレームワークのインタフェース要素とレイアウト制約を設定することです.コードも理解しやすく、自分でフレームワークのソースコードを開いて表示すると、言うまでもなく、UIscrollView+EmptyDataSetという分類の実現を重点的に記録して紹介します.
    UIScrollView+EmptyDataSet
    まず、この分類のコードモジュールを参照します.ソースコードを読むときは、大きな方向から着手し、コードブロックが主にどのモジュールを含んでいるかを見て、さらに一つ一つ突破しなければならない.言い換えれば、まず入り口を見つけてから、ゆっくり探索!
    #pragma mark - Getters (Public)
    #pragma mark - Getters (Private)
    #pragma mark - Data Source Getters
    #pragma mark - Delegate Getters & Events (Private)
    #pragma mark - Setters (Public)
    #pragma mark - Setters (Private)
    #pragma mark - Reload APIs (Public)
    #pragma mark - Reload APIs (Private)
    #pragma mark - Method Swizzling
    #pragma mark - UIGestureRecognizerDelegate Methods
    

    #pragma mark - Getters (Public)
    - (id)emptyDataSetSource {
        return objc_getAssociatedObject(self, kEmptyDataSetSource);
    }
    
    - (id)emptyDataSetDelegate {
        return objc_getAssociatedObject(self, kEmptyDataSetDelegate);
    }
    
    - (BOOL)isEmptyDataSetVisible {
        UIView *view = objc_getAssociatedObject(self, kEmptyDataSetView);
        return view ? !view.hidden : NO;
    }
    
  • このモジュールは、主にruntimeによって属性設定DZNEmptyDataSetSource、DZNEmptyDataSetDelegate、isEmptyDataSetVisibleの属性getterメソッドを取得する.
  • isEmptyDataSetVisibleプロパティは、現在のEmptyDataSetViewが表示されているかどうかを判断するために主に使用されます.ここでの!view.hiddenはYESを返し、デフォルト初期化EmptyDataSetViewのhiddenはYESであり、デフォルトでは表示されません.後のsetterメソッドで見ることができます.

  • #pragma mark - Setters (Public)
    - (void)setEmptyDataSetSource:(id)datasource {
        if (!datasource || ![self dzn_canDisplay]) {
            [self dzn_invalidate];
        }
        
        objc_setAssociatedObject(self, kEmptyDataSetSource, datasource, OBJC_ASSOCIATION_ASSIGN);
        
        //     runtime     -reloadData        -dzn_reloadData  。
        [self swizzleIfPossible:@selector(reloadData)];
        
        //         UITableView,        -dzn_reloadData -endUpdates   。
        if ([self isKindOfClass:[UITableView class]]) {
            [self swizzleIfPossible:@selector(endUpdates)];
        }
    }
    
    - (void)setEmptyDataSetDelegate:(id)delegate {
        if (!delegate) {
            [self dzn_invalidate];
        }
        objc_setAssociatedObject(self, kEmptyDataSetDelegate, delegate, OBJC_ASSOCIATION_ASSIGN);
    }
    
  • の2つのSetterメソッドは、主にruntimeによって2つのエージェント属性を設定します.
  • [self dzn_invalidate]は、ビューを除去する方法である.
  • [self dzn_canDisplay]は、親ビューがUITableView、UIcollectionView、およびUIscrollViewであるかどうかを判断するものである.
  • メソッドが交換をどのように置き換えるかについては、以下で分析する.

  • #pragma mark - Getters (Private)
    - (DZNEmptyDataSetView *)emptyDataSetView {
        DZNEmptyDataSetView *view = objc_getAssociatedObject(self, kEmptyDataSetView);
        if (!view) {
            view = [DZNEmptyDataSetView new];
            //...
            [self setEmptyDataSetView:view];
        }
        return view;
    }
    
    - (BOOL)dzn_canDisplay {
        if (self.emptyDataSetSource && [self.emptyDataSetSource conformsToProtocol:@protocol(DZNEmptyDataSetSource)]) {
            if ([self isKindOfClass:[UITableView class]] || [self isKindOfClass:[UICollectionView class]] || [self isKindOfClass:[UIScrollView class]]) {
                return YES;
            }
        }
        return NO;
    }
    
    - (NSInteger)dzn_itemsCount {
        NSInteger items = 0;
        
        // UIScollView      'dataSource'   ,       
        if (![self respondsToSelector:@selector(dataSource)]) {
            return items;
        }
        // UITableView support
        if ([self isKindOfClass:[UITableView class]]) {
            UITableView *tableView = (UITableView *)self;
            id  dataSource = tableView.dataSource;
            NSInteger sections = 1;        
            if (dataSource && [dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
                sections = [dataSource numberOfSectionsInTableView:tableView];
            }
            if (dataSource && [dataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)]) {
                for (NSInteger section = 0; section < sections; section++) {
                    items += [dataSource tableView:tableView numberOfRowsInSection:section];
                }
            }
        }
        // UICollectionView support
        else if ([self isKindOfClass:[UICollectionView class]]) {
            //...   UITableView     。
        }
        return items;
    }
    
  • (DZNEmptyDataSetView *)emptyDataSetViewはruntimeによってmptyDataSetViewを初期化し、いくつかのデフォルトの属性パラメータ
  • を設定した.
  • (BOOL)dzn_canDisplayは、現在のビューが空白のページをロードすることができるかどうかを判断する、selfがエージェントを実装した場合のみ、UITAbleView、UICOLLectionVIew、UIscrollViewである.
  • (NSInteger)dzn_itemsCount selfのdataSourceの要素個数を統計し、- numberOfSections- numberOfItemsInSectionの方法で統計する.

  • #pragma mark - Setters (Private)
    - (void)setEmptyDataSetView:(DZNEmptyDataSetView *)view{
        objc_setAssociatedObject(self, kEmptyDataSetView, view, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
  • runtimeでプライベートビューを設定します.

  • #pragma mark - Data Source Getters
    - (NSAttributedString *)dzn_titleLabelString;
    
    - (NSAttributedString *)dzn_detailLabelString;
    
    - (UIImage *)dzn_image; 
    
    - (CAAnimation *)dzn_imageAnimation;
    
    - (UIColor *)dzn_imageTintColor;
    
    - (NSAttributedString *)dzn_buttonTitleForState:(UIControlState)state;
    
    - (UIImage *)dzn_buttonImageForState:(UIControlState)state;
    
    - (UIImage *)dzn_buttonBackgroundImageForState:(UIControlState)state;
    
    - (UIColor *)dzn_dataSetBackgroundColor;
    
    - (CGFloat)dzn_verticalOffset;
    
    - (CGFloat)dzn_verticalSpace;
    
    - (UIView *)dzn_customView {
        if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(customViewForEmptyDataSet:)]) {
            UIView *view = [self.emptyDataSetSource customViewForEmptyDataSet:self];
            if (view) NSAssert([view isKindOfClass:[UIView class]], @"You must return a valid UIView object for -customViewForEmptyDataSet:");
            if (!self.isNotFirst) {
                self.isNotFirst = YES;
                return nil;
            }
            return view;
        }
        return nil;
    }
    
  • このモジュールのメソッドは、主にDataSourceのプロキシメソッドによって対応するシステムのデフォルトのViewのプロパティを設定します.
  • - dzn_customViewは、カスタムビューを設定するために使用され、最も一般的な方法でもあります.同様に、プロトコルメソッドによって対応するカスタムビューを取得します.

  • #Delegate Getters & Events (Private)
    - (BOOL)dzn_shouldFadeIn {
        //...
        return YES;
    }
    
    - (BOOL)dzn_shouldDisplay {
        //...
        return YES;
    }
    
    - (BOOL)dzn_isTouchAllowed {
        //...
        return YES;
    }
    
    - (BOOL)dzn_isScrollAllowed {
        //...
        return NO;
    }
    
    - (BOOL)dzn_isImageViewAnimateAllowed {
        if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetShouldAnimateImageView:)]) {
            return [self.emptyDataSetDelegate emptyDataSetShouldAnimateImageView:self];
        }
        return NO;
    }
    
    - (void)dzn_willAppear {
        if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetWillAppear:)]) {
            [self.emptyDataSetDelegate emptyDataSetWillAppear:self];
        }
    }
    
    - (void)dzn_didAppear {
        if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetDidAppear:)]) {
            [self.emptyDataSetDelegate emptyDataSetDidAppear:self];
        }
    }
    
    - (void)dzn_willDisappear {
        if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetWillDisappear:)]) {
            [self.emptyDataSetDelegate emptyDataSetWillDisappear:self];
        }
    }
    
    - (void)dzn_didDisappear {
        if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetDidDisappear:)]) {
            [self.emptyDataSetDelegate emptyDataSetDidDisappear:self];
        }
    }
    
    - (void)dzn_didTapContentView:(id)sender {
        if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSet:didTapView:)]) {
            [self.emptyDataSetDelegate emptyDataSet:self didTapView:sender];
        }
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wdeprecated-declarations"
        else if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetDidTapView:)]) {
            [self.emptyDataSetDelegate emptyDataSetDidTapView:self];
        }
    #pragma clang diagnostic pop
    }
    
    - (void)dzn_didTapDataButton:(id)sender {
        if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSet:didTapButton:)]) {
            [self.emptyDataSetDelegate emptyDataSet:self didTapButton:sender];
        }
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wdeprecated-declarations"
        else if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetDidTapButton:)]) {
            [self.emptyDataSetDelegate emptyDataSetDidTapButton:self];
        }
    #pragma clang diagnostic pop
    }
    
  • 以上のこれらの方法は、エージェントプロトコル方法をプライベートメソッドにカプセル化し、エージェントプロトコル方法が実装される場合にのみ、これらのプライベートメソッドが有効になる.
  • pragma clang diagnosticについては、システムコンパイル警告は無視されます.このブログを参考にすることができます.http://nshipster.cn/clang-diagnostics/

  • #pragma mark - Reload APIs
    #pragma mark - Reload APIs (Public)
    - (void)reloadEmptyDataSet {
        [self dzn_reloadEmptyDataSet];
    }
    
    #pragma mark - Reload APIs (Private)
    - (void)dzn_reloadEmptyDataSet {
        if (![self dzn_canDisplay]) {
            return;
        }
      
        if ([self dzn_shouldDisplay] && [self dzn_itemsCount] == 0) {
            //            
            [self dzn_willAppear];
            
            DZNEmptyDataSetView *view = self.emptyDataSetView;
            
            if (!view.superview) {
                // Send the view all the way to the back, in case a header and/or footer is present, as well as for sectionHeaders or any other content
                if (([self isKindOfClass:[UITableView class]] || [self isKindOfClass:[UICollectionView class]]) && self.subviews.count > 1) {
                    [self insertSubview:view atIndex:0];
                } else {
                    [self addSubview:view];
                }
            }
            //         
            [view prepareForReuse];
            
            UIView *customView = [self dzn_customView];
            //            
            if (customView) {
                view.customView = customView;
            } else {
                //         
            }
            
            //..        ,    ,        。
            
            //               。
            [self dzn_didAppear];
        } else if (self.isEmptyDataSetVisible) {
            [self dzn_invalidate];
        }
    }
    
    - (void)dzn_invalidate {
        //          
        [self dzn_willDisappear];
        
        if (self.emptyDataSetView) {
            [self.emptyDataSetView prepareForReuse];
            [self.emptyDataSetView removeFromSuperview];
            
            [self setEmptyDataSetView:nil];
        }
        
        self.scrollEnabled = YES;
        //          
        [self dzn_didDisappear];
    }
    
    
  • dzn_reloadEmptyDataSetメソッドは、現在カスタムビューが存在するか否かを判断し、ある場合はカスタムビューに置き換え、そうでない場合は上記の構成のプライベートメソッドに従ってビューを構成する.
  • dzn_invalidateメソッドは、ビューが消えたときに親ビューを再設定し、空白のインタフェースのデータソースとサブビューを削除します.

  • #pragma mark - Method Swizzling
    static NSMutableDictionary *_impLookupTable;
    static NSString *const DZNSwizzleInfoPointerKey = @"pointer";
    static NSString *const DZNSwizzleInfoOwnerKey = @"owner";
    static NSString *const DZNSwizzleInfoSelectorKey = @"selector";
    
    void dzn_original_implementation(id self, SEL _cmd) {
        //           
        NSString *key = dzn_implementationKey(self, _cmd);
        
        NSDictionary *swizzleInfo = [_impLookupTable objectForKey:key];
        NSValue *impValue = [swizzleInfo valueForKey:DZNSwizzleInfoPointerKey];
        
        IMP impPointer = [impValue pointerValue];
        
        //                 
        //         ,      “isEmptyDataSetVisible”  。
        [self dzn_reloadEmptyDataSet];
        
        //     ,      
        if (impPointer) {
            ((void(*)(id,SEL))impPointer)(self,_cmd);
        }
    }
    
    NSString *dzn_implementationKey(id target, SEL selector) {
        if (!target || !selector) {
            return nil;
        }
        
        Class baseClass;
        if ([target isKindOfClass:[UITableView class]]) baseClass = [UITableView class];
        else if ([target isKindOfClass:[UICollectionView class]]) baseClass = [UICollectionView class];
        else if ([target isKindOfClass:[UIScrollView class]]) baseClass = [UIScrollView class];
        else return nil;
        
        NSString *className = NSStringFromClass([baseClass class]);
        
        NSString *selectorName = NSStringFromSelector(selector);
        return [NSString stringWithFormat:@"%@_%@",className,selectorName];
    }
    
    - (void)swizzleIfPossible:(SEL)selector {
        //         selector
        if (![self respondsToSelector:selector]) {
            return;
        }
        
        //      
        if (!_impLookupTable) {
            _impLookupTable = [[NSMutableDictionary alloc] initWithCapacity:2];
        }
        
        //       UITableView UICollectionView setImplementation  ,     。
        for (NSDictionary *info in [_impLookupTable allValues]) {
            Class class = [info objectForKey:DZNSwizzleInfoOwnerKey];
            NSString *selectorName = [info objectForKey:DZNSwizzleInfoSelectorKey];
            
            if ([selectorName isEqualToString:NSStringFromSelector(selector)]) {
                if ([self isKindOfClass:class]) {
                    return;
                }
            }
        }
        
        NSString *key = dzn_implementationKey(self, selector);
        NSValue *impValue = [[_impLookupTable objectForKey:key] valueForKey:DZNSwizzleInfoPointerKey];
        
        //             ,  !
        if (impValue || !key) {
            return;
        }
        
        //   Swizzle       
        Method method = class_getInstanceMethod([self class], selector);
        IMP dzn_newImplementation = method_setImplementation(method, (IMP)dzn_original_implementation);
        
        //            
        NSDictionary *swizzledInfo = @{DZNSwizzleInfoOwnerKey: [self class],
                                       DZNSwizzleInfoSelectorKey: NSStringFromSelector(selector),
                                       DZNSwizzleInfoPointerKey: [NSValue valueWithPointer:dzn_newImplementation]};
        
        [_impLookupTable setObject:swizzledInfo forKey:key];
    }
    
    
  • ここには著者が推薦したmethod swizzing技術に関する博文がある:The Right Way to Swizzle in Objective-C
  • dzn_original_implementationメソッドは、元の実装メソッド
  • を呼び出すために使用される.
  • dzn_implementationKeyメソッドは、インタフェースに空白のビューを必要とする親ビューreloadDataメソッドが複数存在し、スワップエラーを回避するため、スワップメソッドのメソッド名を取得し、クラス名および対応するメソッド名でカプセル化します.このデザインの細部は学ぶ価値がある.
  • swizzleIfPossible:(SEL)selectorメソッドは、親ビューが存在し、所定のタイプであるかどうか、および交換が必要なメソッドが呼び出されたかどうかを判断するために使用されます.
  • 注目すべき興味深い点は、この方法では、1つの辞書をルックアップテーブルとして使用することで、複数の空白ビューが存在する場合、UITAbleViewまたはUICOLLectionViewの元の方法が1回しか交換されないことを確認し、重複交換によるバグを回避することです.この細部処理はこのフレームワークの真髄であるはずだ.

  • #pragma mark - UIGestureRecognizerDelegate Methods
    - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
    {
        if ([gestureRecognizer.view isEqual:self.emptyDataSetView]) {
            return [self dzn_isTouchAllowed];
        }
        return [super gestureRecognizerShouldBegin:gestureRecognizer];
    }
    
    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
    {
        UIGestureRecognizer *tapGesture = self.emptyDataSetView.tapGesture;
        
        if ([gestureRecognizer isEqual:tapGesture] || [otherGestureRecognizer isEqual:tapGesture]) {
            return YES;
        }
        
        // defer to emptyDataSetDelegate's implementation if available
        if ( (self.emptyDataSetDelegate != (id)self) && [self.emptyDataSetDelegate respondsToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) {
            return [(id)self.emptyDataSetDelegate gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
        }
        return NO;
    }
    
  • この方法は、プロトコル・メソッドで定義されたジェスチャーを返すことによってコールバックに応答する空白ビューの応答ジェスチャーを設定する.

  • 最後に
    ソースコードの分析を通じて、私も最初に提出した問題を解決しました.このフレームワークは、分類拡張、およびエージェントプロトコルによって、リスニングビューがデータソースなしで空白のビューを表示すべきかどうかを達成する.しかし、個人的には、フレームワークの中には、いくつかのコードを少し簡素に書くことができます.例えば、いくつかの判断をカプセル化することができます.これにより、重複するコードが少なくなります.ははは、ただあら捜しをしているだけだ.全体的にこのフレームワークは素晴らしいもので、このフレームワークを使って、いくつかのユーザー体験を改善して、お勧めします.