3

我有一款遊戲是用iOS7中的新SpriteKit編寫的。我有一個自定義的SKSpriteNode可以獲取並顯示Facebook個人資料圖片。但是,因爲加載圖片可能需要一些時間。當我初始化節點並僅在圖片加載時顯示它時,我試圖在後臺加載圖片。這裏是我寫的代碼片段:EXC_BAD_ACCESS當在dispatch_async中使用「釋放」self時

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 


    // Code to load Facebook Profile picture 
    // ... 
    SKSpriteNode *fbFrame = [SKSpriteNode spriteNodeWithTexture:facebookPicTexture]; 
    dispatch_async(dispatch_get_main_queue(), ^{ 


     // self is my customised SKSpriteNode - so here I just add a sprite node of 
     // a facebook picture to myself as a child  
     [self addChild:fbFrame]; 
    }); 

}); 

它正常工作正常。但是,如果Facebook配置文件圖片的加載速度較慢,則在加載圖片時,用戶可能已經切換到另一個屏幕。在這種情況下,self實際上將從場景層次結構中移除,並且不會參考它。

當我讀取塊doc時,我認爲異步塊將保留self,所以我認爲它在主線程塊被調用時仍然有效。事實證明,如果pic加載非常慢,並且在從層次結構中刪除self時調用了第二個dispatch_async,則會在行[self addChild:fbFrame]處發生錯誤的訪問錯誤。

我理解塊存儲器管理不正確嗎?有沒有辦法解決這種問題?

+0

「如果圖片加載速度非常慢,並且在自我從層次結構中移除時調用第二個dispatch_async,則會在行[self addChild:fbFrame]處發生錯誤的訪問錯誤。」如果從層次結構中刪除「self」,爲什麼會出現訪問不良的情況。這是沒有意義的部分。只要'self'指向一個有效的對象(這裏保證),發送消息不應該崩潰。 – newacct

回答

7

您對存儲器管理的理解是正確的,在該塊中存在self將保留self,直到完成調度塊。解決的辦法是當塊運行時,通過使用weak參考self塊中不保留self:隱含通過直接引用伊娃

__weak typeof(self) weakSelf = self; 

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

    // do some stuff in background here; when done, then: 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     [weakSelf addChild:fbFrame]; 
    }); 
}); 

應查看任何引用該塊self(顯式或),並用weakSelf替換它們。

或者,(在這種情況下不太可能),有時你會看到weakSelf/strongSelf模式,你必須確保self是不是嵌套塊的執行過程中釋放:

__weak typeof(self) weakSelf = self; 

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

    // do some stuff in background here; when done, then: 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     typeof(self) strongSelf = weakSelf; 
     if (strongSelf) { 
      // stuff that requires that if `self` existed at the start of this block, 
      // that it won't be released during this block 
     } 
    }); 
}); 

順便說一下,另一種方法是取消視圖控制器關閉時的網絡請求。這需要進行更爲重大的更改(使用基於NSOperation的方法而不是GCD;或者使用基於NSURLSessionTask的方法,以允許您取消請求),但這是解決此問題的另一種方法。

+0

這與EXEC_BAD_ACCESS有什麼關係? – newacct

+0

@newacct上面並沒有試圖診斷爲什麼'SKSpriteNode'方法'addChild'正在生成'EXC_BAD_ACCESS',但僅僅是阻止了首先導致「EXC_BAD_ACCESS」的情況。將SKSpriteNode放在周圍是很愚蠢的,這樣即使用戶移動了,也可以添加子項(如果你這樣做,SpriteKit可能不喜歡它......我不太瞭解SpriteKit推測)。恕我直言,對於網絡操作掛在用戶界面對象(無論是UIKit還是SpriteKit)超出該用戶界面對象的自然生命週期是不明智的。 – Rob

+0

@Rob,謝謝你。經過更多的診斷後,我發現了一個更有趣的發現:由於難以一致地模擬一個緩慢的網絡,我已經人爲地增加了一個等待,以減緩加載操作。你猜怎麼了?該應用程序沒有崩潰!所以崩潰實際上並不一致。無論如何,正如你所說,弱自我解決方案可以防止任何原因 - 所以我會試一試。 –

-1

我想你需要這樣寫:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul); 

objc_setAssociatedObject(self, @"yourTag", @"Alive", OBJC_ASSOCIATION_RETAIN); 

dispatch_async(queue, ^{  
    // Code to load Facebook Profile picture 
    // ... 
    SKSpriteNode *fbFrame = [SKSpriteNode spriteNodeWithTexture:facebookPicTexture]; 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     NSString *strAlive = (NSString *)objc_getAssociatedObject(self, @"yourTag"); 
     if (strAlive) 
     { 
      // self is my customised SKSpriteNode - so here I just add a sprite node of 
      // a facebook picture to myself as a child  
      [self addChild:fbFrame]; 
     } 
    }); 

}); 
dispatch_release(queue); 

當你不想調度繼續過程時自我(視圖控制器)是不可見的了,所以這樣寫:

objc_setAssociatedObject(self, @"yourTag", nil, OBJC_ASSOCIATION_RETAIN); 

在: - viewWillDisapear- viewDidDisapear 並且當你回到屏幕時更新調用調度。

+1

'self'由塊保留,因此無法通過在塊中間檢查的位置釋放其分配方式 – newacct

+0

正確!我將這段代碼用於別的東西,並沒有意識到那樣是錯的 –

相關問題