为何Safari扩展中SFSafariPage类型属性会变为nil?
SFSafariPage Reference Turns Nil After Assignment in messageReceived Problem Description
I'm running into a confusing issue while developing a Safari extension. Here's my code:
class SafariExtensionHandler: SFSafariExtensionHandler { var somePage: SFSafariPage? override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) { // Triggered when content script calls safari.extension.dispatchMessage("message") if messageName == "script injected successfully" { somePage = page NSLog(String(describing: somePage)) // This logs a valid SFSafariPage instance } } func performPlayerAction(_ action: PlayerAction) { NSLog("page %@", String(describing: somePage)) // This logs nil somePage?.dispatchMessageToScript(withName: action.rawValue, userInfo: nil) } }
When the messageReceived method gets the "script injected successfully" message from the content script, I assign the incoming page to the somePage property, and the log confirms it's a valid instance. But when I call performPlayerAction later to use somePage, it's suddenly nil. Can anyone explain why this happens?
Answer
Let's break down the root cause and fix this step by step:
Why somePage Turns Nil
This boils down to how Safari manages extension objects and their lifecycle:
Weak Reference Design of
SFSafariPage
Safari uses weak references forSFSafariPageinstances. If the corresponding tab/page context gets recycled (like when the page refreshes, the tab is switched away from long enough for memory cleanup, or the page navigates to a new URL), theSFSafariPageproxy object gets destroyed, and your strong reference to it becomes nil.SFSafariExtensionHandlerInstance Volatility
The extension handler isn't a persistent singleton. Safari may create or destroy handler instances based on system resource constraints (e.g., if the extension's background process gets suspended and restarted). When this happens, any stored properties likesomePageget reset.Cross-Process Communication Limits
Content scripts and the extension's background run in separate processes.SFSafariPageacts as a cross-process proxy—if the connection between processes drops, this proxy becomes invalid and resolves to nil.
Fixes You Can Implement
Here are a few practical solutions:
Avoid Storing
SFSafariPageLong-Term
Instead of holding onto a page reference, fetch the active page when you need to send a message:func performPlayerAction(_ action: PlayerAction) { SFSafariApplication.getActiveWindow { window in window?.getActiveTab { tab in tab?.getActivePage { page in page?.dispatchMessageToScript(withName: action.rawValue, userInfo: nil) } } } }Track Pages Using
pageID
If you need to target a specific page, save its uniquepageIDinstead of the page instance, then look it up when needed:var targetPageID: UUID? override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) { if messageName == "script injected successfully" { targetPageID = page.pageID } } func performPlayerAction(_ action: PlayerAction) { guard let targetID = targetPageID else { return } SFSafariApplication.getAllWindows { windows in for window in windows { window.getAllTabs { tabs in for tab in tabs { tab.getActivePage { page in if page?.pageID == targetID { page?.dispatchMessageToScript(withName: action.rawValue, userInfo: nil) } } } } } } }Let Content Scripts Initiate Communication
Instead of relying on a stored page reference, have your background script broadcast a request, and let relevant content scripts respond to establish a fresh connection before you send the action command.
内容的提问来源于stack exchange,提问作者Nikolai Nagornyi

