有关RunTime的知识点已经看过很久了,但是一直苦于在项目中没有好的机会进行实际运用,俗话说“光说不练假把式”,正好最近在项目中碰到一个UITableView和UICollectionView在数据缺省的情况下展示默认缺省页的需求,这个时候RunTime大展拳脚的时候就到了。

 大致的实现思路是这样的,因为UITableView和UICollectionView都是继承自系统的UIScrollView,所以为了同时实现UITableView和UICollectionView的缺省页,我们可以通过对UIScrollView进行扩展来达到这一目标。UITableView和UICollectionView都可以通过dataSource设置它们的数据源,而我们需要做的就是在UITableView和UICollectionView设置数据源之后去获取它们的数据源,从而得知当前UITableView和UICollectionView是否处于缺省状态。当UITableView和UICollectionView处于缺省状态时,我们就将加载缺省页视图。

 #import <UIKit/UIKit.h>

 @protocol EmptyDataViewDataSource;

 @interface UIScrollView (EmptyData)

 @property (nonatomic, weak) id <EmptyDataViewDataSource> emptyViewDataSource;

 @end

 @protocol EmptyDataViewDataSource <NSObject>

 - (UIView *)emptyDataCustomView;

 @end

  首先实现一个UIScrollView的Category,在这个Category中声明一个EmptyDataViewDataSource的代理。当然,由于Category是在编译期间进行决议的,而OC的类对象在内存中是以结构体的形式排布的,结构体的大小是不能动态改变的,因此Category是不能动态地添加成员变量的,但是我们可以通过runtime中的

objc_setAssociatedObject和objc_getAssociatedObject方法去创建一个关联对象,从而在外部看起来好像我们为一个Category添加了一个成员变量。

  我们自定义EmptyDataViewDataSource的set和get方法。创建对象ProtocolContainer,并将它关联到self上。之后执行SelectorShouldBeSwizzle方法,对系统方法进行替换。

 - (void)setEmptyViewDataSource:(id<EmptyDataViewDataSource>)emptyViewDataSource
{
ProtocolContainer *container = [[ProtocolContainer alloc] initWithProtocol:emptyViewDataSource];
objc_setAssociatedObject(self, kEmptyDataDataSource, container, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [self SelectorShouldBeSwizzle]; //在EmptyViewDataSource的set方法中实现系统方法的替换操作
}
- (id<EmptyDataViewDataSource>)emptyViewDataSource
{
ProtocolContainer *container = objc_getAssociatedObject(self, kEmptyDataDataSource);
return container.protocolContainer;
}

EmptyDataViewDataSource set&get方法

  在SelectorShouldBeSwizzle方法中不论是UITableView还是UICollectionView我们都需要替换系统的reloadData方法。由于UITableView还有插入,删除等操作,在这些操作后系统会自动去调用UITableView中的endUpdates方法,因此我们还需要替换endUpdates方法。

 /**
需要被替换的系统方法
*/
- (void)SelectorShouldBeSwizzle
{
[self methodSwizzle:@selector(reloadData)]; //替换系统的reloadData实现方法 if ([self isKindOfClass:[UITableView class]]) //由于tableView有Cell的插入,删除实现,需要替换tableView的endUpdates方法,在操作结束后判断是否为空
{
[self methodSwizzle:@selector(endUpdates)];
}
}

  方法替换的实现主要依赖于runtime中的class_getInstanceMethod和method_setImplementation方法。我们先来看看class_getInstanceMethod方法的描述

 /**
* Returns a specified instance method for a given class.
*
* @param cls The class you want to inspect.
* @param name The selector of the method you want to retrieve.
*
* @return The method that corresponds to the implementation of the selector specified by
* \e name for the class specified by \e cls, or \c NULL if the specified class or its
* superclasses do not contain an instance method with the specified selector.
*
* @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not.
*/
OBJC_EXPORT Method class_getInstanceMethod(Class cls, SEL name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);

  从中我们可以看到这个方法的返回值是一个Method,那么什么是Method呢?Method是一个指向objc_method类型的结构体的指针,这个结构体中有三个成员变量,SEL类型的method_name(我的理解为方法的名称),char*类型的method_types(方法的参数)以及IMP类型的method_imp(方法的实现)。SEL和IMP是一种映射关系,OC在运行时通过SEL方法名去找方法对应的IMP实现,这也是为什么OC不能像C++那样实现函数重载的原因,因为在OC中方法名和实现是一一对应的关系,重名了OC就没办法去找对应的实现方法了(貌似有点扯远了。。。)现在我们就可以很好的理解class_getInstanceMethod这个方法了,这个方法实现的就是去Class cls中去找一个叫做SEL叫做name的方法, 并把这个方法返回。

 /**
* Sets the implementation of a method.
*
* @param m The method for which to set an implementation.
* @param imp The implemention to set to this method.
*
* @return The previous implementation of the method.
*/
OBJC_EXPORT IMP method_setImplementation(Method m, IMP imp)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

  现在再来看method_setImplementation方法就很好理解了。使用我们传入的IMP imp去替换Method中原来的IMP实现,并返回原来的IMP实现。在理解完了这两个方法以后我们再回到代码上来。

 /**
替换系统方法的实现 @param selector 需要被替换的系统方法
*/
- (void)methodSwizzle:(SEL)selector
{
NSAssert([self respondsToSelector:selector], @"self不能响应selector方法"); if (!_cacheDictionary) //如果缓存字典为空,开辟空间
{
_cacheDictionary = [[NSMutableDictionary alloc] init];
} NSString *selfClass = NSStringFromClass([self class]); //获取当前类的类型
NSString *selectorName = NSStringFromSelector(selector); //获取方法名
NSString *key = [NSString stringWithFormat:@"%@_%@",selfClass,selectorName]; //类型+方法名组成需要被替换的方法的key NSValue *value = [_cacheDictionary objectForKey:key]; //查询方法是否被替换 if (value)
{
return; //方法被替换时,直接return
} Method method = class_getInstanceMethod([self class], selector); IMP originalImplemention = method_setImplementation(method, (IMP)newImplemention); //获取替换前的系统方法实现 [_cacheDictionary setObject:[NSValue valueWithPointer:originalImplemention] forKey:key]; //缓存替换前的系统方法实现
}

methodSwizzle

  我们为乐不每一次都去执行方法的替换操作,我们使用一个缓存字典来存储已经被替换过的方法。我们使用类的类型和被替换掉实现的方法名组合起来作为缓存字典中存储的key值,value当然就是被替换下来的系统方法的实现。接下来我们看看我们在替换上去的newImplemention实现中做了什么。

 /**
被替换后的方法 @param self self
@param _cmd 方法名称
*/
void newImplemention(id self, SEL _cmd)
{
NSString *selfClass = NSStringFromClass([self class]);
NSString *selectorName = NSStringFromSelector(_cmd);
NSString *key = [NSString stringWithFormat:@"%@_%@",selfClass,selectorName]; //使用当前的类名和当前方法的名称组合称为key值 NSValue *value = [_cacheDictionary objectForKey:key]; //通过key从缓存字典中取出系统原来方法实现 IMP originalImplemention = [value pointerValue]; if (originalImplemention)
{
((void(*)(id,SEL))originalImplemention)(self,_cmd); //执行被替换前系统原来的方法
} if([self canEmptyDataViewShow]) //判断是否需要展示缺省页
{
[self reloadEmptyView];
}
}

newImplemention(id self, SEL _cmd)

  在OC中,每个方法被调用时,系统都会自动的为方法添加两个参数,一个self,另一个就是被调用的方法的名称,也就是我们的newImplemention中的两个入参。我们取出来系统原来的实现方法,这一步是必须的,毕竟我们的本意并不是抹除掉系统方法,而是在调用系统方法执行后判断是否需要展示缺省页而已啦。。。因此通过调用IMP的方式,直接调用系统方法实现。之后判断是否需要展示缺省页。

 - (BOOL)canEmptyDataViewShow
{
NSInteger itemCount = ; NSAssert([self respondsToSelector:@selector(dataSource)], @"tableView或CollectionView没有实现dataSource"); if ([self isKindOfClass:[UITableView class]])
{
UITableView *tableView = (UITableView *)self;
id<UITableViewDataSource> dataSource = tableView.dataSource;
NSInteger sections = ;
if (dataSource && [dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)])
{
sections = [dataSource numberOfSectionsInTableView:tableView];
}
if(dataSource && [dataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)])
{
for (NSInteger i=; i<sections; i++) {
itemCount += [dataSource tableView:tableView numberOfRowsInSection:i];
}
}
} if ([self isKindOfClass:[UICollectionView class]])
{
UICollectionView *collectionView = (UICollectionView *)self;
id<UICollectionViewDataSource> dataSource = collectionView.dataSource;
NSInteger sections = ;
if (!dataSource || [dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)])
{
sections = [dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)];
}
if (!dataSource || [dataSource respondsToSelector:@selector(collectionView:numberOfItemsInSection:)])
{
for (NSInteger i=; i<sections; i++) {
itemCount += [dataSource collectionView:collectionView numberOfItemsInSection:i];
}
}
} if (itemCount == )
{
return YES;
}
else
{
return NO;
}
}

- (BOOL)canEmptyDataViewShow

  我们获取到对象本身,并从对象中取出它的数据源,然后判断数据源的item是否为0 ,当为0时,就可以自动展示缺省页了。

 - (void)reloadEmptyView
{
UIView *emptyView;
if (self.emptyViewDataSource && [self.emptyViewDataSource respondsToSelector:@selector(emptyDataCustomView)])
{
emptyView = [self.emptyViewDataSource emptyDataCustomView];
}
else
{
emptyView = self.emptyDataView;
emptyView.backgroundColor = [UIColor redColor];
} if (!emptyView.superview)
{
if (self.subviews.count > )
{
[self insertSubview:emptyView atIndex:];
}
}
emptyView.frame = self.frame;
}

- (void)reloadEmptyView

  还记得我们在之前设置的EmptyDataViewDataSource吗?既然已经设置了EmptyDataViewDataSource,那我们就要把它充分利用起来嘛,来个自定义的emptyView也不是什么问题嘛~

源码github地址:https://github.com/cgy-tiaopi/CGYEmptyData

RumTime实践之--UITableView和UICollectionView缺省页的实现的更多相关文章

  1. UITableView和UICollectionView的方法学习一

    参考资料 UITableView UICollectionView UICollectionViewDataSource UICollectionViewDelegate UICollectionVi ...

  2. UItableView嵌套UICollectionView

    首先我们需要继承一下UITableView并且遵守<UITableViewDelegate,UITableViewDataSource,UICollectionViewDataSource,UI ...

  3. UITableView和UICollectionView的Cell高度的几种设置方式

    UITableViewCell 1.UITableView的Cell高度默认由rowHeight属性指定一个低优先级的隐式约束 2.XIB中可向UITableViewCell的contentView添 ...

  4. iOS 8自动调整UITableView和UICollectionView布局

    本文转载自:http://tech.techweb.com.cn/thread-635784-1-1.html 本文讲述了UITableView.UICollectionView实现 self-siz ...

  5. iOS中UITableView和UICollectionView的默认空态页

    项目中想实现空态页风格统一控制的效果,就封装了一个默认空态页,使用的技术点有:1 方法替换 ,2 给分类(Category)添加属性. 我们知道,扩展(extension)可以给类添加私有变量和方法. ...

  6. UITableVIew与UICollectionView带动画删除cell时崩溃的处理

    UITableVIew与UICollectionView带动画删除cell时崩溃的处理 -会崩溃的原因是因为没有处理好数据源与cell之间的协调关系- 效果: tableView的源码: ModelC ...

  7. [转]iOS8 自动调整UITableView和UICollectionView布局

    转自:http://www.cocoachina.com/industry/20140825/9450.html (via:玉令天下的Blog)   本文讲述了UITableView.UICollec ...

  8. 封装Button ,封装UITableView,封装UICollectionView

    ---恢复内容开始--- 封装Button ,封装UITableView,封装UICollectionView: 1.实现Button的创建和点击事件不用分开操作处理; 2.实现UITableView ...

  9. iOS全埋点解决方案-UITableView和UICollectionView点击事件

    前言 在 $AppClick 事件采集中,还有两个比较特殊的控件: UITableView •UICollectionView 这两个控件的点击事件,一般指的是点击 UITableViewCell 和 ...

随机推荐

  1. tomcat集群学习记录1--初识apache http server

    起因 平时开发的时候我都是用eclipse把代码部署到本地tomcat的,当然只会去跑1台tomcat啦... 偶尔有时候解决问题需要去公司测试环境找日志.连上公司测试环境以后发现竟然有2台weblo ...

  2. Xshell5 里桌面显示数据调大!!!

    今天告诉大家一个好用的技巧, 在Xshell5下工作,屏幕上很容易积攒数据量,尤其是编译的时候,(当然你编译的时候可以重定向到一个文件中,也可以只重定向编译错误的信息): 这个时候,你想观看之前的编译 ...

  3. Input输入框输入银行卡号自动空格

    input输入框是表格中用都最多的,像输入手机,密码,银行卡号的,很多对于输入银行卡号是没有处理的,比如这样~~ 看起来是不是一团乱麻,, 眼睛瞬间一片漆黑~  如果是这样,会不会好很多呢~~ 其实逻 ...

  4. R可视化lend_club 全球最大的P2P平台数据75W条

    lend_club 全球最大的P2P平台2007~2012年贷款数据百度云下载. 此文章基于R语言做简单分析. rm(list=ls()) #清除变量 gc() #释放内存 step1 考虑到后续分析 ...

  5. 第一届山东省ACM——Balloons(java)

    Description Both Saya and Kudo like balloons. One day, they heard that in the central park, there wi ...

  6. Linux快速上手

    1.Linux系统架构 内核(kernel) 内存管理(mm) Linux内存特性无论物理内存有多大,Linux 都将其充份利用,将一些程序调用过的硬盘数据读入内存,利用内存读写的高速特性来提高Lin ...

  7. list操作

    1.查看列表属性 >>> a = [1,2] >>> dir(a) ['__add__', '__class__', '__contains__', '__dela ...

  8. APUE学习--第三版apue编译

    第三版apue编译:     1. 首先在  http://www.apuebook.com/   下载源码解压:      tar zxvf src.3e.tar.gz 看完Readme可知,直接执 ...

  9. 【JS】字符串操作

    1.charCodeAt方法返回一个整数,代表指定位置字符的Unicode编码. strObj.charCodeAt(index) 说明: index将被处理字符的从零开始计数的编号.有效值为0到字符 ...

  10. Python全栈开发【re正则模块】

    re正则模块 本节内容: 正则介绍 元字符及元字符集 元字符转义符 re模块下的常用方法 正则介绍(re) 正则表达式(或 RE)是一种小型的.高度专业化的编程语言. 在Python中,它内嵌在Pyt ...