objc与鸭子对象(上)
这是《objc与鸭子对象》的上半部分,《objc与鸭子对象(下)》中介绍了鸭子类型的进阶用法、依赖注入以及demo。
我是前言

鸭子类型(Duck Type)即:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子”,换成程序猿语言就是:“当调用者知道这个对象能调用什么方法时,管它这个对象到底是什么类的实例呢”。本文对objc中的鸭子类型对象进行简单探究,并用一个“只用一个类实现Json Entity”的小demo实践下这个思路的魔力。进阶篇请看下半部分。
objc与鸭子类型
id类型是个大鸭子
鸭子类型是动态语言的特性,编译时并不决定函数调用关系,说白了所有的类型声明都是给编译器看的。objc在动态和静态方面找到了不错的平衡,既保留了严格的静态检查也没破坏运行时的动态特性。
我们知道,向一个objc对象(或Class)发消息,实际上就是沿着它的isa指针寻找真正函数地址,所以只要一个对象满足下面的结构,就可以对它发送消息:
struct objc_object {
|
也就是熟知的id类型,objc在语言层面先天就支持了这个基本的鸭子类型,我们可以将任意一个对象强转为id类型从而向它发送消息,就算它并不能响应这个消息,编译器也无从知晓。
正如这篇文章中对objc对象的简短定义:The best definition for a Smalltalk or Objective-C "object" is "something that can respond to messages. object并非一定是某个特定类型的实例,只要它能响应需要的消息就可以了。
从@interface到@protocol
正如objc先天支持的动态的id类型,@protocol为鸭子类型提供了编译时的强类型检查,实现了Cocoa中经典的鸭子类型使用场景:
@property (nonatomic, assign) id <UITableViewDataSource> dataSource; |
利用鸭子类型设计的接口会给使用者更大的灵活度。同时@protocol可以用来建立伪继承关系
@protocol UIScrollViewDelegate<NSObject> |
<NSObject>协议的存在一方面是给NSProxy这样的其他根类使用,同时也给了鸭子协议类型一个根类型,正如给了大部分类一个NSObject根类一样。说个小插曲,由于objc中Class也是id类型,形如id<UITableViewDataSource>的鸭子类型是可以用Class对象来扮演的,只需要把实例方法替换成类方法,如:
@implementation DataSource |
设置table view的data source:
self.tableView.dataSource = (Class<UITableViewDataSource>)[DataSource class]; |
这种非主流写法合法且运行正常,归功于objc中加号和减号方法在@selector中并未体现,在@protocol中也是形同虚设,这种代码我相信没人真的写,但确实能体现鸭子类型的灵活性。
[Demo]一个类实现Json Entity
Entity对象表示某个纯数据的结构,如:
@interface XXUserEntity : NSObject |
实际开发中这种类往往对应着server端返回的一个JSON串,如:
{"name": "sunnyxx", "sex": "boy", "age": 24, ...}
|
解析这些映射是个纯重复工作,建类、写属性、解析…如今已经有JSONModel,Mantle等不错的框架帮忙。这个demo我们要用鸭子类型的思想去重新设计,把这些Entity类简化成一个鸭子类。
由于上面的UserEntity类,只有属性的getter和setter,这正对应了NSMutableDictionary的objectForKey:和setObjectForKey:,同时,JSON数据也会解析成字典,这就完成了巧妙的对接,下面去实现这个类。
真正干活的是一个字典,保证封装性和纯粹性,这个类直接使用NSProxy作为纯代理类,只暴露一个初始化方法就好了:
// XXDuckEntity.h |
NSProxy默认是没有初始化方法的,也省去了去规避其他初始化方法的麻烦,为了简单直接初始化时就把json串解开成字典(暂不考虑json是个array):
- (instancetype)initWithJSONString:(NSString *)json |
NSProxy可以说除了重载消息转发机制外没有别的用法,这也是它被设计的初衷,自己什么都不干,转给代理对象就好。往这个proxy发消息是注定会走消息转发的,首先判断下是不是一个getter或setter的selector:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector |
签名替换成字典的两个方法后开始走转发,在这里设置参数和对内部字典的真正调用:
- (void)forwardInvocation:(NSInvocation *)invocation |
当然还有这两个必不可少的从getter和setter中获取属性名的Helper:
- (NSString *)propertyNameScanFromGetterSelector:(SEL)selector |
一个简单的鸭子Entity就完成了,之后所有的Entity都可以使用@protocol而非子类化的方式来定义,如:
@protocol XXUserEntity <NSObject> |
当数据从网络层回来时,鸭子类型让这个对象用起来和真有这么个类没什么两样:
- (void)requestFinished:(XXDuckEntity<XXStudentEntity> *)student {
|
至此,所有的entity被表示成了N个<Protocol>的.h文件加一个XXDuckEntity类,剩下的就靠想象力了。
这个demo的源码将在下半部分之后给出
http://blog.sunnyxx.com/2014/08/24/objc-duck/
objc与鸭子对象(上)的更多相关文章
- 通过jQuery Ajax使用FormData对象上传文件
FormData对象,是可以使用一系列的键值对来模拟一个完整的表单,然后使用XMLHttpRequest发送这个"表单". 在 Mozilla Developer 网站 使用For ...
- 在Function对象上扩展method方法
;(function() { /** * 在Function对象上扩展method方法 * @param {String} name 扩展的方法名称 * @param {Function} callb ...
- Javascript进阶篇——( JavaScript内置对象---上-Date,string,charAt,indexOf,split,substring,substr)笔记整理
什么是对象JavaScript 中的所有事物都是对象,如:字符串.数值.数组.函数等,每个对象带有属性和方法.对象的属性:反映该对象某些特定的性质的,如:字符串的长度.图像的长宽等:对象的方法:能够在 ...
- Effective JavaScript Item 36 实例状态仅仅保存在实例对象上
本系列作为EffectiveJavaScript的读书笔记. 一个类型的prototype和该类型的实例之间是"一对多"的关系.那么,须要确保实例相关的数据不会被错误地保存在pro ...
- IT轮子系列(四)——使用Jquery+formdata对象 上传 文件
前言 在MVC 中文件的上传,一般都采用控件: <h2>IT轮子四——文件上传</h2> <div> <input type="file" ...
- FormData 对象上传二进制文件
使用jQuery 利用 FormData 上传文件:http://harttle.com/2016/07/04/jquery-file-upload.html 通过FormData对象可以组装 ...
- WinForm 绑定到嵌套对象上的属性
WinFrom 绑定到嵌套对象上的属性 关键字: Windows Forms, DataBindings, Nested Class, 嵌套类 在 WinForm 中很早就已经支持数据绑定, 使用数据 ...
- 通过jQuery Ajax使用FormData对象上传文件 (转载)
XMLHttpRequest Level 2 添加了一个新的接口——FormData.与普通的 Ajax 相比,使用 FormData 的最大优点就是我们可以异步上传二进制文件.jQuery 2.0+ ...
- [转] 通过jQuery Ajax使用FormData对象上传文件
FormData对象,是可以使用一系列的键值对来模拟一个完整的表单,然后使用XMLHttpRequest发送这个"表单". 在 Mozilla Developer 网站 使用For ...
随机推荐
- apt 下载安装包
1) Try both without sudo, apt-get download will pass and apt-get -d install will fail (root required ...
- 源码分析篇 - Android绘制流程(二)measure、layout、draw流程
performTraversals方法会经过measure.layout和draw三个流程才能将一帧View需要显示的内容绘制到屏幕上,用最简化的方式看ViewRootImpl.performTrav ...
- Vue + Element UI 实现权限管理系统 前端篇(六):更换皮肤主题
自定义主题 命令行主题工具 1.安装主题工具 首先安装「主题生成工具」,可以全局安装或者安装在当前项目下,推荐安装在项目里,方便别人 clone 项目时能直接安装依赖并启动. yarn add ele ...
- git 删除远程分支文件夹
把不需要版本控制的文件提交到远程分支上后,需要删除远程分支上的文件,用以下操作即可: git rm -r –cached dirname //删除远程文件夹,但保留本地文件夹 git commit - ...
- 代理模式——java设计模式
代理模式(Proxy Pattern) GoF中给出的代理模式的定义为: 代理模式给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问. 代理模式的英文叫做Proxy或Surrogate ...
- scala-05-map映射
Map Scala映射(Map)是一组键/值对的对象. 任何值都可以根据键来进行检索.键在映射中是唯一的,但值不一定是唯一的.映射也称为哈希表.映射有两种,不可变的和可变的.可变对象和不可变对象之间的 ...
- curl常用命令【转】
原文地址: http://www.thegeekstuff.com/2012/04/curl-examples/ 下载单个文件,默认将输出打印到标准输出中(STDOUT)中 curl http://w ...
- CentOS 7通过RVM来安装指定版本的Ruby
RVM也就是Ruby Version Manager,Ruby版本管理器 1.安装依赖库: yum install gcc-c++ patch readline readline-devel zlib ...
- C++语言------顺序表实现,用动态数组的方法
C++ 中常用的一些东西,通过使用动态数组来实现顺序表, 掌握了一下知识点: 1.预处理有三中方法 宏定义,文件包含,条件编译 2.使用同名的变量时,可以在外层使用命名空间 类解决变量名重定义的错误 ...
- U3D Input类之键位输入GetKey
Input类中包含许多属性和方法,下面介绍一下和键盘输入有关的内容 一.有关接收键盘信息的属性 属性名 类型 属性类型 含义 anyKey bool get 当前是否按住任意键或鼠标按钮 anyKey ...