2012-02-05 16 views
5

Tôi gặp sự cố có vẻ như là bản phát hành sớm của đối tượng được sử dụng trong ứng dụng dựa trên ARC. Tôi đang cố tạo thư mục trên máy chủ FTP. Các phần mã có liên quan dưới đây; tôi sẽ mô tả vấn đề trước.dealloc sớm trong ứng dụng dựa trên ARC

vấn đề với mã là, kết xuất debug trong phương pháp

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode 

không bao giờ được gọi.

Thay vào đó, tôi chỉ gặp lỗi _EXC_BAD_ACCESS_. Trong khi gỡ lỗi tôi phát hiện ra hai điều:

  1. lỗi chỉ xuất hiện nếu dòng mã sau đây (createDir phương pháp) được thực hiện:

    [ftpStream open]; 
    

nếu thông điệp rằng không được gửi , phần còn lại của mã không thực sự hợp lý - nhưng nó không bị hỏng ...

  1. Tôi theo dõi các EXC_BAD_ACCESS xuống với NSZombieEnabled: Với đối tượng zombie kích hoạt, GDB tạo ra các thông tin gỡ lỗi sau:

    *** -[FTPUploads respondsToSelector:]: message sent to deallocated instance 0x9166590 
    

Địa chỉ gọi 0x9166590 là địa chỉ của FTPUploads tôi phản đối. Có vẻ như các đại biểu luồng được deallocated trước khi nó có thể xử lý tin nhắn.

Tại sao hệ thống lại phân phối một đối tượng đang sử dụng? Làm thế nào tôi có thể ngăn chặn nó được deallocated sớm?

mã:

FTPUploads.h trích đoạn:

#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 trích

#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; 
    } 
} 

EDIT: thêm mã tạo được sử dụng trong lớp UploadDatasetVC:

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

Các đối tượng thường không giữ lại (hoặc trong trường hợp vòng cung, giữ lại các tham chiếu mạnh mẽ cho) các đại biểu của họ. Dường như với tôi như vấn đề sẽ được với mã mà tạo ra đối tượng FtpUploads của bạn và những gì nó sau đó không với nó –

+0

Tôi đang thêm mã đó trong một đoạn trích khác của câu hỏi. cũng thấy bình luận của tôi về câu trả lời đầu tiên. –

+0

Điều gì xảy ra sau đó? Nếu tải lên chỉ là một biến địa phương khi nó đi ra khỏi phạm vi ARC sẽ zap nó –

Trả lời

3

Dường như với tôi giống như tham chiếu duy nhất cho đối tượng FTPUploads của bạn là thuộc tính delegate trên luồng. Điều này sẽ không giữ lại đối tượng của bạn, vì vậy nếu không có gì khác có một tham chiếu đến đối tượng, đối tượng sẽ được dealloced. A.R.C. không cố gắng ngăn chặn tình huống này.

Những gì bạn cần làm là có mã phân bổ đối tượng FTPUploads giữ tham chiếu đến đối tượng cho đến khi hoàn thành. Bạn cũng có thể không phải là một ý tưởng tồi khi đặt thuộc tính ftpStream.delegate thành 0 trong phương thức dealloc FTPUploads của bạn, vì điều này sẽ ngăn chặn sự cố nếu đối tượng được xử lý sớm.

+0

không; mã gọi để tạo đối tượng FTPUploads giữ tham chiếu đầu tiên cho đối tượng đó? trong phương thức 'createDir' của i set 'self' làm đại biểu - ok. nhưng tham chiếu đầu tiên được tạo trong UploadDatasetVC, nơi tôi tạo đối tượng FTPUploads hte: 'FTPUploads * uploads = [[FTPUploads alloc] initWithTimeseriesID: ​​timeseries_id fromDatasetDir: datasetDir withFiles: tệp andCallbackObject: self]; [tải lên createDir]; ' –

+0

Hầu hết các thuộc tính đại biểu được đặt là "yếu" hoặc "chỉ định", có nghĩa là chúng không được tính vào số lần giữ lại. Biến 'uploads' của bạn sẽ chỉ được tính nếu nó tiếp tục tồn tại cho đến khi nào kết nối được thực hiện, điều mà tôi đoán là không. – grahamparks

+1

ok, một cách giải quyết là lưu đối tượng FTPUploads đã sử dụng làm thuộc tính có tham chiếu mạnh. đây là những gì làm việc tốt nhất cho tôi; thêm dòng sau vào UploadDataset.h: '@property (strong) FTPUploads * tải lên;' và điều chỉnh việc tạo đối tượng trong UploadDataset.h theo đó –

0

tôi đoán là bạn nên hoặc là đợi cho đến khi dòng kết thúc để làm CFRelease(writeStreamRef), hoặc làm một __bridge_transfer chuyển quyền sở hữu đối với ftpStream trước khi bạn phát hành

+0

một dự đoán sai. lý do là việc khai báo sai FTPUploads chỉ là một biến cục bộ (xem mã tạo UploadDatasetVC trong câu hỏi gốc). bạn vẫn đúng với sự sai lầm của tôi '__bridge_transfer' mà sẽ có turnged ra được một lỗi. tôi chỉ cố định điều đó, cảm ơn! –

1

vấn đề của bạn writeStreamRef ở đây là đối tượng ftpStream của bạn đang bị deallocated. Bạn tạo nó với CFWriteStreamCreateWithFTPURL(), sau đó thả nó với CFRelease(). Bạn đã sử dụng phép đúc __bridge, về cơ bản có nghĩa là "không thực hiện bất kỳ quản lý bộ nhớ nào đối với nhiệm vụ này". Vì vậy, ARC đã không giữ lại nó khi bạn gán nó cho ftpStream. Vì ý định của bạn là chuyển quyền sở hữu từ CF sang ARC, đó là do dàn diễn viên sai sử dụng.

Bạn thực sự muốn hoặc là __bridge_retained hoặc __bridge_transfer. Tôi không bao giờ có thể nhớ đó là cái gì, mặc dù. May mắn thay, có một tùy chọn khác — các macro CFBridgingRetain()CFBridgingRelease(). Họ giải quyết xuống những phôi tương tự, nhưng được đặt tên rõ ràng hơn nhiều.

Trong trường hợp này, bạn muốn CF giải phóng, nhưng chuyển nó qua ARC. Vì vậy, bạn muốn CFBridgingRelease(). Điều đó sẽ cho ARC biết quyền sở hữu đối tượng và sau đó thực hiện một CFRelease. Nói tóm lại, thay thế này:

ftpStream = (__bridge NSOutputStream *) writeStreamRef; 

với điều này:

ftpStream = CFBridgingRelease(writeStreamRef); 

Và sau đó loại bỏ các cuộc gọi đến CFRelease() một vài dòng sau.

+0

sai. như đã nêu trong câu hỏi ban đầu của tôi vấn đề là đối tượng FTPUploads đang được deallocated - không phải là ftpStream! đó chỉ là một lỗi khác trong mã của tôi chứ không phải vấn đề ban đầu. cảm ơn vì gợi ý gợi ý –

+1

Ồ, đúng vậy. Vâng, sau khi bạn sửa chữa (mà tôi thấy rằng bạn đã thực hiện), đây sẽ là vấn đề tiếp theo của bạn. –