แก้ไขคำเตือน“ การจับ [วัตถุ] อย่างยิ่งในบล็อกนี้มีแนวโน้มที่จะนำไปสู่การรักษารอบ” ในรหัสที่เปิดใช้งาน ARC


141

ในรหัสที่เปิดใช้งาน ARC จะแก้ไขคำเตือนเกี่ยวกับวงจรการรักษาที่อาจเกิดขึ้นได้อย่างไรเมื่อใช้ API แบบบล็อก

คำเตือน:
Capturing 'request' strongly in this block is likely to lead to a retain cycle

ผลิตโดยโค้ดขนาดสั้นนี้:

ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.rawResponseData error:nil];
    // ...
    }];

คำเตือนเชื่อมโยงกับการใช้วัตถุrequestภายในบล็อก


1
คุณควรจะใช้responseDataแทนrawResponseDataตรวจสอบเอกสาร ASIHTTPRequest
0xced

คำตอบ:


165

ตอบกลับตัวเอง:

ความเข้าใจเกี่ยวกับเอกสารของฉันบอกว่าการใช้คำหลักblockและการตั้งค่าตัวแปรเป็นศูนย์หลังจากใช้ภายในบล็อกควรจะใช้ได้ แต่ก็ยังคงแสดงคำเตือน

__block ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.responseData error:nil];
    request = nil;
// ....

    }];

อัปเดต:ทำให้มันทำงานกับคำหลัก '_ อ่อนแอ' แทน ' _block' และใช้ตัวแปรชั่วคราว:

ASIHTTPRequest *_request = [[ASIHTTPRequest alloc] initWithURL:...
__weak ASIHTTPRequest *request = _request;

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.responseData error:nil];
    // ...
    }];

หากคุณต้องการที่จะกำหนดเป้าหมาย iOS 4 ใช้แทน__unsafe_unretained __weakพฤติกรรมเดียวกัน แต่ตัวชี้ยังคงห้อยอยู่แทนที่จะถูกตั้งค่าเป็นศูนย์โดยอัตโนมัติเมื่อวัตถุถูกทำลาย


8
จากเอกสาร ARC ดูเหมือนว่าคุณจะต้องใช้ __unsafe_unretained __block ร่วมกันเพื่อให้ได้พฤติกรรมเหมือนเดิมเมื่อใช้ ARC และบล็อก
ฮันเตอร์

4
@SeanClarkHess: เมื่อฉันรวมสองบรรทัดแรกฉันได้รับคำเตือนนี้: "การกำหนดออบเจ็กต์ที่เก็บไว้ให้กับตัวแปรที่อ่อนแอและออบเจ็กต์จะถูกปล่อยหลังจากการมอบหมาย"
Guillaume

1
@ Guillaume ขอบคุณสำหรับการตอบสนองบางวิธีที่ฉันมองข้ามตัวแปรชั่วคราวลองมันและคำเตือนจะหายไป คุณรู้ไหมว่าทำไมสิ่งนี้ถึงได้ผล มันเป็นเพียงการหลอกให้คอมไพเลอร์เพื่อยับยั้งการเตือนหรือเป็นคำเตือนจริงไม่ได้อีกต่อไป?
Chris Wagner

2
ฉันโพสต์คำถามติดตามแล้ว: stackoverflow.com/questions/8859649/…
barfoon

3
มีคนอธิบายได้ไหมว่าทำไมคุณต้องการคำหลัก __block และ __ ที่อ่อนแอ ฉันเดาว่าจะมีการสร้างวงจรการเก็บรักษา แต่ฉันไม่เห็น และการสร้างตัวแปรชั่วคราวจะช่วยแก้ปัญหาได้อย่างไร
user798719

50

ปัญหานี้เกิดขึ้นเนื่องจากคุณกำลังกำหนดบล็อกให้กับคำขอที่มีการอ้างอิงที่รัดกุมเพื่อร้องขอ บล็อกจะเก็บคำขอโดยอัตโนมัติดังนั้นคำขอดั้งเดิมจะไม่ยกเลิกการจัดสรรเนื่องจากรอบ ทำให้รู้สึก?

เป็นเรื่องแปลกเพราะคุณติดแท็กคำขอวัตถุด้วย __block เพื่อให้สามารถอ้างถึงตัวมันเอง คุณสามารถแก้ไขปัญหานี้โดยการสร้างการอ้างอิงอ่อนแอควบคู่ไปกับมัน

ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...];
__weak ASIHTTPRequest *wrequest = request;

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:wrequest.rawResponseData error:nil];
    // ...
    }];

__weak ASIHTTPRequest * wrequest = ขอ; ไม่ได้ผลสำหรับฉัน ข้อผิดพลาดในการให้ฉันใช้ __block ASIHTTPRequest * blockRequest = คำขอ;
Ram G.

13

มันเกิดจากการรักษาตัวเองในบล็อก บล็อกจะเข้าถึงได้จากตัวเองและตัวเองถูกอ้างอิงในบล็อก สิ่งนี้จะสร้างรอบการเก็บข้อมูล

ลองแก้ปัญหานี้โดยสร้างการอ้างอิงที่อ่อนแอ self

__weak typeof(self) weakSelf = self;

operationManager = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operationManager.responseSerializer = [AFJSONResponseSerializer serializer];
[operationManager setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

    [weakSelf requestFinishWithSucessResponseObject:responseObject withAFHTTPRequestOperation:operation andRequestType:eRequestType];

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    [weakSelf requestFinishWithFailureResponseObject:error withAFHTTPRequestOperation:operation andRequestType:eRequestType];
}];
[operationManager start];

นี่เป็นคำตอบที่ถูกต้องและควรสังเกตว่าเป็นเช่นนี้
Benjamin

6

บางครั้งคอมไพเลอร์ xcode มีปัญหาในการระบุวงรอบการเก็บรักษาดังนั้นหากคุณแน่ใจว่าคุณไม่ได้รักษาความสมบูรณ์ของบล็อคคุณสามารถใส่ค่าสถานะคอมไพเลอร์ดังนี้:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"

-(void)someMethod {
}

1
บางคนอาจแย้งว่ามันเป็นการออกแบบที่ไม่ดี แต่บางครั้งฉันก็สร้างวัตถุอิสระที่ออกไปเที่ยวในหน่วยความจำจนกว่ามันจะเสร็จสิ้นด้วยภารกิจอะซิงโครนัส พวกเขาจะถูกเก็บไว้ในทรัพย์สินที่เสร็จสมบูรณ์บล็อกซึ่งมีการอ้างอิงที่แข็งแกร่งเพื่อตนเองสร้างวงจรการเก็บรักษาโดยเจตนา เสร็จสิ้นการล็อคประกอบด้วย self.completionBlock = nil ซึ่งเผยแพร่เสร็จสมบูรณ์บล็อกและแบ่งวงจรการเก็บรักษาที่ช่วยให้วัตถุที่จะออกจากหน่วยความจำเมื่องานเสร็จสมบูรณ์ คำตอบของคุณมีประโยชน์ในการช่วยเงียบคำเตือนที่เกิดขึ้นเมื่อฉันทำเช่นนี้
hyperspasm

1
พูดตามตรงโอกาสในการเป็นคนที่ถูกต้องและคอมไพเลอร์ไม่ถูกต้องมีขนาดเล็กมาก ดังนั้นฉันจะบอกว่าเพียงแค่คำเตือนที่เหนือกว่าคือธุรกิจที่มีความเสี่ยง
Max MacLeod

3

เมื่อฉันลองใช้วิธีแก้ปัญหาของ Guillaume ทุกอย่างใช้ได้ดีในโหมด Debug แต่มันล้มเหลวในโหมด Release

โปรดทราบว่าอย่าใช้ __ อ่อนแอ แต่ __unsafe_unretained เนื่องจากเป้าหมายของฉันคือ iOS 4.3

รหัสของฉันล้มเหลวเมื่อ setCompletionBlock: ถูกเรียกบนวัตถุ "คำขอ": คำขอถูกจัดสรรคืน ...

ดังนั้นโซลูชันนี้ทำงานได้ทั้งในโหมด Debug และ Release:

// Avoiding retain cycle :
// - ASIHttpRequest object is a strong property (crashs if local variable)
// - use of an __unsafe_unretained pointer towards self inside block code

self.request = [ASIHttpRequest initWithURL:...
__unsafe_unretained DataModel * dataModel = self;

[self.request setCompletionBlock:^
{
    [dataModel processResponseWithData:dataModel.request.receivedData];        
}];

ทางออกที่น่าสนใจ คุณทราบหรือไม่ว่าเหตุใดจึงขัดข้องในโหมด Release และไม่อยู่ใน Debug
Valerio Santinelli


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.