作者:Soroush Khanlou,原文链接,原文日期:2016-10-25
译者:wiilen;校对:Cwift;定稿:CMB

要使用 NSCoding,必须遵循 NSObjectProtocol 这个类协议,因此结构体无法使用。如果我们想对某些数据进行编码,最简单的方式是将它们作为一个类来实现,并且继承自 NSObject

我找到了一种优雅的方式来将结构体包在 NSCoding 的容器中,存储时也不会让人觉得小题大做。用 Coordinate 举个例子:

struct Coordinate: JSONInitializable {
    let latitude: Double
    let longitude: Double
        
    init(latitude: Double,longitude: Double) {
        self.latitude = latitude
        self.longitude = longitude
    }
}

这是一个简单的类型,带有两个常量属性。接下来我将创建一个遵循 NSCoding 协议的类,并将 Coordinate 包在其中:

class encodableCoordinate: NSObject,NSCoding {
    
    var coordinate: Coordinate?
    
    init(coordinate: Coordinate?) {
        self.coordinate = coordinate
    }
    
    required init?(coder decoder: NSCoder) {
        guard
            let latitude = decoder.decodeObject(forKey: "latitude") as? Double,let longitude = decoder.decodeObject(forKey: "longitude") as? Double
            else { return nil }
        coordinate = Coordinate(latitude: latitude,longitude: longitude)
    }
    
    func encode(with encoder: NSCoder) {
        encoder.encode(coordinate?.latitude,forKey: "latitude")
        encoder.encode(coordinate?.longitude,forKey: "longitude")
    }
}

把以上的逻辑放在另一个类型中是合情合理的,这样可以更严格地适用单一职责原则(single responsibility principle)。聪明的读者在阅读上面的类时,会发现 encodableCoordinate 类中的 coordinate 这一属性是 Optional 的,但也可以不这样实现。我们可以使对应的构造器接收一个非 Optional 的 Coordiante 参数(或使用可失败构造器),而 init(coder:) 构造器原本就是可失败的,现在如果能得到一个 encodableCoordinate 类的实例,可以保证该实例中总有 coordinate

然而由于 NSCoder 工作方式的特殊性,当编码 Double 类型(以及其他基本类型)时,这些类型的数据无法使用 decodeObject(forKey:) 方法来进行解码(这样做会返回 Any? ),而是需要使用它们专属的方法,对 Double 来说,则是 decodeDouble(forKey:)。不幸的是,这些专属方法不会返回 Optional,在找不到 key 或碰到其他类型的错误时会返回 0.0。因此,我选择将 coordinate 属性实现为 Optional,并作为 Optional 来编码,从而在使用 decodeObject(forKey:) 方法来进行解码时,能获取 Double? 类型的对象,并添加一些额外的安全性。

从现在开始,我们可以创建 encodableCoordinate 的实例,用它来编解码 Coordinate 对象,并通过 NSKeyedArchiver 写入磁盘:

let encodable = encodableCoordinate(coordinate: coordinate)
let data = NSKeyedArchiver.archiveRootObject(encodable,toFile: somePath)

存储时每次都创建一个额外的对象未免太麻烦了,并且我也希望将这种方法和 SKCache(来源于 Cache Me If You Can 这篇文章)一起使用,如果我能规范编码器与被编码对象之间的关系,也许就能避免每次都创建一个 NSCoding 容器。

想要做到这一点,先添加两个协议:

protocol Encoded {
    associatedtype Encoder: NSCoding
    
    var encoder: Encoder { get }
}

protocol encodable {
    associatedtype Value
    
    var value: Value? { get }
}

并让两个类对应遵守这两个协议:

extension encodableCoordinate: encodable {
    var value: Coordinate? {
        return coordinate
    }
}

extension Coordinate: Encoded {
    var encoder: encodableCoordinate {
        return encodableCoordinate(coordinate: self)
    }
}

实现了以上内容之后,类型系统就知道如何在这些对象对之间进行值的转换了。

class Cache<T: Encoded> where T.Encoder: encodable,T.Encoder.Value == T {
    //...
}

对上文中提到的 SKCache 对象进行了升级之后,它现在更具通用性,可以在符合 Encoded 协议的类型中使用了。同时它也约束了该类型的编码器的 value 对象类型必须是该类型本身,使得两个类型之间可以进行双向转换。

最后需要完善的一部分是该类型的 savefetch 方法。save 包括了获取 encoder(真正遵守 NSCoding 协议的对象),并将其存到某个路径中:

func save(object: T) {
   NSKeyedArchiver.archiveRootObject(object.encoder,toFile: path)
}

fetch 则包括了一些微小的编译器工作。我们需要将解档对象的类型转换为 T.encodable,即编码器的类型,然后获取它的值,并动态将其类型转换回 T

func fetchObject() -> T? {
    let fetchedEncoder = NSKeyedUnarchiver.unarchiveObject(withFile: storagePath)
    let typedEncoder = fetchedEncoder as? T.Encoder
    return typedEncoder?.value as T?
}

现在,要使用这个 cache,只需要实例化一个对象并指定其类型为 Coordinate

let cache = Cache<Coordinate>(name: "coordinateCache")

生成了该对象之后,我们就可以透明地存取 coordinate 结构体了:

cache.save(object: coordinate)

使用以上方法,我们可以通过 NSCoding 来编码结构体,遵守单一职责原则,并加强了类型安全。

本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 http://swift.gg。

结构体与 NSCoding的更多相关文章

  1. ios – MKMapView MKCircle呈现一个半径太大的圆

    我面临着MKCircle外表的奇怪行为.基本上我正试图用一个任意的中心绘制一个半径为8500km的圆.这是我的代码:我还有一个自定义双击手势处理程序,它会覆盖地图视图的标准处理程序,并允许通过双击地图视图来更改地图中心:结果很奇怪:您可能会注意到这两个半径之间存在显着差异:第二个半径比第一个半径大!发生了什么,如何使它们正确显示?

  2. xcode – 尝试在Pin上居中地图(MKMapView)

    苦苦寻找一种方法来使地图缩放并以注释引脚为中心.Pin下降,但地图加载海洋.代码如下.第2个问题,非常相关:在实现上述问题的答案后,我已经修改了我的代码.现在,我的坐标从前一个视图到我的MKMapView,所以我不必费心去做两次API调用,第二个是在MKMapView中.目前在我的ViewWillAppear中,我有以下内容,并且AGAIN遇到了一个问题,即视图不会居中并放大图钉:反馈非常感谢,因为我不知道还应该做些什么.引脚加载到正确的坐标上,只是没有居中/缩放…

  3. ios – Swift无法分配类型[CLLocationCoordinate2D]的不可变值

    有人可以解释为什么我收到错误“无法分配类型[CLLocationCoordinate2D]的不可变值”我会给出两个场景.我希望第二个工作的原因是因为我将处于循环中并且需要每次都将它传递给drawShape函数.此代码有效:此代码不起作用:我不明白为什么这不起作用.我甚至有println(coordinates)和println(coords),它给了我相同的输出.解决方法将参数传递给函数时,默认情

  4. ios – 如何在MKMapView中将引脚和贴图保持在移动叠加层的中心

    如何在地图上垂直移动另一个视图,使得引脚保持在覆盖图上方,如何将引脚置于地图中心.请参阅附加屏幕截图,了解第一个和最终状态.当用户平移上/下时,我已经获得了叠加层和屏幕顶部之间空间的CGRect.然而,当用户向上平移时,我如何使用它来移动地图和图钉同时放大地图……

  5. ios – 如何使用Swift使用Core Data更新/保存和保留非标准(可转换)属性?

    我已经构建了一个非常基本的示例来演示我尝试更新可转换类型并在应用程序重新启动之间保持更改的问题.我有一个Destination类型的实体……解决方法核心数据无法跟踪该对象的脏状态,因为它不了解其内部.而不是改变对象,创建一个副本,改变它,然后设置新对象.它可能会变异,然后重新设置相同的对象,不确定,没有测试它.您可以检查,只是改变地址,然后询问托管对象是否有更改,如果没有则则不会保存.

  6. 寒城攻略:Listo 教你 25 天学会 Swift 语言 - 13 Methods

    方法还可以给它隐含的self属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例structPoint1{0.0,y=mutatingfuncmoveByX{x+=deltaXy+=deltaY}}varsomePoint1=Point1">1.0)注意定义结构体实例时必须为变量不能为常量somePoint1moveByX2.03.0)")//在变异方法中给self赋值structPoint2{Double){self=Point2}}varsomePoint2=Point2(x:somePoi

  7. 【Swift初见】Swift结构体

    访问rect这个实例里面的width和height方法也是用点语法:这样我们就完成了一个结构体的定义和创建一个结构体实例。结构体的成员方法:与C还有OC结构体不一样的是,swift的结构体可以包含成员方法,即行为,这就跟面向对象中的类的概念比较类似:我们给刚刚Rect结构体加上一个面积的方法:我们可以看出,方法的定义跟外部全局函数的定义是一样的,那么如何使用该成员方法呢?

  8. 寒城攻略:Listo 教你 25 天学会 Swift 语言 - 21 Nested Types

    //***********************************************************************************************//1.nestedTypes(类型嵌套)//________________________________________________________________________________

  9. Swift语法基础:5 - Swift的枚举和结构体

    在Siwft中的枚举类型以及结构体,是和OC中差不多的,但Swift中又有一些特性,下面让我们来看看:1.枚举的声明及使用PS:这里解释一下,枚举类型第一个开始的参数都是1,无论你是有多少case,都会递增的,比如例子的的Ace是1,那么Two就是名副其实的2,Three就是3,以此类推,一直到King,就是13,而enum里面有一个方法,这里面这个方法只是说可以在enum里定义方法,但我这个例子

  10. Swift语法基础:6 - Swift的Protocol和Extensions

    前面我们知道了枚举类型和结构体的声明,嵌套,以及基本的使用,现在让我们来看看Swift中的另外两个,一个是Protocol(协议),一个是Extensions(类别):1.声明ProtocolPS:在声明协议的时候,如果你要修改某个方法属性,你必须的在该方法前加上mutating这个关键字,否则不能修改.2.使用Protocol同样的,在结构体里一样可以使用,但是必须的加上mutating:Protocol也可以这么使用:PS:这里的a是前面例子里面的a.3.声明Extensions好了,这次我们就讲到这

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部