打造强大的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 ...
随机推荐
- android中Invalidate和postInvalidate的区别
Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用. Android提供了Inva ...
- C# 检测机器是否有声卡设备
有时候我们的程序需要进行音频的播放,则我们首先需要判断机器是否有声卡能够进行音频的播放.在网上找了一下没有发现太多关于如何检机器是否有声卡的例子.我在看了一些文档后自己写了一个小测试程序,如果机器装有 ...
- unity3d教程资源
unity2d教程网 http://learnunity2d.com/2d-shootem-up-tutorial/ 横版射击2d游戏系列教程 http://pixelnest.io/tutorial ...
- 关于贴友的一个书本页面简单布局(html+css)的实现
贴友需求:以html+css仿照书本的页面实现布局效果(见图) html代码: 1: <!-- 我的博客:http://www.ido321.com --> DOCTYPE HTML> ...
- 【暑假】[深入动态规划]UVAlive 4794 Sharing Chocolate
UVAlive 4794 Sharing Chocolate 题目: http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=12055 ...
- C++11 并发指南------std::thread 详解
参考: https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/blob/master/zh/chapter3-Thread/Int ...
- flappy pig小游戏源码分析(3)——解剖util
这一节我们继续高歌猛进,如果对源码中有无论无何都理解不通的问题,欢迎和我交流,让我也学习一下,我的qq是372402487. 还是按照惯例看看我们的目录结构. 我们在前两节中已经分析了game.js, ...
- AIDL:Binder invocation to an incorrect interface
Android进程之间通信异常:主要原因是客户端的aidl文件和与远程调用的Service的aidl文件包名不同 处理方式一般就是在客户端要一个与远程暴露出来的接口包名要一致 服务端: 客户端:
- leetcode@ [54/59] Spiral Matrix & Spiral Matrix II
https://leetcode.com/problems/spiral-matrix/ Given a matrix of m x n elements (m rows, n columns), r ...
- iso定制封装
http://xiaoli110.blog.51cto.com/1724/1617541