基于ARC的应用程序中的早期dealloc

时间:2012-02-05 14:59:10

标签: objective-c automatic-ref-counting dealloc

我有一个问题似乎是在基于ARC的应用程序中过早发布正在使用的对象。我正在尝试在FTP服务器上创建一个文件夹。代码的相关部分如下;我将首先描述问题。

代码的问题是,

中的调试输出
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
永远不会调用

方法。

相反,我只是得到_EXC_BAD_ACCESS_错误。 调试时我发现了两件事:

  1. 只有在执行以下代码行(createDir方法)时才会出现错误:

    [ftpStream open];
    
  2. 如果没有发送该消息,其余的代码实际上没有意义 - 但它也不会崩溃......

    1. 我使用NSZombieEnabled跟踪EXC_BAD_ACCESS:启用僵尸对象后,GDB会生成以下调试器信息:

       *** -[FTPUploads respondsToSelector:]: message sent to deallocated instance 0x9166590
      
    2. 引用的地址 0x9166590 是我的FTPUploads对象的地址。 在委托处理消息之前,它似乎已被释放。

      为什么系统会释放正在使用的对象?如何防止它过早解除分配?

      代码:

      FTPUploads.h摘录:

      #import <Foundation/Foundation.h>
      
      enum UploadMode {
      
          UploadModeCreateDir, 
          UploadModeUploadeData
      };
      
      @class UploadDatasetVC;
      
      @interface FTPUploads : NSObject<NSStreamDelegate> {
      
          @private
          NSString *uploadDir;
          NSString *ftpUser;
          NSString *ftpPass;
      
          NSString *datasetDir;
          NSArray *files;
      
          /* FTP Upload fields */
          NSInputStream *fileStream;
          NSOutputStream *ftpStream;
          // some more fields...
          enum UploadMode uploadMode;
          UploadDatasetVC *callback;
      }
      
      - (id) initWithTimeseriesID: (int) aTimeseriesID 
                   fromDatasetDir: (NSString *) aDir
                        withFiles: (NSArray *) filesArg 
                andCallbackObject: (UploadDatasetVC *) aCallback;
      
      - (void) createDir;
      
      @end
      

      FTPUploads.m摘录

      #import "FTPUploads.h"
      #import "UploadDatasetVC"
      
      @implementation FTPUploads
      
      - (id) initWithTimeseriesID: (int) aTimeseriesID 
                   fromDatasetDir: (NSString *) aDir
                        withFiles: (NSArray *) filesArg 
                andCallbackObject: (UploadDatasetVC *) aCallback {
      
          self = [super init];
      
          if (self) {
      
              uploadDir = [NSString stringWithFormat: @"ftp://aServer.org/%i/", aTimeseriesID];
              ftpUser = @"aUser";
              ftpPass = @"aPass";
      
                  datasetDir = aDir;
                  files = filesArg;
      
              bufferOffset = 0;
              bufferLimit = 0;
      
              index = 0;
      
              callback = aCallback;
          }
      
          return self;
      }
      
      - (void) createDir {
      
          uploadMode = UploadModeCreateDir;
          NSURL *destinationDirURL = [NSURL URLWithString: uploadDir];
      
          CFWriteStreamRef writeStreamRef = CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) destinationDirURL);
          assert(writeStreamRef != NULL);
      
          ftpStream = (__bridge_transfer NSOutputStream *) writeStreamRef;
          [ftpStream setProperty: ftpUser forKey: (id)kCFStreamPropertyFTPUserName];
          [ftpStream setProperty: ftpPass forKey: (id)kCFStreamPropertyFTPPassword];
      
          ftpStream.delegate = self;
          [ftpStream scheduleInRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
          // open stream
          [ftpStream open];
      
          CFRelease(writeStreamRef);
      }
      
      - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
      
          NSLog(@"aStream has an event: %i", eventCode);
      
          switch (eventCode) {
              // all cases handled properly
              default:
                  // no event
                  NSLog(@"default mode; no event");
                  break;
          }
      }
      

      编辑:添加了在类UploadDatasetVC中使用的创建代码:

      FTPUploads *uploads = [[FTPUploads alloc] initWithTimeseriesID: timeseries_id 
                                                      fromDatasetDir: datasetDir 
                                                           withFiles: files 
                                                   andCallbackObject: self];
      [uploads createDir];
      

3 个答案:

答案 0 :(得分:3)

在我看来,对FTPUploads对象的唯一引用是流上的delegate属性。这不会保留您的对象,因此如果没有其他内容对该对象有引用,则该对象将被释放。弧。并不试图阻止这种情况。

您需要做的是让分配FTPUploads对象的代码保持对对象的引用,直到它完成。

ftpStream.delegate dealloc方法中将FTPUploads属性设置为nil也不是一个坏主意,因为如果对象被过早释放,这将防止崩溃。

答案 1 :(得分:1)

问题是您的ftpStream对象正在被释放。您使用CFWriteStreamCreateWithFTPURL()创建它,然后使用CFRelease()发布它。您使用了__bridge强制转换,这基本上意味着“不对此分配执行任何内存管理”。因此,当您将其分配给ftpStream时,ARC不会保留它。由于您的目的是将所有权从CF转移到ARC,这是错误的演员使用。

您实际上想要__bridge_retained__bridge_transfer。我永远不会记得哪个是哪个。幸运的是,还有另一个选项 - CFBridgingRetain()CFBridgingRelease()宏。他们决定使用相同的桥接演员,但命名得更清楚。

在这种情况下,您希望CF释放它,但将其桥接到ARC。所以你想要CFBridgingRelease()。这将告诉ARC获取对象的所有权,然后执行CFRelease。简而言之,替换为:

ftpStream = (__bridge NSOutputStream *) writeStreamRef;

用这个:

ftpStream = CFBridgingRelease(writeStreamRef);

然后在几行之后删除对CFRelease()的调用。

答案 2 :(得分:0)

我的猜测是,您应该等到流完成后才能CFRelease(writeStreamRef),或者在发布ftpStream之前执行__bridge_transfer将所有权转移到writeStreamRef