杭州达内嵌入式培训火热招生中。。。|杭州达内c++培训火热招生中
您现在的位置: 杭州达内 >>3G-IOS>>swift开发技巧之让Model自我描述
swift开发技巧之让Model自我描述
杭州达内 - 3G-IOS 来源:未知 发布时间:2016-04-27 14:04


    Model就是MVC和MVVM较前面的M,显然Model的重要性不言而喻。只有在将网络&数据库获取的数据正确转化成Model后,才能更好地服务ViewController和View。

    本文中,杭州达内ios培训专家重点讲一下如何增强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就行了。

    达内ios培训专家提示,如果你的类是继承了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类。

    注意在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类后,只有两个属性,另外两个全没了。所以在运行时找不到这两个属性,也就无法打印了。

    另外一个问题就是如果这个类中的属性是另一个类怎么办?或者是个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]";
}]


    可见所有的属性都正确地打印出来了。

 


相关标签:
 达内简介 达内就业 达内课程 联系我们 网站导航
杭州达内为上软件有限公司 Copyright @ 2007-2012 版权所有 苏ICP备10118953号-1
分享到: