2

最近、私は他の誰かのコードベースに落とされました。これまでに投げ出されたほとんどのことに取り組むことができましたが、これは少し頭がおかしいです。修正方法がわからない保持サイクルがいくつかあります。

FROAuthRequestをラップするカスタムオブジェクトがあります。FROAuthRequestには、解析、終了、失敗のブロックの3つのブロックが使用される完了ブロックがあります。完了、終了、および失敗のブロックはすべて、保持サイクルを引き起こしています。

原因はブロック内のivarへの参照であることがわかっていますが、試したものが機能しませんでした。試した内容については、投稿の最後を参照してください。

次のコードは、修正を開始する前の状態です。コードパスは次のようになります。

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:userInfoメソッドからSHRequestが返されると(1)、終了ブロックと失敗ブロックが設定されます。(この場合、failBlockは設定されていません。

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

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

    //more code

};
[request send];

これが私が試したものです
。completionBlockコードをリクエストを開始するメソッドに移動し、__ blockタイプを使用しました。リークは消えたように見えますが、完了ブロックの実行時に__block変数の一部はゾンビです。

//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

4 に答える 4

0

まず、コードベースを作り直している場合は、ARCに移行する機会を利用することをお勧めします。それはブロックに関して非常に楽になります。

とにかくすべてのコードをふるいにかける必要がある場合は、それだけの価値があるかもしれません...そしてARC自動化変換ツールはかなり優れています。

それでも、保持サイクルが表示されますが、ARCと__weakは保持サイクルを中断するための良い方法です。

それを除けば、外部メソッドだけでなく、各ブロック構成を個別に処理する必要があります。

于 2012-07-12T17:59:13.287 に答える
0

ブロックが表示され、ほとんどすべてのインターフェイスを作成したとき、私はブロックに興奮しました。その後、メモリ管理と可読性の問題に気づき、APIが実際にブロックを要求しない限り、委任に戻ることになりました。変更するコードが多すぎない場合は、コードの見栄えも良くなるかもしれません。

于 2012-07-12T14:04:34.747 に答える
0

EXC_BAD_ACCESSを取得している場合SHRequestは、完了ブロックが実行されるまでにオブジェクトがなくなっているか、(おそらく可能性は低いですが)一部のコードが完了ブロックのプロパティを時期尚早にクリアしています。SHRequestiOS 5をターゲットにしている場合は、ブロック変数を弱参照にすることで解決できるため、オブジェクトのライフサイクルがコールバックの前に終了した場合、ブロック変数は呼び出されません。

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

iOS4をターゲットにする必要があるため、コールバックをプロパティではなくメソッドに移動するのが最善のオプションだと思います。この場合、保持サイクルはまだ存在しますが、の実行に限定されunderlyingRequestます。実行して解放するcompletionBlockfailedBlock、SHRequestの割り当てを解除できます(他に何も保持されていない場合)。ブロックプロパティの一般的な問題は、自己が自己を保持するブロックを保持することです。サイクルを中断する唯一の方法は、ブロック内の自己への弱参照を使用するか、ある時点でプロパティを明示的に無効にすることです。コールバックシナリオで行うのは難しいです。コールバックブロックを返すメソッドを使用したコードは、おおよそ次のようになります。

-(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];
}

ちなみに、メソッドからコールバックブロックを返す理由は他にもいくつかあります。

于 2012-07-16T19:53:57.813 に答える
0

解析/終了/失敗ブロックをコピーし、ブロックのパラメータとしてリクエストオブジェクトを渡すことで、問題が解決したようです

-(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];
};

このようにして、Instrumentsはこれ以上リークを報告しません。

于 2012-08-10T11:50:47.237 に答える