การจับตัวเองอย่างมากในบล็อกนี้มีแนวโน้มที่จะนำไปสู่วงจรการเก็บรักษา


207

ฉันจะหลีกเลี่ยงคำเตือนนี้ใน xcode ได้อย่างไร นี่คือข้อมูลโค้ด:

[player(AVPlayer object) addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
queue:nil usingBlock:^(CMTime time) {
    current+=1;

    if(current==60)
    {
        min+=(current/60);
        current = 0;
    }

    [timerDisp(UILabel) setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];///warning occurs in this line
}];

เป็นtimerDispสถานที่ให้บริการในชั้นเรียนหรือไม่?
ทิม

ใช่ @ คุณสมบัติ (ไม่ใช่แบบเชิงกลแรง) UILabel * timerDisp;
user1845209

2
นี้คืออะไร: player(AVPlayer object)และtimerDisp(UILabel)?
Carl Veazey

เครื่องเล่น AVPlayer *; UILabel * timerDisp;
user1845209

5
คำถามจริงคือวิธีที่จะเงียบคำเตือนนี้โดยไม่ต้องอ้างอิงอ่อนแอด้วยตนเองที่ไม่จำเป็นเมื่อคุณรู้ว่าการอ้างอิงแบบวงกลมจะถูกทำลาย (เช่นถ้าคุณล้างการอ้างอิงเสมอเมื่อคำขอเครือข่ายเสร็จสิ้น)
Glenn Maynard

คำตอบ:


514

จับของselfที่นี่คือที่มาในที่มีการเข้าถึงทรัพย์สินของคุณโดยนัยของself.timerDisp- คุณไม่สามารถอ้างถึงselfหรือคุณสมบัติในการจากภายในบล็อกที่จะถูกเก็บรักษาไว้อย่างยิ่งด้วยselfself

คุณสามารถแก้ไขได้โดยสร้างการอ้างอิงที่อ่อนแอselfก่อนที่จะเข้าถึงtimerDispภายในบล็อกของคุณ:

__weak typeof(self) weakSelf = self;
[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                     queue:nil
                                usingBlock:^(CMTime time) {
                                                current+=1;

                                                if(current==60)
                                                {
                                                    min+=(current/60);
                                                    current = 0;
                                                }

                                                 [weakSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
                                            }];

13
ลองใช้__unsafe_unretainedแทน
ทิม

63
การแก้ไข ใช้สิ่งนี้แทน: __unsafe_unretained typeof (self) weakSelf = self; ขอบคุณสำหรับความช่วยเหลือ
@Tim

1
คำตอบที่ดี แต่ฉันหยิบเรื่องเล็ก ๆ ขึ้นมาพูดว่า:“ คุณไม่สามารถอ้างถึงตัวเองหรือคุณสมบัติของตัวเองจากภายในบล็อกที่จะถูกเก็บรักษาไว้อย่างแรงด้วยตัวเอง” สิ่งนี้ไม่เป็นความจริงอย่างเคร่งครัด โปรดดูคำตอบของฉันด้านล่าง ดีกว่าที่จะพูดว่า“ คุณจะต้องระมัดระวังเป็นอย่างมากหากคุณอ้างถึงตัวเอง…”
Chris Suter

8
ฉันไม่เห็นวงจรการรักษาในรหัสของ OP บล็อกไม่ได้ถูกเก็บรักษาไว้อย่างแน่นหนาselfแต่ยังคงอยู่ในคิวส่งหลัก ฉันผิดหรือเปล่า?
erikprice

3
@erikprice: คุณไม่ผิด ฉันตีความคำถามเป็นหลักเกี่ยวกับข้อผิดพลาด Xcode แสดง ("ฉันจะหลีกเลี่ยงคำเตือนนี้ใน xcode") ได้อย่างไรแทนที่จะเกี่ยวกับสถานะที่แท้จริงของวัฏจักรการเก็บรักษา คุณถูกต้องในการบอกว่าไม่มีวงจรการเก็บรักษาที่ชัดเจนจาก OP ตัวอย่างที่ให้ไว้
ทิม

52
__weak MyClass *self_ = self; // that's enough
self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
    if (!error) {
       [self_ showAlertWithError:error];
    } else {
       self_.items = [NSArray arrayWithArray:receivedItems];
       [self_.tableView reloadData];
    }
};

และสิ่งหนึ่งที่สำคัญมากที่ต้องจำ: อย่าใช้ตัวแปรอินสแตนซ์โดยตรงในบล็อกใช้เป็นคุณสมบัติของวัตถุที่อ่อนแอตัวอย่าง:

self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
        if (!error) {
           [self_ showAlertWithError:error];
        } else {
           self_.items = [NSArray arrayWithArray:receivedItems];
           [_tableView reloadData]; // BAD! IT ALSO WILL BRING YOU TO RETAIN LOOP
        }
 };

และอย่าลืมที่จะทำ:

- (void)dealloc {
    self.loadingCompletionHandler = NULL;
}

ปัญหาอื่นสามารถปรากฏขึ้นได้หากคุณจะส่งสำเนาที่อ่อนแอของวัตถุที่ไม่ได้เก็บไว้

MyViewController *vcToGo = [[MyViewCOntroller alloc] init];
__weak MyViewController *vcToGo_ = vcToGo;
self.loadingCompletion = ^{
    [vcToGo_ doSomePrecessing];
};

ถ้าvcToGoจะถูกยกเลิกการจัดสรรและบล็อกนี้ถูกไล่ออกฉันเชื่อว่าคุณจะได้รับความเสียหายจากตัวเลือกที่ไม่รู้จักไปยังถังขยะซึ่งมีvcToGo_ตัวแปรอยู่ในขณะนี้ พยายามควบคุมมัน


3
นี่จะเป็นคำตอบที่ดีกว่าถ้าคุณอธิบายด้วย
Eric J.

43

รุ่นที่ดีกว่า

__strong typeof(self) strongSelf = weakSelf;

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

ดังนั้นสิ่งทั้งหมดจะเป็นเช่นนี้:

// Establish the weak self reference
__weak typeof(self) weakSelf = self;

[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                 queue:nil
                            usingBlock:^(CMTime time) {

    // Establish the strong self reference
    __strong typeof(self) strongSelf = weakSelf;

    if (strongSelf) {
        [strongSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
    } else {
        // self doesn't exist
    }
}];

ฉันได้อ่านบทความนี้หลายครั้ง นี่เป็นบทความที่ยอดเยี่ยมโดยErica Sadunเกี่ยวกับ วิธีหลีกเลี่ยงปัญหาเมื่อใช้ Blocks และ NSNotificationCenter


อัพเดตอย่างรวดเร็ว:

ตัวอย่างเช่นในวิธีที่รวดเร็วพร้อมบล็อกความสำเร็จจะเป็นดังนี้:

func doSomeThingWithSuccessBlock(success: () -> ()) {
    success()
}

เมื่อเราเรียกวิธีนี้และจำเป็นต้องใช้selfในบล็อกความสำเร็จ เราจะใช้[weak self]และguard letฟีเจอร์ต่างๆ

    doSomeThingWithSuccessBlock { [weak self] () -> () in
        guard let strongSelf = self else { return }
        strongSelf.gridCollectionView.reloadData()
    }

Alamofireนี้เรียกว่าการเต้นที่แข็งแกร่งที่อ่อนแอจะถูกใช้โดยโครงการที่นิยมเปิดแหล่งที่มา

สำหรับข้อมูลเพิ่มเติมตรวจสอบคู่มือสไตล์ swift


จะเป็นอย่างไรถ้าคุณทำtypeof(self) strongSelf = self;นอกบล็อก (แทนที่จะเป็น __weak) จากนั้นในบล็อกพูดstrongSelf = nil;หลังจากใช้งาน ฉันไม่เห็นว่าตัวอย่างของคุณทำให้มั่นใจได้อย่างไรว่าอ่อนแอตัวเองไม่ได้เป็นศูนย์ตามเวลาที่บล็อกดำเนินการ
Matt

เพื่อหลีกเลี่ยงการเก็บข้อมูลที่เป็นไปได้รอบเราสร้างการอ้างอิงตนเองที่อ่อนแอนอกบล็อกใด ๆ ที่ใช้ตัวเองในรหัส ในทางของคุณคุณต้องตรวจสอบให้แน่ใจว่าบล็อกถูกดำเนินการ ขณะนี้บล็อคของรหัส ur อื่นมีหน้าที่รับผิดชอบในการเพิ่มหน่วยความจำที่เก็บไว้ก่อนหน้านี้
Warif Akhand Rishi

@Matt วัตถุประสงค์ของตัวอย่างนี้คือการไม่ทำให้อ่อนแอตัวเองไว้ จุดประสงค์คือถ้าอ่อนแอตัวเองไม่เป็นศูนย์ทำการอ้างอิงที่แข็งแกร่งภายในบล็อก ดังนั้นเมื่อบล็อกเริ่มดำเนินการด้วยตนเองตัวเองจะไม่กลายเป็นศูนย์ภายในบล็อก
Warif Akhand Rishi

15

ในคำตอบอื่นทิมพูดว่า:

คุณไม่สามารถอ้างถึงตัวเองหรือคุณสมบัติของตัวเองจากภายในบล็อกที่จะถูกเก็บรักษาด้วยตนเองอย่างยิ่ง

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

ในกรณีของฉันตอนนี้ฉันมีคำเตือนสำหรับรหัสที่ทำ:

[x setY:^{ [x doSomething]; }];

ตอนนี้ฉันรู้แล้วว่าเสียงดังกราวจะสร้างคำเตือนนี้เฉพาะเมื่อตรวจพบวิธีที่ขึ้นต้นด้วย“ set” (และอีกกรณีพิเศษหนึ่งที่ฉันจะไม่พูดถึงที่นี่) สำหรับฉันฉันรู้ว่าไม่มีอันตรายจากการมีห่วงวนการวนซ้ำดังนั้นฉันจึงเปลี่ยนชื่อวิธีเป็น“ useY:” แน่นอนว่าอาจไม่เหมาะสมในทุกกรณีและโดยปกติคุณจะต้องการใช้การอ้างอิงที่อ่อนแอ แต่ ฉันคิดว่ามันคุ้มค่าที่จะแก้ปัญหาของฉันในกรณีที่มันช่วยเหลือผู้อื่น


4

หลายครั้งนี้ไม่ได้เป็นจริงรักษาวงจร

หากคุณรู้ว่ามันไม่ได้คุณไม่จำเป็นต้องนำจุดอ่อนที่ไร้ผลมาสู่โลก

แอปเปิ้ลยังบังคับให้คำเตือนเหล่านี้เมื่อพวกเรากับ API ของพวกเขาUIPageViewControllerซึ่งรวมถึงวิธีการตั้งค่า(ซึ่งก่อให้เกิดคำเตือนเหล่านี้ - ดังกล่าวที่อื่น ๆ - คิดว่าคุณกำลังตั้งค่าให้กับ ivar นั่นคือบล็อก)และบล็อกจัดการเสร็จสมบูรณ์ คุณจะอ้างถึงตัวคุณเองอย่างแน่นอน)

ต่อไปนี้เป็นคำสั่งของคอมไพเลอร์เพื่อลบคำเตือนออกจากโค้ดหนึ่งบรรทัด:

#pragma GCC diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
    [self.pageViewController setViewControllers:@[newViewController] direction:navigationDirection animated:YES completion:^(BOOL finished) {
        // this warning is caused because "setViewControllers" starts with "set…", it's not a problem
        [self doTheThingsIGottaDo:finished touchThePuppetHead:YES];
    }];
#pragma GCC diagnostic pop

1

เพิ่มสองเซ็นต์ในการปรับปรุงความแม่นยำและสไตล์ ในกรณีส่วนใหญ่คุณจะใช้เพียงหนึ่งหรือสองสมาชิกselfในบล็อกนี้มีแนวโน้มมากที่สุดเพียงเพื่อปรับปรุงตัวเลื่อน กำลังคัดเลือกนักแสดงselfมากเกินไป แต่จะเป็นการดีกว่าหากคุณมีความชัดเจนและส่งเฉพาะวัตถุที่คุณต้องการอย่างแท้จริงในบล็อก ตัวอย่างเช่นถ้ามันเป็นตัวอย่างของUISlider*พูด_timeSliderเพียงทำต่อไปนี้ก่อนที่จะประกาศบล็อก:

UISlider* __weak slider = _timeSlider;

จากนั้นใช้sliderภายในบล็อก selfเทคนิคนี้เป็นที่แม่นยำมากขึ้นในขณะที่มันแคบลงศักยภาพในการรักษาวงจรเพียงวัตถุที่คุณต้องการไม่ได้ทั้งหมดวัตถุภายใน

ตัวอย่างเต็มรูปแบบ:

UISlider* __weak slider = _timeSlider;
[_embeddedPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)
     queue:nil
     usingBlock:^(CMTime time){
        slider.value = time.value/time.timescale;
     }
];

นอกจากนี้ส่วนใหญ่แล้ววัตถุที่ถูกส่งไปยังตัวชี้จุดอ่อนนั้นเป็นตัวชี้จุดอ่อนภายในอยู่แล้วselfรวมถึงการย่อหรือกำจัดโอกาสในการวนรอบที่สมบูรณ์ ในตัวอย่างข้างต้น_timeSliderเป็นทรัพย์สินที่จัดเก็บเป็นข้อมูลอ้างอิงที่อ่อนแอเช่น:

@property (nonatomic, weak) IBOutlet UISlider* timeSlider;

ในแง่ของรูปแบบการเข้ารหัสเช่นเดียวกับ C และ C ++ การประกาศตัวแปรจะอ่านได้ดีขึ้นจากขวาไปซ้าย ประกาศในลำดับนี้อ่านขึ้นตามธรรมชาติจากขวาไปซ้ายเป็น:SomeType* __weak variablevariable is a weak pointer to SomeType


1

ฉันวิ่งเข้าหาคำเตือนนี้เมื่อเร็ว ๆ นี้และต้องการที่จะเข้าใจมันได้ดีขึ้นเล็กน้อย หลังจากการทดลองใช้และข้อผิดพลาดเล็กน้อยฉันพบว่ามันมาจากการเริ่มต้นด้วยวิธีการ "เพิ่ม" หรือ "บันทึก" Objective C ปฏิบัติต่อชื่อเมธอดที่ขึ้นต้นด้วย "new", "alloc" ฯลฯ เป็นการส่งคืนออบเจกต์ที่เก็บไว้ แต่ไม่ได้กล่าวถึง อย่างไรก็ตามถ้าฉันใช้ชื่อวิธีด้วยวิธีนี้:

[self addItemWithCompletionBlock:^(NSError *error) {
            [self done]; }];

ฉันจะเห็นคำเตือนที่บรรทัด [ทำเอง] อย่างไรก็ตามสิ่งนี้จะไม่:

[self itemWithCompletionBlock:^(NSError *error) {
    [self done]; }];

ฉันจะไปข้างหน้าและใช้วิธี "__ อ่อนแอ __typeof (self) weakSelf = self" เพื่ออ้างอิงวัตถุของฉัน แต่จริงๆแล้วไม่ชอบที่จะทำเช่นนั้นเพราะมันจะสร้างความสับสนในอนาคตฉันและ / หรือ dev อื่น ๆ แน่นอนว่าฉันไม่สามารถใช้ "เพิ่ม" (หรือ "บันทึก") ได้ แต่มันแย่กว่าเนื่องจากมันใช้ความหมายของวิธีการออกไป

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