Archimedes for Mac uses (WebKit 1) WebViews to render live previews of documents. While working on improved cache management for the next release of Archimedes, I noticed WebView
allocations were not going down as documents were being closed. What’s more, the PreviewViewController
s that own the WebView
s were also not being deallocated.
I had a hunch retain cycles were to blame, so I launched Instruments and ran the Allocations instrument with the Record reference counts option turned on. Immediately, it became clear what was happening. PreviewViewController
, which maintains a strong reference to the WebView
, was being exported to JavaScriptCore
as a JSExport
as such:
- (void)webView:(WebView *)webView didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame {
frame.javaScriptContext[@"archimedes"] = self;
}
Here, JavaScriptCore
was creating a strong reference back to the PreviewViewController
, thus creating a strong reference cycle.
I tried unloading the page right before the document closes hoping the WebView
would relinquish control of the PreviewViewController
, but it did not work as I expected.
Next, instead of passing the entire view controller to JavaScriptCore
, I decided to create a new class whose sole purpose is to relay messages to a weakly-referenced delegate object:
@protocol PreviewViewMessengerDelegate
- (void)previewViewMessengerWantsToGoToRange:(NSRange)range;
@end
@interface PreviewViewMessenger : NSObject <PreviewViewExport>
@property (nonatomic, weak) id <PreviewViewMessengerDelegate> delegate;
@end
@implementation PreviewViewMessenger
- (void)goToRange:(int)location length:(int)length {
[self.delegate previewViewMessengerWantsToGoToRange:NSMakeRange(location, length)];
}
@end
And the JavaScriptContext
was now set up as follows:
- (void)webView:(WebView *)webView didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame {
PreviewViewMessenger *messenger = [[PreviewViewMessenger alloc] init];
messenger.delegate = self;
frame.javaScriptContext[@"archimedes"] = messenger;
}
Now, while JavaScriptCore
still retains the proxy object, it no longer retains our PreviewViewController
since it is weakly-referenced. This allows the WebView
to get destroyed when the document is closed, and a moment later the PreviewViewMessenger
disappears along with it. As the saying by David Wheeler goes, maybe all problems in computer science really can be solved by adding a level of indirection. 😊