Как я могу разорвать циклы сохранения в моем коде?


Недавно я был сброшен в чью-то кодовую базу, я был в состоянии справиться с большинством вещей, которые он бросил до сих пор, но этот немного выше моей головы. Есть некоторые циклы удержания, которые я не могу понять, как исправить.

Существует пользовательский объект, который обертывает FROAuthRequest, FROAuthRequest имеет блок завершения, в котором используются еще 3 блока, разбор, finish и fail блоков. Блоки завершения, финиша и отказа все вызывают цикл удержания.

Я знаю. что причина-ссылки на Иварса в блоке, но то, что я пробовал, не сработало, смотрите Конец сообщения для того, что я пробовал.

Следующий код выглядит так же, как и до того, как я начал его исправлять.Путь кода выглядит следующим образом:

1: Создайте запрос:

//in the MainViewController.m
SHRequest *request = [api userInfo];

2: Метод, который создает SHRequest

//in API.m
-(SHRequest*)userInfo{

    FROAuthRequest *request = [[FROAuthRequest alloc]initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@",SH_URL_API,SH_URL_USER_INFO]] 
                                                    consumer:consumer 
                                                       token:token 
                                                       realm:nil 
                                           signatureProvider:signatureProvider];

    //wrap the FROAuthRequest in our custom object
    //see below for the SHRequest 
    SHRequest *shRequest = [[SHRequest alloc]initWithRequest:request];

    //set the parsing block
    shRequest.parsingBlock = ^id(FROAuthRequest* finishedRequest){

        NSDictionary *jsonResponse = [finishedRequest.responseData objectFromJSONData];

        [user release];
        user = [[SHUser alloc]initWithJSON:[jsonResponse objectForKey:@"user"]];

        //more code

        return [NSDictionary dictionaryWithObjectsAndKeys:user,kUserKey,nil];
   };

   [request release];
   return [shRequest autorelease];
}

3: SHRequest

//in SHRequest.m
-(id)initWithRequest:(FROAuthRequest*)_underlyingRequest{
    if(self = [super init]){
        underlyingRequest = [_underlyingRequest retain];

        //this is the majority of the post processing
        underlyingRequest.completionBlock = ^{
            //if the requests fails call the fail block
            if(underlyingRequest.responseStatusCode != 200){
                if(failBlock != nil)
                    failBlock();
                return;
            }

            if([underlyingRequest.responseData length] > 0){
                [object release];
                object = parsingBlock(underlyingRequest);
                [object retain];

                if((underlyingRequest.error || !object) && failBlock != nil)
                    failBlock();
                else if(finishBlock != nil)
                    finishBlock();
            }else if(failBlock != nil)
                failBlock();
        };

        underlyingRequest.failedBlock = ^{
            if(failBlock)
                failBlock();
        };
    }
return self;
}

4: Как только SHRequest возвращается из метода userInfo (1), устанавливаются блоки finish и fail. (Для этого экземпляр no failBlock не установлен.

//in MainViewController.m
request.finishBlock = ^{
    NSDictionary *userInfo = request.object;

    //User
    SHUser *user = [userInfo objectForKey:kUserKey];

    //more code

};
[request send];

Вот что я попробовал
Я переместил код блока завершения в метод, который запускает запрос и использовал типы блоков__, и утечки, похоже, исчезли, но некоторые из _ _ block vars являются зомби, когда запускается блок завершения.

//in SHRequest.m
-(void)send{

    __block void(^fail)(void) = failBlock;
    __block void(^finish)(id) = finishBlock;
    __block id(^parsing)(FROAuthRequest*) = parsingBlock;
    __block FROAuthRequest *req = underlyingRequest;

    underlyingRequest.completionBlock = ^{

        if(req.responseStatusCode != 200){
            if(fail != nil)
                fail();
            return;
        }
        if([req.responseData length] > 0){
           id obj = parsing(req);//<--- parsing is a zombie 

            if((req.error || !obj) && fail != nil)
                fail();
            else if(finish != nil)
                finish(obj);//<--- finish is a zombie
        }else if(fail != nil)
            fail();
    };

    underlyingRequest.failedBlock = ^{
         if(fail)
             fail();
    };

    [underlyingRequest startAsynchronous];
}
Есть идеи по поводу того, что я делаю неправильно?
4 2

4 ответа:

Я был в восторге от блоков, когда они появились и написал почти каждый интерфейс с ними. Через некоторое время я осознал проблемы с управлением памятью и удобочитаемостью, что заставило меня вернуться к делегированию, если API Действительно не вызывает блоки. Может быть, это также сделает ваш код лучше, если это не слишком много кода, чтобы изменить.

Во-первых, если вы перерабатываете кодовую базу, я бы предложил воспользоваться возможностью перейти к ARC. Это значительно облегчит жизнь в отношении блоков.

Если вам все равно придется просеять весь код, это может стоить того... и инструменты преобразования Arc automation довольно хороши.

Даже тогда, однако, вы увидите циклы сохранения, но ARC plus __weak-это хороший способ разорвать циклы сохранения.

За исключением этого, вам придется иметь дело с каждым блоком стройте индивидуально, а не только по внешнему методу.

Если вы получаете EXC_BAD_ACCESS, то либо объект SHRequest исчез к моменту запуска блока завершения, либо (возможно, менее вероятно) какой-то код преждевременно очистил свойства блока завершения. Если бы Вы были нацелены на iOS 5, вы могли бы решить эту проблему, сделав переменные блока слабыми ссылками, чтобы они не вызывались, если жизненный цикл объекта SHRequest заканчивается до обратного вызова:

__weak void(^fail)(void) = failBlock;

Поскольку вы должны ориентироваться на iOS4, я думаю, что лучший вариант-переместить обратные вызовы в методы вместо свойств. В этом случае цикл сохранения все еще существует, но он узко ограничен выполнением underlyingRequest. Как только он выполняется и освобождает completionBlock и failedBlock, SHRequest может быть освобожден (как только ничто другое не удерживает его). Общая проблема со свойствами блока заключается в том, что self сохраняет блок, который сохраняет self, и единственный способ разорвать цикл-либо использовать слабую ссылку на self в блоке, либо явно свести к нулю свойства в какой-то момент, что может привести к нарушению цикла. будет трудно сделать в сценарии обратного вызова. Вот примерно как будет выглядеть ваш код с методами, возвращающими блоки обратного вызова:

-(void (^)(void))requestFinishedCallback {
    return [^{
        NSDictionary *userInfo = self.object;

        //User
        SHUser *user = [userInfo objectForKey:kUserKey];

        //more code
    } copy] autorelease];
}

-(void)send{
    underlyingRequest.completionBlock = ^{
        if(req.responseStatusCode != 200){
            [self requestFailedCallback]();
            return;
        }
        if([req.responseData length] > 0){
            id obj = [self parsingBlock](underlyingRequest);

            if (req.error || !obj) {
                [self requestFailedCallback]();
            }
            else {
                [self requestFinishedCallback]();
            }
        } else {
                [self requestFailedCallback]();
        }
    };

    underlyingRequest.failedBlock = [self requestFailedCallback];

    [underlyingRequest startAsynchronous];
}

Кстати, естьи другие веские причины для возврата блоков обратного вызова из методов .

Копирование блоков parsing / finish / fail и передача объекта запроса в качестве параметра для блока, похоже, решили мою проблему

-(void)send{

__block void(^fail)(void) = [failBlock copy];
__block void(^finish)(id) = [finishBlock copy];
__block id(^parsing)(FROAuthRequest*) = [parsingBlock copy];
__block FROAuthRequest *req = underlyingRequest;

underlyingRequest.completionBlock = ^{

    if(req.responseStatusCode != 200){
        if(fail != nil)
            fail();
        return;
    }
    if([req.responseData length] > 0){
       id obj = parsing(req);

        if((req.error || !obj) && fail != nil)
            fail();
        else if(finish != nil)
            finish(obj);
    }else if(fail != nil)
        fail();
};

underlyingRequest.failedBlock = ^{
     if(fail)
         fail();
};

[underlyingRequest startAsynchronous];
}

И в любом запросе, где мне нужна ссылка на ivar или сам запрос, я создаю блок var и сохраняю его, а затем освобождаю его внутри блоков finish / fail

__block SHRequest *req = [request retain];
request.finishBlock = ^(id object){

    NSDictionary *userInfo = object;

    //User
    SHUser *user = [userInfo objectForKey:kUserKey];

    //more code
    [req release];
};

request.failBlock = ^{
    if(req.requestStatusCode == 500)
        //do stuff
    [req release];
};

Таким образом, приборы больше не сообщают об утечках.