打造强大的BaseModel(1):让Model自我描述
前言
从事iOS开发已经两年了,从一无所知到现在能独立带领团队完成一系列APP的开发,网络上的大神给了我太多的帮助。他们无私地贡献自己的心得和经验,写出了一篇篇精美的文章。现在我也开始为大家贡献自己的心得,把它写成一系列iOS开发技巧系列文章。
这一系列文章都干货十足,希望各位读者可以积极留言,和我沟通。
何为Model?
Model就是MVC和MVVM最前面的M,显然Model的重要性不言而喻。只有在将网络&数据库获取的数据正确转化成Model后,才能更好地服务ViewController和View。通常 Model 是应用逻辑层的对象,如 Account、Order 等等。这些对象是你开发的应用程序中的一些核心对象,负责应用的逻辑计算和诸多与业务相关的方法和操作。
首先Model将未处理的数据转化成Model后,再传给ViewController,再传给ViewController再将处理好的Model数据显示到View上去。相反View产生的数据可也可以转化为Model,通过ViewConroller传到Model层处理后再保存&更新。在iOS开发中,Model还可以分为胖Model和瘦Model。当然,这些东西都不在本文的讨论范围之内。本文讨论的是如何增强Model的一些功能,这些功能并不是业务逻辑上的功能,而是让Model可以自动实现一些代码层面的功能。可以降低我们的代码量,大量减少重复的代码。
功能一: 让Model可以自我描述
众所周知,利用iOS的NSLog和print功能是可以打印iOS的任意对象的。但是对于自定义对象,打印出来的却是一连串的数字,这串数字就是该对象的内存地址(Objc),如果是用Swift,就会打印出来对象的类名。
class demoClass{
var a = 1
var b = "demo"
} //定义一个demoClass对象
print(demoClass())//打印出来
"demoClassn" //swift打印出了类名
//那么为什么Swift打印出了类名而不是类的内存地址呢?
//实际上,打印出对象的地址是Objective C对象的一个功能。
//单纯的Swift对象并非由NSObject派生,只能打印出类名
显然这串的数字或者是类名对我们来说毫无用处,正常情况下,我们需要看的是这个对象所有属性的数据。在Objc里面,直接在自定义类里重写description方法就行,当你打印对象时,运行时会自动调用对象的description方法。
-(NSString)descprition
{
return 你对自定义类的各个变量的描述,从而可以打印出来
}
但是在Swift语言,情况变得不一样了。Swift并不存在descprition方法,那么Swift是怎么实现的呢?
Swift中有一系列协议.其中以Custom开头的协议(目前共有5个):
CustomReflectable
CustomLeafReflectable
CustomPlaygroundQuickLookable
CustomStringConvertible
CustomDebugStringConvertible
这些协议表示自定义一个方法(其实并不是方法,后面会说到这是一个属性),这个方法是用来将对象转化为可以打印出来的字符串或者可视化的图形等。
其中协议CustomStringConvertible和CustomDebugStringConvertible就是相当于Objc的实现descprition方法的协议(其他协议可以看官方文档),让自定义的类继承这两个协议后就需要重写description属性和debugDescription属性(所以前面有提到这不是一个方法)
class demoClass{
var demoId:Int = 0
var demoName:String?
}
//实现协议可以在Extension里进行
extension demoClass:CustomStringConvertible{
var description:String{//重写description,注意,因为这个类没有父类,所以不需要加上override
return "DemoClass: demoId:(demoId) demoName:(demoName ?? "nil")"
}
}
extension demoClass:CustomDebugStringConvertible{
var debugDescription:String{
return self.description
}
}
let demo1 = demoClass()
print(demo1)
"DemoClass: demoId:0 demoName:niln"//打印的结果,因没有设置demoName所以为nil
demo1.demoName = "this is a demo"
print(demo1)
"DemoClass: demoId:0 demoName:this is a demon"//打印出了自己在里面写的属性
细心的同学可能注意到了CustomStringConvertible对应的应该是属性description,而CustomDebugStringConvertible对应的是debugDescription属性.那么debugDescription有什么用呢? debugDescription是在调试时你可以用po命令来打印对象,所以在这里我让它直接返回description就行了。
这里有一点需要注意,如果你的类是继承了NSObject的话,那么就不需要再继承CustomStringConvertible了,因为NSObject已经继承这个协议了,所以只要重写description属性就行了。
class DemoClassA: NSObject {
var demoId:Int = 0
var demoName:String?
override var description:String{
return "DemoClass: demoId:(demoId) demoName:(demoName ?? "nil")"
}
}
let demo2 = DemoClassA()
print(demo2)//"DemoClass: demoId:0 demoName:niln"
好了,怎么实现对象的自我描述很清楚了,但下一个问题又来了.一个项目里面通常会有十几个甚至几十个Model,如果每个Model都这样重写description属性是件极耗精力的事情.这需要重复写大量相似的代码,显然不这不可取的。那么有没有办法可以直接让Model自我描述呢?答案是有的。通过反射的方法或者在运行时可以找到Model的所有属性,再通过KVC给这些属性赋值就可以打印出来了。再将所有的属性名的其对应的值保存到字典里。再把字典按照某种格式转化为String就完成了。
当然这里有一个局限性,就是单纯的Swift类是没有KVC的,你需要让它继承NSObject就有这个功能。因为只有Objctive C才有运行时这一套东西。如果让Swift中加入Objc运行时,Swift的效率会有降低。这就要看自己的取舍了。
下面直接上代码
//先定义Model
class GrandModel:NSObject{
//这里不定义任何属性,所有用的属性都在子类,直接重写description
internal override var description:String{
get{
var dict = [String:AnyObject]()
let count:UnsafeMutablePointer = UnsafeMutablePointer()
var properties = class_copyPropertyList(self.dynamicType, count)
while properties.memory.debugDescription != "0x0000000000000000"{
let t = property_getName(properties.memory)
let n = NSString(CString: t, encoding: NSUTF8StringEncoding)
let v = self.valueForKey(n as! String) ?? "nil"
dict[n as! String] = v
properties = properties.successor()
}
return "(self.dynamicType):(dict)"
}
}
}
接下来写一个测试Model继承于GrandModel
class TestModel: GrandModel {
var i = 0
var a:String?
}
let model = TestModel()
print(model)//TestModel:["a": nil, "i": 0]n
model.a = "aaa"
print(model)//TestModel:["a": aaa, "i": 0]n
可见,结果完全符合我们需要的效果。所有的字段都可以成功打印出来,那么我再深入一下,如果TestModel里有一个属性是Enum,或者是其他的非Objc支持的运行时类型,会出现什么情况呢?
我们先定义一个枚举,并且把i改成Int?的类型,再加一个有初始值的Int类型
enum week{
case Mon,Thu,Wed,Tur,Fri,Sai,Sun
}
//TestModel加入枚举
class TestModel: GrandModel {
var i:Int?
var j = 1
var a:String?
var weeb:week?
}
let model = TestModel()
print(model)
//打印结果
//TestModel:["j": 1, "a": nil]
这个结果有点让人奇怪? 运行时找不到这两个属性?可以分析一下,我们定义的这个枚举是个纯粹的Swift枚举,而Int?类型也无法在Objc里面用正确的类型来表示。那为什么String?可以被Objc运行时正确地识别呢?所以一个大的问题出来了,Apple是怎么设定Swift类型到Objc类型的映射关系的?
关于这个问题,我想到了下面的方法
不再使用PlayGround来验证,新建立一个Command Line项目,默认语言设为Swift,然后再添加一个Objc类,如图所示
Xcode
注意在Objc的类加入Swift的头文件,其格式是[项目名]-Swift.h,然后进入这个文件,可以很容易找到定义在Main.swift里的TestModel类
SWIFT_CLASS("_TtC11DemoConsole9TestModel")
@interface TestModel : GrandModel
@property (nonatomic) NSInteger j;
@property (nonatomic, copy) NSString * __nullable a;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder * __nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER;
@end
这下就可以看得一清二楚了,被转成Objc类后,只有两个属性,另外两个全没了。所以在运行时找不到这两个属性,也就无法打印了。
关于更多的Swift类型到Objc类型的映射关系这里就不多说了,有兴趣的同学可以用XCode调试,相信你会有大收获的。
另外一个问题就是如果这个类中的属性是另一个类怎么办?或者是个Array,Dict呢,其实很简单,只要这个属性也继承了GrandModel,都可是顺利打印出来。
struct StructDemo {
var q = 1
var w = "w"
}
class ClassDemo {
var q = 1
var w = "w"
}
class ClassDemoA:GrandModel{
var q = 1
var w = "w"
}
class TestModelA: GrandModel {
var i:Int = 1
var o:String?
var structDemo:StructDemo?
var classDemo:ClassDemo?
var classDemoA:ClassDemoA?
var classDemoAArray:[ClassDemoA]?
var classDemoDict:[String:ClassDemoA]?
}
let modelA = TestModelA()
modelA.classDemoAArray = [ClassDemoA]()
modelA.classDemoAArray?.append(ClassDemoA())
modelA.classDemoAArray?.append(ClassDemoA())
modelA.classDemoDict = [String:ClassDemoA]()
modelA.classDemoDict!["1"] = ClassDemoA()
modelA.classDemoDict!["2"] = ClassDemoA()
print(modelA)
//TestModelA:["o": nil, "classDemoA": ClassDemoA:["q": 1, "w": w], "i": 1, "classDemoAArray": (
"ClassDemoA:["q": 1, "w": w]",
"ClassDemoA:["q": 1, "w": w]"
), "classDemoDict": {
1 = "ClassDemoA:["q": 1, "w": w]";
2 = "ClassDemoA:["q": 1, "w": w]";
}]
可见所有的属性都正确地打印出来了。
结语:让iOS的Model拥有自我描述的功能,可以在调试DeBug中发挥非常大的作用。也让我们看到了单纯的Swift类和Objc的的一套运行时机制完全不同的。不过目前iOS开发还是脱离不了Objc运行时,所以虽然相比较于单纯的Swift类,Objc运行时会有性能损失,但是还是可以完全接受的。
打造强大的BaseModel(1):让Model自我描述的更多相关文章
- 打造强大的BaseModel(2):让Model实现自动映射,将字典转化成Model
打造强大的BaseModel(1):让Model自我描述 这篇文章将讲述Model一项更高级也最常用的功能,让Model实现自动映射–将字典转化成Model(所有代码全由Swift实现) 将JSON转 ...
- [置顶网]POWER 9为云与智能打造强大引擎
POWER 9为云与智能打造强大引擎 关键字: 浪潮商用机器 POWER9 至顶网服务器频道 (文/董培欣): 从全球角度看,政治经济波动持续.逆全球化趋势抬头.技术加速变革商业等因素促使企业需要数字 ...
- 10分钟打造强大的gvim
感谢Ruchee的共享精神,让我等vim新手省去了配置vim的麻烦(教程地址:配置文件使用指南). 只需要简单的6个步骤,就可以配置完成一个强大的gvim神器,下图是我的最终配置效果图. (另外,我的 ...
- JavaWeb http协议的自我描述
1.http协议的组成 http:规范那种协议 localhost.127.0.0.1:访问的ip地址(默认,根据自己的需求改变) 端口号:8080(默认,根据自己的需求改变) 工程:XXX 资源:可 ...
- atitit.提升软件开发的效率and 质量的那些强大概念and方法总结
atitit.提升软件开发的效率and 质量的那些强大概念and方法总结 1. 主流编程中三个最糟糕的问题 1 1.1. 从理解问题后到实现的时间很长 1 1.2. 理解和维护代码 2 1.3. 学 ...
- 从线性模型(linear model)衍生出的机器学习分类器(classifier)
1. 线性模型简介 0x1:线性模型的现实意义 在一个理想的连续世界中,任何非线性的东西都可以被线性的东西来拟合(参考Taylor Expansion公式),所以理论上线性模型可以模拟物理世界中的绝大 ...
- 利用泛型和反射,管理配置文件,把Model转换成数据行,并把数据行转换成Model
利用泛型和反射,管理配置文件,把Model转换成数据行,并把数据行转换成Model 使用场景:网站配置项目,为了便于管理,网站有几个Model类来管理配置文件, 比如ConfigWebsiteMo ...
- 利用反射和泛型把Model对象按行储存进数据库以及按行取出然后转换成Model 类实例 MVC网站通用配置项管理
利用反射和泛型把Model对象按行储存进数据库以及按行取出然后转换成Model 类实例 MVC网站通用配置项管理 2018-3-10 15:18 | 发布:Admin | 分类:代码库 | 评论: ...
- Vim Python3环境打造
Vim Python3环境打造 tags: Vim Python3 参考网址:Vim与Python真乃天作之合:打造强大的Python开发环境 分割布局 sv 纵向分割 vs 横向分割 ctrl+W ...
随机推荐
- Discuz!NT中的Redis架构设计
在之前的Discuz!NT缓存的架构方案中,曾说过Discuz!NT采用了两级缓存方式,即本地缓存+memcached方式.在近半年多的实际运行环境下,该方案经受住了检验.现在为了提供多样式的解决方案 ...
- SQL Server 2008 R2主数据服务安装
SQL Server 2008 R2的主数据服务(Master Data Services,简称MDS)已经放出,目前是CTP版本,微软提供了下载地址: http://www.microsoft.co ...
- lightoj 1016
水题,排个序直接搞. #include<cstdio> #include<string> #include<cstring> #include<iostrea ...
- Zabbix探索:Discovery任务、进程以及占用率
刚刚又报错了,如下所示: Zabbix discoverer processes more than 75% busy 原因是,我配置了一个自动发现的任务.而每个自动发现的任务都会在一定时间内占用一个 ...
- 在 MacOS 上编译链接 OpenGL 程序
几个星期以前开始折腾在我的MBA上写 OpenGL 小程序.我不太熟悉MacOS上的开发工具比如XCode,所以一开始的想法就是用vim来写程序,然后手工编译链接.网上查了一下,MacOS上的Open ...
- bzoj 3110 [Zjoi2013]K大数查询(树套树)
Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置 ...
- uvalive 4992 Jungle Outpost
题意:一个凸边型,目标在凸边型内且最优.问最多删除几个点使目标暴露在新凸边型外面. 思路:二分+半平面相交. #include<cstdio> #include<cmath> ...
- volatile,可变参数,memset,内联函数,宽字符窄字符,国际化,条件编译,预处理命令,define中##和#的区别,文件缓冲,位域
1.volatile: 要求参数修改每次都从内存中的读取.这种情况要比普通运行的变量需要的时间长. 当设置了成按照C99标准运行之后,使用volatile变量之后的程序运行的时间将比register的 ...
- Java/Andriod- 使用Eclipse搭建环境
从网上找来的,做了一点小修改,我自己试了一下,1.3步骤我没去做,最后也不影响. 在开始Android开发之旅启动之前,首先要搭建环境,然后创建一个简单的HelloWorld.本文的主题如下: 1.环 ...
- Oracle用户及角色的权限管理[Oracle基础]
1.查看全部用户: select * from dba_users; select * from all_users; select * from user_users; 2.查看用户或角 ...