คำตอบสั้น ๆ
แทนที่จะเข้าถึงself
โดยตรงคุณควรเข้าถึงโดยทางอ้อมจากข้อมูลอ้างอิงที่จะไม่ถูกเก็บไว้ หากคุณไม่ได้ใช้การนับการอ้างอิงอัตโนมัติ (ARC)คุณสามารถทำได้:
__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
__block
ตัวแปรเครื่องหมายคำหลักที่สามารถปรับเปลี่ยนภายในบล็อก (เราไม่ได้ทำอย่างนั้น) แต่ยังพวกเขาจะไม่ถูกเก็บไว้โดยอัตโนมัติเมื่อบล็อกจะถูกเก็บไว้ (ยกเว้นกรณีที่คุณกำลังใช้ ARC) หากคุณทำเช่นนี้คุณต้องแน่ใจว่าไม่มีสิ่งใดที่จะพยายามดำเนินการบล็อกหลังจากที่อินสแตนซ์ MyDataProcessor ถูกนำออกใช้ ( ป.ร. ให้โครงสร้างของรหัสของคุณที่ไม่ควรจะมีปัญหา.) อ่านข้อมูลเพิ่มเติมเกี่ยวกับ__block
หากคุณใช้ ARCความหมายของ__block
การเปลี่ยนแปลงและการอ้างอิงจะถูกเก็บไว้ซึ่งในกรณีนี้คุณควรประกาศ__weak
แทน
คำตอบที่ยาว
สมมติว่าคุณมีรหัสดังนี้:
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
ปัญหาของที่นี่คือตัวมันเองยังคงมีการอ้างอิงถึงบล็อก; ในขณะที่บล็อกจะต้องเก็บรักษาการอ้างอิงถึงตนเองเพื่อดึงคุณสมบัติของผู้รับมอบสิทธิ์และส่งวิธีการมอบหมาย หากทุกอย่างในแอปของคุณปล่อยการอ้างอิงไปยังวัตถุนี้จำนวนการเก็บรักษาจะไม่เป็นศูนย์ (เพราะบล็อกชี้ไปที่มัน) และบล็อกนั้นไม่ได้ทำสิ่งใดผิด (เพราะวัตถุนั้นชี้ไปที่มัน) คู่ของวัตถุจะรั่วไหลเข้าไปในกองที่ครอบครองหน่วยความจำ แต่ไม่สามารถเข้าถึงได้ตลอดไปโดยไม่มีการดีบัก น่าสลดใจจริงๆ
กรณีนี้สามารถแก้ไขได้ง่ายโดยการทำเช่นนี้แทน:
id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
[progressDelegate processingWithProgress:percentComplete];
}
ในรหัสนี้ตัวเองกำลังรักษาบล็อกบล็อกกำลังรักษาผู้รับมอบสิทธิ์และไม่มีรอบ (มองเห็นได้จากที่นี่; ผู้รับมอบสิทธิ์อาจเก็บวัตถุของเรา แต่ที่อยู่ในมือของเราตอนนี้) รหัสนี้จะไม่เสี่ยงต่อการรั่วไหลในลักษณะเดียวกันเนื่องจากค่าของคุณสมบัติผู้รับมอบสิทธิ์ถูกจับเมื่อมีการสร้างบล็อกแทนที่จะค้นหาเมื่อดำเนินการ ผลข้างเคียงคือถ้าคุณเปลี่ยนผู้รับมอบสิทธิ์หลังจากที่สร้างบล็อกนี้บล็อกจะยังคงส่งข้อความอัปเดตไปยังผู้รับมอบสิทธิ์เดิม ไม่ว่าจะเกิดขึ้นหรือไม่นั้นขึ้นอยู่กับใบสมัครของคุณ
แม้ว่าคุณจะเจ๋งกับพฤติกรรมแบบนั้นคุณก็ยังไม่สามารถใช้เคล็ดลับนั้นได้ในกรณีของคุณ:
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
ที่นี่คุณกำลังส่งself
ต่อไปยังผู้รับมอบสิทธิ์โดยตรงในวิธีการโทรดังนั้นคุณต้องเข้าไปที่นั่นสักแห่ง หากคุณมีอำนาจควบคุมคำจำกัดความของประเภทบล็อกสิ่งที่ดีที่สุดคือการส่งผู้แทนเข้าสู่บล็อกเป็นพารามิเตอร์:
self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};
วิธีนี้จะหลีกเลี่ยงรอบการเก็บข้อมูลและจะเรียกผู้รับมอบสิทธิ์ปัจจุบันเสมอ
หากคุณไม่สามารถเปลี่ยนบล็อกได้คุณสามารถจัดการกับมันได้ เหตุผลที่วัฏจักรการเก็บรักษาเป็นคำเตือนไม่ใช่ข้อผิดพลาดคือพวกเขาไม่จำเป็นต้องสะกดคำลงโทษสำหรับแอปพลิเคชันของคุณ หากMyDataProcessor
สามารถปล่อยบล็อกได้เมื่อการดำเนินการเสร็จสิ้นก่อนที่ผู้ปกครองจะพยายามปล่อยมันวงจรจะพังและทุกอย่างจะถูกล้างอย่างถูกต้อง หากคุณมั่นใจในสิ่งนี้สิ่งที่ถูกต้องที่ควรทำคือใช้#pragma
เพื่อยับยั้งคำเตือนสำหรับบล็อคโค้ดนั้น (หรือใช้แฟล็กคอมไพเลอร์ต่อไฟล์ แต่อย่าปิดใช้งานคำเตือนสำหรับโครงการทั้งหมด)
คุณสามารถใช้เคล็ดลับที่คล้ายกันข้างต้นประกาศการอ้างอิงที่อ่อนแอหรือไม่ได้รับและการใช้สิ่งนั้นในบล็อก ตัวอย่างเช่น:
__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
ทั้งสามข้อข้างต้นจะให้การอ้างอิงแก่คุณโดยไม่เก็บผลลัพธ์แม้ว่าพวกเขาทั้งหมดจะทำงานแตกต่างกันเล็กน้อย: __weak
จะพยายามอ้างอิงที่ศูนย์เมื่อปล่อยวัตถุ __unsafe_unretained
จะทำให้คุณมีตัวชี้ที่ไม่ถูกต้อง __block
จะเพิ่มอีกระดับหนึ่งทางอ้อมและอนุญาตให้คุณเปลี่ยนค่าการอ้างอิงจากภายในบล็อก (ไม่เกี่ยวข้องในกรณีนี้เนื่องจากdp
ไม่ได้ใช้งานที่อื่น)
สิ่งที่ดีที่สุดจะขึ้นอยู่กับรหัสที่คุณสามารถเปลี่ยนแปลงได้และสิ่งที่คุณไม่สามารถทำได้ แต่หวังว่านี่จะทำให้คุณมีความคิดในการดำเนินการต่อไป