преждевременное освобождение в ARC-приложении - PullRequest
5 голосов
/ 05 февраля 2012

У меня есть проблема, которая, по-видимому, является преждевременным выпуском используемого объекта в приложении на основе ARC. Я пытаюсь создать папку на FTP-сервере. Соответствующие части кода приведены ниже; Сначала я опишу проблему.

проблема с кодом в том, что вывод отладки в

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

метод никогда не вызывается.

Вместо этого я просто получаю ошибку _EXC_BAD_ACCESS_. Во время отладки я обнаружил две вещи:

  1. ошибка появляется только при выполнении следующей строки кода (метод createDir):

    [ftpStream open];
    

если это сообщение не отправлено, остальная часть кода на самом деле не имеет смысла - но он также не падает ...

  1. Я отслеживал EXC_BAD_ACCESS с помощью NSZombieEnabled: при включенных объектах-зомби GDB выдает следующую информацию отладчика:

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

Указанный адрес 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 ]

3 голосов
/ 05 февраля 2012

Мне кажется, что единственная ссылка на ваш FTPUploads объект - это свойство delegate в потоке. Это не сохранит ваш объект, поэтому, если ничто иное не имеет ссылки на объект, объект будет освобожден. A.R.C. не пытается предотвратить этот сценарий.

Вам нужно иметь код, который выделяет объект FTPUploads, сохраняя ссылку на объект до его завершения.

Также было бы неплохо установить для свойства ftpStream.delegate значение nil в вашем методе FTPUploads dealloc, так как это предотвратит сбой, если объект был отменен преждевременно.

1 голос
/ 05 февраля 2012

Проблема в том, что ваш ftpStream объект освобожден.Вы создаете его с CFWriteStreamCreateWithFTPURL(), а затем отпускаете с CFRelease().Вы использовали __bridge приведение, что в основном означает «не занимайтесь управлением памятью в этом назначении».Таким образом, ARC не сохранил его, когда вы присвоили его ftpStream.Поскольку ваше намерение состояло в том, чтобы передать право собственности с CF на ARC, это было неправильное использование.

Вы действительно хотели либо __bridge_retained, либо __bridge_transfer.Я никогда не могу вспомнить, что есть что, хотя.К счастью, есть еще один вариант - макросы CFBridgingRetain() и CFBridgingRelease().Они разрешаются вплоть до тех же самых бросающих мостов, но названы гораздо более четко.

В этом случае вы хотите, чтобы CF выпустил его, но соединил его с ARC.Итак, вы хотите CFBridgingRelease().Это скажет ARC взять на себя ответственность за объект, а затем сделать CFRelease.Короче говоря, замените это:

ftpStream = (__bridge NSOutputStream *) writeStreamRef;

на это:

ftpStream = CFBridgingRelease(writeStreamRef);

И затем удалите вызов на CFRelease() несколькими строками позже.

0 голосов
/ 05 февраля 2012

Полагаю, вам следует либо дождаться окончания потока, чтобы выполнить CFRelease(writeStreamRef), либо выполнить __bridge_transfer, чтобы перенести владение на ftpStream, прежде чем выпускать writeStreamRef

...