EMFileStream —— 基于 stdio 的 Swift 文件流操作库

发布于: 2016-12-25 14:56
阅读: 1363
评论: 0
喜欢: 13

这是一款基于Swift3.0的文件流操作库

项目在「GitHub」中开源,可在iOSmacOSLinux中通用。

引言

由于项目原因,将一些用C++实现的库移植到了iOS中。移植过程必然造了不少轮子,本文将开源一个基于stdioSwift文件流操作库。底层由C语言接口实现,可以简化Swift对文件流的操作过程。

这个库不仅适用于Swift开发的iOS App,也同样适用于Swift开发的macOSLinux程序。

实现

由于底层功能由C语言实现,文件读写都依赖缓冲区实现,所以就会用到大量指针操作,这对Swift来说简直就是灾难。因此我造了一些轮子来避免指针操作以及使ARC也能管理这些内存。

EMMemory

该工具类将接管所有游离的内存缓冲区,封装过后使缓冲区拥有一定的C指针功能,同时使ARC得以维护这段内存。

作为ARC的媒介封装,在构造函数中分配内存,在析构函数中释放内存,使得C分配的缓冲区内存得以管理:

open class EMMemory {
    
    open var mptr: UnsafeMutablePointer<UInt8>
    
    open var size: Int = 0
    
    public init(size: Int) {
        self.mptr = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
        self.size = size
    }
    
    deinit {
        mptr.deallocate(capacity: size)
    }
    
}

  添加下标使其获得C语言指针的能力:

    open subscript(index: Int) -> UnsafeMutablePointer<UInt8> {
        return mptr.advanced(by: index)
    }

  最后完善Swift不可变指针属性和数据数组属性:

    open var iptr: UnsafePointer<UInt8> {
        get {
            return UnsafePointer<UInt8>.init(mptr)
        }
    }
    
    open var data: Array<UInt8> {
        get {
            var data = Array<UInt8>.init(repeating: 0, count: size)
            for i in 0..<size {
                data[i] = mptr.advanced(by: i).pointee
            }
            return data
        }
    }

有了这个类以后,我们不再使用allocate分配内存区块,由创建该对象接管。

stdio文件操作

stdio中对于文件的操作函数有很多,本框架主要用到如下几个函数:

public func fopen(_ __filename: UnsafePointer<Int8>!, _ __mode: UnsafePointer<Int8>!) -> UnsafeMutablePointer<FILE>!

public func fclose(_: UnsafeMutablePointer<FILE>!) -> Int32

public func fread(_ __ptr: UnsafeMutableRawPointer!, _ __size: Int, _ __nitems: Int, _ __stream: UnsafeMutablePointer<FILE>!) -> Int

public func fwrite(_ __ptr: UnsafeRawPointer!, _ __size: Int, _ __nitems: Int, _ __stream: UnsafeMutablePointer<FILE>!) -> Int

public func fseek(_: UnsafeMutablePointer<FILE>!, _: Int, _: Int32) -> Int32

public func ftell(_: UnsafeMutablePointer<FILE>!) -> Int

public func feof(_: UnsafeMutablePointer<FILE>!) -> Int32

由这些接口提供文件操作功能,在此基础上做Swift封装,以完成文件操作。

API

本库的接口分为了标准API以及扩展API,具体可以直接下载源码查看。

  • 标准API:基本文件操作接口,可以完成所有文件操作。
  • 扩展API:由标准API扩展的接口。例如readIntreadDouble等的简单封装,以及以协议扩展的readObjectwriteObject接口。

协议扩展

框架包含了两个可扩展的协议来完成用户自定义对象对文件的读取和写入。

  • EMFileStreamReadable
public protocol EMFileStreamReadable {
    init(stream: EMFileStream) throws
}

实现此协议后由文件流构造一个自定义对象。

  • EMFileStreamWriteable
public protocol EMFileStreamWriteable {
    public func emObjectWrite(withStream stream: EMFileStream) throws
}

实现此协议后将对象保存到文件流。

例:

Student类实现了文件流读写协议:

import Foundation
import EMFileStream

class Student: CustomStringConvertible, EMFileStreamReadable, EMFileStreamWriteable {
    
    var name: String
    var age: Int
    var source: Float
    var memo: String
    
    var description: String {
        return "name: \(name), age: \(age), source: \(source) memo: \(memo)"
    }
    
    init(name: String, age: Int, source: Float, memo: String) {
        self.name = name
        self.age = age
        self.source = source
        self.memo = memo
    }
    
    required init(stream: EMFileStream) throws {
        self.name = try stream.readString(withSize: 20)
        self.age = try stream.readInt()
        self.source = try stream.readFloat()
        self.memo = try stream.readString(withSize: 100)
    }
    
    func emObjectWrite(withStream stream: EMFileStream) throws {
        try stream.write(string: name, writeSize: 20)
        try stream.write(int: age)
        try stream.write(float: source)
        try stream.write(string: memo, writeSize: 100)
    }
}

保存该对象到文件流:

    let student = Student.init(name: "Sark", age: 20, source: 78.9, doubleSource: 90.12345, memo: "Memo..........")
    do {
        let file = try EMFileStream.init(path: path, mode: .writeBin)
        try file.write(object: student)
    } catch {
        print(error)
    }

从文件流读取该对象:

    do {
        let file = try EMFileStream.init(path: path, mode: .readBin)
        let student: Student = try file.readObject()
        print(student)
    } catch {
        print(error)
    }

测试结果: 16011_1

错误

stdio中发生的错误由私有方法swiftwrap_errormsg()截获,经过封装后抛出。所有抛出的错误类型都被封装成了EMError对象,其结构为:

open class EMError: Error, CustomStringConvertible {
    
    open var name: String
    open var detail: String
    
    public var description: String {
        return "name: \(name), detail: \(detail)"
    }
    
    public init(type: EMErrorType, detail: String) {
        name = type.rawValue
        self.detail = detail
    }
}

错误中包含了错误名称以及错误描述,以便开发者调试。

结语

这是一个简单的工具库,可以很方便的操作文件流。在文件太大全部读进内存操作不现实的情况下可以带来不错的效果。同时EMFileStreamReadableEMFileStreamWriteable协议可以带来NSCoding一部分功能,持久化归档对象到文件,又可以快速从文件恢复出对象。相对NSCoding来说,又有多平台通用性,同时也脱离了NSCoding类绑定的特性。希望能给Swift开发者们带来一丝便利。


Thanks for reading.

All the best wishes for you! 💕