有关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. ZOJ3944 People Counting ZOJ3939 The Lucky Week (模拟)

    ZOJ3944 People Counting ZOJ3939 The Lucky Week 1.PeopleConting 题意:照片上有很多个人,用矩阵里的字符表示.一个人如下: .O. /|\ ...

  2. AD域-让共享目录只显示用户有权限访问的文件夹

    问题: 在AD域中,我们一般都会用到共享,如果有很多部门,我们可能还会按部门.职位配置权限.比如CSD,IT,PA等,但文件夹一多,用户看着就头大,而且用户没权限访问的文件夹误点击进去还会提示无权限访 ...

  3. AD域的安装(在Windows Server 2003中安装Active Directory)

    在Active Directory中提供了一组服务器作为身份验证服务器或登录服务器,这类服务器被称作域控制器(Domain Controller,简称DC).建立一个AD域的过程实际就是在一台运行Wi ...

  4. 利用Levenshtein Distance (编辑距离)实现文档相似度计算

    1.首先将word文档解压缩为zip /** * 修改后缀名 */ public static String reName(String path){ File file=new File(path) ...

  5. “fatal error C1010”错误解决的三种方法

    尝试写了一个简单的类文件,但在编译的时候提示错误,具体错误信息如下: fatal error C1010: unexpected end of file while looking for preco ...

  6. tomcat 快速部署静态文件

    server.conf配置: <?xml version='1.0' encoding='utf-8'?> <!-- Licensed to the Apache Software ...

  7. PSD文件在MAC上和在WINDOWS上的大小有本质区别

    因为偷懒在MAC上的美工,发我的PSD文件,我就直接在上面做了= =后来不知道为什么无论我怎么合并图层.PSD的大小永远都是107M....然后忍无可忍重新画就从107M变成2M.....MAC为什么 ...

  8. ios 和安卓常用图标、启动图 尺寸

    ---------------------------------------------ios---------------------------------------------------- ...

  9. 自动化运维 Expect

      Mac 下载:brew install homebrew/dupes/expect expect : ->  自动化脚本工具:  用于处理交互命令; #注意 调用时并不是使用的 /bin/b ...

  10. Feature Access

    在ArcGIS Server中发布支持Feature Access地图服务,你需要知道的几点: 所绘制的mxd地图文件中包含的数据,必须来自企业级数据库链接: mxd中包含的所有图层的数据,必须来自同 ...