前言

从事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自我描述的更多相关文章

  1. 打造强大的BaseModel(2):让Model实现自动映射,将字典转化成Model

    打造强大的BaseModel(1):让Model自我描述 这篇文章将讲述Model一项更高级也最常用的功能,让Model实现自动映射–将字典转化成Model(所有代码全由Swift实现) 将JSON转 ...

  2. [置顶网]POWER 9为云与智能打造强大引擎

    POWER 9为云与智能打造强大引擎 关键字: 浪潮商用机器 POWER9 至顶网服务器频道 (文/董培欣): 从全球角度看,政治经济波动持续.逆全球化趋势抬头.技术加速变革商业等因素促使企业需要数字 ...

  3. 10分钟打造强大的gvim

    感谢Ruchee的共享精神,让我等vim新手省去了配置vim的麻烦(教程地址:配置文件使用指南). 只需要简单的6个步骤,就可以配置完成一个强大的gvim神器,下图是我的最终配置效果图. (另外,我的 ...

  4. JavaWeb http协议的自我描述

    1.http协议的组成 http:规范那种协议 localhost.127.0.0.1:访问的ip地址(默认,根据自己的需求改变) 端口号:8080(默认,根据自己的需求改变) 工程:XXX 资源:可 ...

  5. atitit.提升软件开发的效率and 质量的那些强大概念and方法总结

    atitit.提升软件开发的效率and 质量的那些强大概念and方法总结 1. 主流编程中三个最糟糕的问题 1 1.1. 从理解问题后到实现的时间很长 1 1.2. 理解和维护代码  2 1.3. 学 ...

  6. 从线性模型(linear model)衍生出的机器学习分类器(classifier)

    1. 线性模型简介 0x1:线性模型的现实意义 在一个理想的连续世界中,任何非线性的东西都可以被线性的东西来拟合(参考Taylor Expansion公式),所以理论上线性模型可以模拟物理世界中的绝大 ...

  7. 利用泛型和反射,管理配置文件,把Model转换成数据行,并把数据行转换成Model

    利用泛型和反射,管理配置文件,把Model转换成数据行,并把数据行转换成Model   使用场景:网站配置项目,为了便于管理,网站有几个Model类来管理配置文件, 比如ConfigWebsiteMo ...

  8. 利用反射和泛型把Model对象按行储存进数据库以及按行取出然后转换成Model 类实例 MVC网站通用配置项管理

    利用反射和泛型把Model对象按行储存进数据库以及按行取出然后转换成Model 类实例 MVC网站通用配置项管理   2018-3-10 15:18 | 发布:Admin | 分类:代码库 | 评论: ...

  9. Vim Python3环境打造

    Vim Python3环境打造 tags: Vim Python3 参考网址:Vim与Python真乃天作之合:打造强大的Python开发环境 分割布局 sv 纵向分割 vs 横向分割 ctrl+W ...

随机推荐

  1. JAVACC详解

    JavaCC(Java Compiler Compiler)是一个用JAVA开发的最受欢迎的语法分析生成器.这个分析生成器工具可以读取上下文无关且有着特殊意义的语法并把它转换成可以识别且匹配该语法的J ...

  2. Tomcat 7 Connector 精读(2) 协议处理器 Http11Protocol(待续)

    . Http11Protocol是阻塞式IO的实现,上图的几个方法是它的生命周期相关的方法.

  3. 【暑假】[深入动态规划]UVAlive 4794 Sharing Chocolate

    UVAlive 4794 Sharing Chocolate 题目: http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=12055 ...

  4. 《Genesis-3D开源游戏引擎完整实例教程-跑酷游戏篇03:暂停游戏》

    3.暂停游戏 暂停游戏概述: 在游戏进行时,玩家有可能会遇到多种突发事件.在跑酷游戏中突发状况的发生对游戏的影响更甚,游戏进行时玩家死亡,游戏只能从头开始,那么如果因为外界因素而影响游戏的进行,显然是 ...

  5. html5爱心表白

    http://js.itivy.com/jiaoben1892/index.html http://bangpai.sourceforge.net/main.html

  6. 【noip2012】开车旅行

    题意: 给n个点的海拔h[i](不同点海拔不同) 两点的距离为abs(h[i]-h[j]) 有a.b两人轮流开车(只能往下标大的地方开) a每次会开到里当前点第二近的点 b每次会开到离当前点最近的点( ...

  7. hdoj 1008 Elevator

    Elevator Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Su ...

  8. Windbg分析DMP文件

    1.提取Dump格式文件 有两种方式: 第一种,程序崩溃时,启动任务管理器,选择崩溃的*.exe进程,右键选择创建转储文件,通过 开始—运行—输入 %temp% --确定--在打开Temp窗口中即可找 ...

  9. 运用集合来做一个DVD管理器(全代码)

    package DVD;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Array ...

  10. win10中android studio中的terminal不能输入

    1 打开CMD窗口右击           2    3 重启电脑,你试试就知道了.