文件操作

(挖坑自己跳系列二)
上回说到文件操作,那就把文件操作来一遍吧,先放源码:GitHub某个地方

初始化 & 文件路径

所有的文件操作都要先声明一个根控制器以获得文件的句柄:

let manager = NSFileManager.defaultManager()

而文件路径的表示有两种方式:String和NSURL。在NSURL的视角中,本地文件作为一种特殊的网络文件而存在,以“file://”开头。多数的操作对于两种路径方式都有两个相对应的接口,要注意对于NSURL,表示本地文件时必须以“file://”开头,否则很多API会失败。为了测试方便,我给String加了个转换的扩展:

extension String {
    var toFilePathURL : NSURL {
    let url =  NSURL( fileURLWithPath: self)
    return url
    }
}

这里使用参数fileURLWithPath:而不是string:就是为了上述原因。
对于路径的修改,String表示方法很简单,直接相加即可;NSURL需要用以下代码:

你看到的是非授权版本!爬虫凶猛,请尊重知识产权!

转载请注明出处:http://conanwhf.github.io/2015/12/15/fileop/

访问原文「文件操作」获取最佳阅读体验并参与讨论

let newUrl = url.URLByAppendingPathComponent("/urlNewDir/", isDirectory: true)   
//当然false也是可以的,但要注意匹配,别把参数给错了

获取用户目录

由于Apple的沙盒机制,平时在iOS上能操作的文件也只存在于App当前用户目录、临时文件之类的地方而已,获得这个地址的方法是:

NSHomeDirectory()
NSHomeDirectoryForUser("Conan")

另外还有一些别的”NSXXXDirectory()”,大家自己可以去探索下。对于NSURL的方法如下:

let urlForDocument = manager.URLsForDirectory( NSSearchPathDirectory.DocumentDirectory, inDomains:NSSearchPathDomainMask.UserDomainMask)    //获得用户Document目录
let url = urlForDocument[0] as NSURL

返回的是Document目录。第一句其实是获得一个搜索集,目标由NSSearchPathDirectory.XXX参数指定,在我们知道只有一个搜索结果的情况下,可以直接指定数组中的[0]
在第一种方法中,我还没找到怎么拿到Document文件夹,谁知道麻烦留言告诉我,谢谢!🤗
在Playground中跑的时候,用户环境其实是在模拟器中,各种权限问题和奇怪的找不到文件之类,调试也不太方便,我就在此吃了点亏。建议有想不通错哪里的时候去终端跑一下,说不准就OK了(怎么跑见前一篇)。

从这里开始进入罗列模式,因为文件操作的很多API实在雷同。我默认文件路径方面已经没有问题了,且你也申明了文件的根控制器manager,那么基本上就是使用:manager.TODOWHAT(URL/PATH),如果有针对String和URL的不同API我会分别列出,写两次。

判断文件或者文件夹是否存在

首先是判断文件是否存在:

manager.fileExistsAtPath(workdir)       //文件夹
manager.fileExistsAtPath(workdir + "aaa.txt”) //文件

新建文件

创建文件夹

do {
try manager.createDirectoryAtPath(fn, withIntermediateDirectories: true, attributes: nil)
} catch {
print("Error by createDirectoryAtPath: \(error)\n")
}

do {
try manager.createDirectoryAtURL(fnUrl,     withIntermediateDirectories: true, attributes: nil)
} catch {
print("Error by createDirectoryAtURL: \(error)\n")
}

其中参数_withIntermediateDirectories_为true则表示路径中间如果有不存在的文件夹都会一并创建

创建普通文件

fn          = workdir + "try.txt"
manager.createFileAtPath(fn, contents: nil , attributes: nil)

创建符号链接

do {
try manager.createSymbolicLinkAtPath(workdir + "tryLink.txt", withDestinationPath: fn)
} catch {
    print("Error by createSymbolicLinkAtPath: \(error)\n")
}

do {
let linkurl = url.URLByAppendingPathComponent("link-url", isDirectory: false)
try manager.createSymbolicLinkAtURL(linkurl, withDestinationURL: fnUrl)
} catch {
print("Error by createSymbolicLinkAtURL: \(error)\n")
}

以上的文件创建,在默认的情况下如果已有目标文件存在则会失败;除了createFileAtPath会直接覆盖掉。

读取文件

读取文件的数据结果为NSData,如果需要变成可读的文字,转换成String即可:

let st      = String(data: data!, encoding: NSUTF8StringEncoding)

而读取文件有两种方式,一种直接获取全部数据:

data        = manager.contentsAtPath(fn)

另一种是使用文件句柄:

let handler = NSFileHandle(forReadingAtPath: fn)
data        = handler?.readDataToEndOfFile()

do {
    let handler = try NSFileHandle(forReadingFromURL: fnUrl)
    data        = handler.readDataToEndOfFile()
} catch {
    print("Error by NSFileHandle: \(error)\n")
}

第二种方式比较灵活,还有别的参数可用,通常结合写数据用来修改文件

将数据写入文件

整体数据写入

可以通过writeToFile方法,创建并将数据整体写入文件。支持的数据对象包括String,NSString,UIImage,NSArray,NSDictionary等。

字符串

let info        = "测试数据1234"
do {
try info.writeToFile(fn + "_string.txt", atomically: true, encoding: NSUTF8StringEncoding)
} catch {
print("Error by writeToFile: \(error)\n")
}

图片

let image       = UIImage(named: workdir + "/test/207006981.jpg”)//这里只是先load一下
let data:NSData = UIImagePNGRepresentation(image!)!
data.writeToFile(fn + "_img.jpg", atomically: true)

数组

let array       = NSArray(objects: "aaa","bbb","ccc")
array.writeToFile(fn + "_arr.txt", atomically: true)

字典

let dictionary  = NSDictionary(objects: ["111","222"], forKeys: ["aaa","bbb"]
dictionary.writeToFile(fn + "_dic.txt", atomically: true)

末尾添加、修改文件

修改文件跟C的用法很像,使用文件句柄,结合seek到的某个位置,写入数据。需要注意的是没有专门的插入数据方法,所以如果你想插入数据,就需要把后面的数据都出来(或者另存下来),添加数据,再续上之前的后半截。所以如果数据量大,下面的暴力方法不合适,需要用callback结合缓存去做读写。

末尾添加数据

let handler = NSFileHandle(forUpdatingAtPath: fn)
guard handler != nil else {
    print("No such file")
    return
}
string      = "\n用forUpdatingAtPath在文件末尾添加XXX"
data        = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!
handler!.seekToEndOfFile()
handler!.writeData(data)

插入数据

var string  = "\n用forUpdatingAtURL在文件第10个字节插入XXX\n"
var data    = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!
do {
    let handler = try NSFileHandle(forUpdatingURL: fnUrl)
    handler.seekToFileOffset(10)
    let data2   = handler.readDataToEndOfFile()
    handler.seekToFileOffset(10)
    handler.writeData(data)
    handler.writeData(data2)
} catch {
    print("Error by NSFileHandle: \(error)\n")
}

这里两种方式用了不同的句柄获得方式,实际上可以随意互换。

复制文件(夹)

do {
    try manager.copyItemAtPath(src, toPath: dest)
} catch let error as NSError {
    print("Error by Path: \(error)\n")
}

do {
    try manager.copyItemAtURL( srcUrl, toURL: destUrl)
} catch let error as NSError {
    print("Error by URL: \(error)\n")
}

移动文件(夹)

do {
    try manager.moveItemAtPath(src, toPath: dest)
} catch {
print("Error by Path: \(error)\n")
}

do {
    try manager.moveItemAtURL( srcUrl, toURL: destUrl)
} catch  {
    print("Error by URL: \(error)\n")
}

删除文件(夹)

do {
    try manager.removeItemAtPath(fn)
} catch {
    print("Error by Path: \(error)\n")
}

do {
    try manager.removeItemAtURL( fn.toFilePathURL)
} catch  {
    print("Error by URL: \(error)\n")
}   

遍历文件夹

遍历文件夹有两种方式:遍历当前文件夹,和递归遍历子文件夹。下面是范例。注意:使用Path为参数的时候返回的是相对路径和文件名,而使用URL方式时返回的是绝对路径!

不遍历子文件夹

do {
let contentsOfPath   = try manager.contentsOfDirectoryAtPath(dir)
    contentsOfPath.forEach{ print($0) }
} catch {
    print("Error by 1-1: \(error)\n")
}

do {
let contentsOfURL    = try manager.contentsOfDirectoryAtURL(url, includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions.SkipsHiddenFiles)
    contentsOfURL.forEach{ print($0) }
} catch {
    print("Error by 1-2: \(error)\n")
}

递归遍历子文件夹,但不递归符号链接

let enumeratorAtPath = manager.enumeratorAtPath(dir)
if enumeratorAtPath == nil  {
    print("Error by 2-1: no such folder: \(dir)")
    //return
}
else {
    enumeratorAtPath!.forEach{ print($0) }
}

let enumeratorAtURL  = manager.enumeratorAtURL(url,includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions.SkipsHiddenFiles, errorHandler:nil)
if enumeratorAtURL == nil  {
    print("Error by 2-2: no such folder: \(url)")
    //return
}
else {
    enumeratorAtURL!.forEach{ print($0) }
}

递归遍历子文件夹,包括符号链接

let subpathsAtPath   = manager.subpathsAtPath(dir)
if subpathsAtPath == nil  {
    print("-Error by 3: no such folder: \(dir)")
    //return
}
else {
    subpathsAtPath!.forEach{ print($0) }
}

这个函数有点危险,可能一不小心就死循环了,建议慎用。

获取文件属性和权限

权限判断

let readable   = manager.isReadableFileAtPath(fn)
let writeable  = manager.isWritableFileAtPath(fn)
let executable = manager.isExecutableFileAtPath(fn)
let deleteable = manager.isDeletableFileAtPath(fn)
print("文件\(fn) \(readable ? "" : "不")可读, \(writeable ? "" : "不")可写,\(executable ? "" : "不")可执行,\(deleteable ? "" : "不")可删除")

获取文件属性

do {
let attributes = try manager.attributesOfItemAtPath(fn)
        print("attributes: \(attributes)")
} catch {
    print("Error by attributesOfItemAtPath: \(error)\n")
}

比较文件(夹)

manager.contentsEqualAtPath(workdir + "read.md", andPath: workdir + "copied.txt")   //文件
manager.contentsEqualAtPath(workdir, andPath: workdir + "/tesst/")  //文件夹

这个函数不是diff,只能判断是否相同,不能做内容对比