ios - 在 ARC 下使用 dispatch_write 将 NSData 写入后台唯一文件

标签 ios automatic-ref-counting grand-central-dispatch nsdata

我正在尝试创建一个具有唯一名称的文件并在后台向其写入数据。

mktemp只要有可能,就应该使用 mkstemp(),因为它没有竞争条件。

使用 mkstemp 会产生一个打开的文件描述符,因此 dispatch_write 似乎很明显。

现在 NSData 必须使用 dispatch_data_create 包装在 dispatch_data_t 中。必须注意释放需要释放的内存,保留必须保留的内存。在 ARC 下,这不太明显。

+ (void) createUnique:(NSData*)content name:(NSString*)name
            extension:(NSString*)extension
           completion:(void (^)(NSURL* url, NSError* error))completion {
    dispatch_queue_t queue = dispatch_get_global_queue(
                                       DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    dispatch_data_t data = dispatch_data_create(content.bytes, content.length,
                                                queue, ^{});
    // dispatch_data_create() copies the buffer if DISPATCH_DATA_DESTRUCTOR_DEFAULT
    // (= NULL) is specified, and attempts to free the buffer if
    // DISPATCH_DATA_DESTRUCTOR_FREE is specified, so an empty destructor is
    // specified.
    dispatch_fd_t descriptor;

    // Ignore details of creating the template C string
    strcpy(nameCString, templateCString);
    descriptor = mkstemps(nameCString, extensionLength);
    free(nameCString);

    if (descriptor != -1) {
        dispatch_write(descriptor, data, queue,
                       ^(dispatch_data_t data, int error) {
                           NSData* strongContent = content;
                           // Will this keep the NSData reference until the
                           // write is finished?

                           if (error) {
                               completion(nil, [NSError
                                                errorWithDomain:NSURLErrorDomain
                                                code:error userInfo:nil]);
                           } else {
                               // Ignore details of getting path from nameCString.
                               completion([NSURL URLWithString:path], nil);
                           }

                           // How does the file get closed?
                       });
    } else {
        completion(nil, [NSError errorWithDomain:NSURLErrorDomain code:errno
                                        userInfo:nil]);
    }
}

所以问题是:

  1. 这有必要吗? mktemp 是否应该与 NSDatawriteToFile:options:error: 一起使用而不用担心安全/竞争条件?
  2. 使用空析构函数调用 dispatch_data_create 是否可以避免不必要的复制(保留指向 NSData 缓冲区的指针)?
  3. mkstemps打开的描述符可以和dispatch_write一起使用吗?
  4. 保留对 NSData 的引用是否会使 dispatch_data_t 有效?这是必要的吗? ARC 在这里做什么?
  5. 文件是如何关闭的? dispatch_io_close?

最佳答案

这并不是 dispatch_write(和一般的 dispatch_data)的真正用途。正如您所发现的,dispatch_data 关注的是功能和性能,而不是易用性。而你有一个如此简单的问题。

另请注意,您正在讨论的竞争条件与正在您的临时目录中快速创建文件的活跃攻击者有关。攻击是这样的:

  • 您正在以某个特权用户的身份运行。 Eve(攻击者)正在运行一个非特权用户。
  • 你想在 /tmp 中创建一个你和 Eve 都可以读写的临时文件。
  • 您查看 /tmp 并发现某些文件名不存在
  • 在您创建文件之前,Eve 使用您刚刚检查过的名称创建了文件。她使文件世界可写(但它归 Eve 所有)。
  • 您现在打开文件并开始写入,但它仍归 Eve 所有。
  • 现在 Eve 可以读取和修改您的数据。这可能会转化为特权升级。

这是对 Unix 系统的真正攻击。很明显,这不是对 iOS 系统的真正攻击。这并不意味着您不应该使用 mkstemp。你应该。但重要的是要了解您要防范的是什么。这不是“哎呀;我撞到自己了”竞争条件,除非您每秒制作数百个文件(不要那样做)。

好的,那你是怎么做到的呢? Matt Gallagher 在 Cocoa with Love: Temporary files and folders in Cocoa 中有一个很好的例子.复制到这里以供将来的搜索者使用,但我强烈推荐这篇文章:

NSString *tempFileTemplate =
    [NSTemporaryDirectory() stringByAppendingPathComponent:@"myapptempfile.XXXXXX"];
const char *tempFileTemplateCString =
    [tempFileTemplate fileSystemRepresentation];
char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
strcpy(tempFileNameCString, tempFileTemplateCString);
int fileDescriptor = mkstemp(tempFileNameCString);

if (fileDescriptor == -1)
{
    // handle file creation failure
}

// This is the file name if you need to access the file by name, otherwise you can remove
// this line.
tempFileName =
    [[NSFileManager defaultManager]
        stringWithFileSystemRepresentation:tempFileNameCString
        length:strlen(tempFileNameCString)];

free(tempFileNameCString);
tempFileHandle =
    [[NSFileHandle alloc]
        initWithFileDescriptor:fileDescriptor
        closeOnDealloc:NO];

现在,在最后,您会看到 Matt 创建了一个文件名和一个 NSFileHandle。两者都可以使用。您可以使用 NSData 方法写入文件名,也可以使用 NSFileHandle 写入方法。此时不存在使用文件名的竞争条件,因为该文件已经存在并且归您所有。

要在后台编写它,只需将它放在 dispatch_async block 中即可。

关于ios - 在 ARC 下使用 dispatch_write 将 NSData 写入后台唯一文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30447381/

相关文章:

ios - Xcode 7.1 错误 UIAccessibility 错误

ios - 从对象数组中获取属性值数组

iphone - dispatch_once 调用导致崩溃

iphone - UITableView上的GCD实现

iphone - UIDatepicker 日期只显示星期一的日期

ios - 如何为 iOS 应用程序创建 iCloud 日历以与 swift 一起使用?

ios - ARC 不允许将非 Objective-C 指针类型 void* 隐式转换为 NSString*__strong*

iphone - 局部变量的ARC生命周期,过早释放

Swift函数在另一个函数完成后执行

Objective-C 和 Quartz Composer; [qcView PauseRendering] 导致 Bad_Access