我们知道,一个计算机程序,通常都涉及数据,且该数据是变动的。例如,即使是简单的记事本程序,也需要保存记事本的内容,当程序关闭然后再次打开,这些内容仍然需要显示出来。数据的保存需要文件读取和操作(也可能存储在数据库中)。而iOS和其它操作系统的程序的区别在于iOS通常使用沙盒机制,也就是说每个应用程序的数据文件不能随意选择位置存储,必须存储在这个应用程序对应的文件夹中。本文就通过一个简单的例子讲解用Swift编写iOS应用程序时如何进行数据文件操作。
这里使用iPad的Swift playground来设计iOS程序
一个iOS应用程序,除了代码外,还可以包含一些资源文件。资源文件可以通过点击下图的红圈的按钮从iPad中导入,导入后会加入Resources(下图蓝框)里。
导入资源文件
而这些文件,作为设计应用程序就加入的文件,在应用程序中是只读的。这些资源文件,当程序在iPad里安装后,会存放在该应用程序的Bundle文件夹里。要获取该应用程序的Bundle文件夹路径,应该使用语句Bundle.main.url(forResource: "File for test.txt", withExtension: nil)。此语句读取资源文件File for test.txt的路径。
这里用一个函数来读取资源文件的内容。函数如下:

func getDataString()->String{
    let dataURL = Bundle.main.url(forResource: "File for test.txt", withExtension: nil)
    //print(dataURL?.description)
    var dataString = ""
    do{
        dataString = try String(contentsOf: dataURL!)}
    
    catch{
        dataString = "0"
        fatalError("Can not find the file!")
    }
    return dataString
}

Swift中,读取文件内容的方式之一是String(contentsOf: URL)
话说,iOS应用程序中,资源文件的位置到底是哪个文件夹啊?这里可以通过Bundle.main.bundlePath找到。
现在设计一个简单的界面来显示出文件的内容,以及Bundle(资源文件)的文件夹位置。

struct fromFile: View {
    var body: some View {
        VStack{
            Text("Test resource:")
            Text(getDataString())
            Text("The bundle is in:")
            Text(Bundle.main.bundlePath)
        }
        
    }
}

struct fromFile_Previews: PreviewProvider {
    static var previews: some View {
        fromFile()
    }
}

Bundle文件和文件夹
资源文件File for test.txt的内容被显示出来了。此外,资源文件在已安装的iOS应用程序中的存放位置也显示了,是
/private/var/containers/Bundle/Application/…

刚才说到应用程序存储数据,需要在指定的目录进行读写操作。那么,把这个bundle目录作为存放程序数据的位置进行文件读写,可以吗?
这里尝试一下,写个函数进行文件创建操作:

func createNewFile(){
    let fm = FileManager()
    var fileURL:URL? = nil
    do{
        let fileURL = Bundle.main.bundleURL
    }
    catch{
        print("No file URL")
    }
    let dataToWrite = "I love yo".data(using: String.Encoding.utf8, allowLossyConversion: true)
    do{
        try "I love yo".write(to: fileURL!.appendingPathComponent("newfile.txt"), atomically: true, encoding: .utf8)
    }
    catch{
       print("Failed to write!")   
    }
    
}

界面上也加一个创建文件的按钮:

struct fromFile: View {
    

    var body: some View {
        VStack{
            Text("Test resource:")
            Text(getDataString())
            Text("The bundle is in:")
            Text(Bundle.main.bundlePath)
            Button("Create new file"){
                createNewFile()
            }
        }
        
    }
}

运行。注意:在iPad的Swift playground中,通常调试时需要查看后台(Console)。运行程序时,查看后台的方式是:从顶部向下划动屏幕,然后会弹出以下通知:
调试通知
点击该通知上的Swift Playground字,会弹出以下窗口调试弹窗
打开Show Console,控制台就会打开。
然后在应用程序界面上按下Create new file按钮(该按钮绑定了createNewFile()函数)
应用程序界面
点左下角的小按钮,显示控制台。
控制台
文件写入失败。失败的原因是:用于存放资源文件的Bundle文件夹是只读的,不能写文件。
那么,可读可写的数据文件存放在哪里呢?可以用Swift里的一个叫FileManager的类创建一个对象,然后用它的.url方法获取可读可写的数据文件的目录。详细内容参见:
FileManager | Apple developer documentation
这里贴上改写后的createNewFile()函数:

func createNewFile(){
    let fm = FileManager()
    var fileURL:URL? = nil
    do{
        fileURL = try fm.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: true)
    }
    catch{
        print("No file URL")
    }
    let dataToWrite = "I love yo".data(using: String.Encoding.utf8, allowLossyConversion: true)
    do{
        try "I love yo".write(to: fileURL!.appendingPathComponent("newfile.txt"), atomically: true, encoding: .utf8)
    }
    catch{
       print("Failed to write!")   
    }
    
}

这里,fileURL是通过FileManager的url()方法,产生的该应用程序的可读可写的文件夹,用于存放数据文件的。
此时点击Create new file,不会出现错误。
但是还是想确认一下,文件是否真的被添加进去了。可以创建一个新的函数getFiles()来列举数据文件目录下的所有文件。

func getFiles(){
    var ctString = ""
    var ctURL:URL? = nil
    do{
        let ct = FileManager()
        ctURL = try ct.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: true)
        let ctfind = try ct.contentsOfDirectory(atPath: ctURL!.path)
        ctString = ctfind.description
    }
    catch{
        print("Can not find directory")
    }
    print("The files in \(ctURL!.description) are \(ctString)")
    
}

FileManager的contentsOfDirectory()方法可以列出某个目录下的所有文件。现在添加一个按钮绑定函数getFiles()

struct fromFile: View {
    

    var body: some View {
        VStack{
            Text("Test resource:")
            Text(getDataString())
            Text("The bundle is in:")
            Text(Bundle.main.bundlePath)
            Button("Create new file"){
                createNewFile()
            }
            Button("Get Files (check console)"){
                getFiles()
            }
        }
        
    }
}

点击按钮Get Files (check console),后台里出现:

The files in file:///var/mobile/Containers/Data/Application/D6D0DF6F-AF5A-4FFB-A588-AD0B971BCAC1/Documents/ are ["newfile.txt"]

显然,这个新文件已经创建了。
也可以删除文件

func deleteFile(){
    let fm = FileManager()
    do{
        let fileURL = try fm.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false).appendingPathComponent("newfile.txt")
        try fm.removeItem(at: fileURL)
    }
    catch{
        print("It does not exist so no need to delete")
    }
}

总结一下,对于iOS程序,由于沙盒机制,数据文件不能随意存储,需存储在通过FileManager的url()方法找到的专用于存放数据文件的文件夹。Bundle文件夹是用于存放资源文件(即应用程序在设计时就自带的文件)的,只读不写,不能用于存放(动态)数据文件。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐