Refrence:
Memory management
1• Objective-C uses ARC(OC 使用ARC機制)
2• JavaScriptCore uses garbage collection (JS 使用垃圾回收機制)
■ All references are strong (JS中全部都是“強引用”)
3• API memory management is mostly automatic
4• Two situations that require extra attention: (幾乎SDK已經做好了很多事情,所以開發者只需要重點掌握以下亮點)
■ Storing JavaScript values in Objective-C objects
■ Adding JavaScript fields to Objective-C objects
(英文文檔只要被翻譯了,就難免失去原有的含義,儘量不翻譯~)
根據官方文檔關於JS-OC內存管理總結:由於JS中全部都是強引用,如果JS 與 OC互相引用時,就要防止OC也強引用JS,這樣會形成引用循環,所以OC要想辦法弱引用,但弱引用會被系統釋放,所以把可能被釋放的對象放到一個容器中來防止對象被被錯誤釋放。
看代碼(一):
JS:
function ClickHandler(button, callback) {
this.button = button;
this.button.onClickHandler = this;
this.handleEvent = callback;
};
OC:
@implementation MyButton
- (void)setOnClickHandler:(JSValue *)handler
{
_onClickHandler = handler; // Retain cycle
}
@end
如果直接保存 handler,就會出現內存泄露,因爲 JS 中引用 button 對象是強引用,如果 Button 也用強引用來保存 JS 中的 handler,這就導致了 循環引用。我們沒法改變
JavaScript 中的強引用機制,只能在 Objective-C 中弱引用 handler,爲了防止 onclick handler 被錯誤釋放, JavaScriptCore 給出的解決方案如下:
- (void)setOnClickHandler:(JSValue *)handler
{
_onClickHandler = [JSManagedValue managedValueWithValue:handler];
[_context.virtualMachine addManagedReference:_onClickHandler
withOwner:self]
}
代碼(二):
- (void)loadColorsPlugin
{
// Load the plugin script from the bundle.
NSString *path = [[NSBundlemainBundle]pathForResource:@"colors"ofType:@"js"];
NSString *pluginScript = [NSStringstringWithContentsOfFile:pathencoding:NSUTF8StringEncodingerror:nil];
_context = [[JSContextalloc]init];
// We insert the AppDelegate into the global object so that when we call
// -addManagedReference:withOwner: for the plugin object we're about to load
// and pass the AppDelegate as the owner, the AppDelegate itself is reachable from
// within JavaScript. If we didn't do this, the AppDelegate wouldn't be reachable
// from JavaScript, and there wouldn't be anything keeping the plugin object alive.
_context[@"AppDelegate"] =self;
// Insert a block so that the plugin can create NSColors to return to us later.
_context[@"makeNSColor"] = ^(NSDictionary *rgb){
return [NSColorcolorWithRed:[rgb[@"red"]floatValue] / 255.0f
green:[rgb[@"green"]floatValue] /255.0f
blue:[rgb[@"blue"]floatValue] /255.0f
alpha:1.0f];
};
JSValue *plugin = [_contextevaluateScript:pluginScript];
_colorPlugin = [JSManagedValuemanagedValueWithValue:plugin];
[_context.virtualMachineaddManagedReference:_colorPluginwithOwner:self];
[self.windowsetDelegate:self];
}
JSManagedValue:
The primary use case for JSManagedValue is for safely referencing JSValues
from the Objective-C heap. It is incorrect to store a JSValue into an
Objective-C heap object, as this can very easily create a reference cycle,
keeping the entire JSContext alive.
(將 JSValue 轉爲 JSManagedValue 類型後,可以添加到 JSVirtualMachine 對象中,這樣能夠保證你在使用過程中 JSValue 對象不會被釋放掉,當你不再需要該 JSValue 對象後,從 JSVirtualMachine 中移除該 JSManagedValue 對象,JSValue 對象就會被釋放並置空。)
JSVirtualMachine:
All instances of JSContext are associated with a single JSVirtualMachine. The
virtual machine provides an "object space" or set of execution resources.(JSVirtualMachine就是一個用於保存弱引用對象的數組,加入該數組的弱引用對象因爲會被該數組 retain,所以保證了使用時不會被釋放,當數組裏的對象不再需要時,就從數組中移除,沒有了引用的對象就會被系統釋放。)
Threading
API is thread safe
• Locking granularity is JSVirtualMachine
■ Use separate JSVirtualMachines for concurrency/parallelism
JavaScriptCore C API
JSValue ↔ JSValueRef :
JSValueRef valueRef = XXX;
JSValue *value = [JSValue valueWithJSValueRef:valueRef inContext:context];
JSValue *value = XXX;
JSValueRef valueRef = [value JSValueRef];
JSContext ↔ JSGlobalContextRef :
JSGlobalContextRef ctx = XXX;
JSContext *context = [JSContext contextWithJSGlobalContextRef:ctx];
JSContext *context = XXX;
JSGlobalContextRef ctx = [context JSGlobalContextRef];
使用Block的注意事項
從之前的例子和介紹應該有體會到Block在JavaScriptCore中起到的強大作用,它在JavaScript和Objective-C之間的轉換 建立起更多的橋樑,讓互通更方便。但是要注意的是無論是把Block傳給JSContext
對象讓其變成JavaScript方法,還是把它賦給exceptionHandler
屬性,在Block內都不要直接使用其外部定義的JSContext
對象或者JSValue
,應該將其當做參數傳入到Block中,或者通過JSContext
的類方法+
(JSContext *)currentContext;
來獲得。否則會造成循環引用使得內存無法被正確釋放。
比如上邊自定義異常處理方法,就是賦給傳入JSContext
對象con
,而不是其外創建的context
對象,雖然它們其實是同一個對象。這是因爲Block會對內部使用的在外部定義創建的對象做強引用,而JSContext
也會對被賦予的Block做強引用,這樣它們之間就形成了循環引用(Circular
Reference)使得內存無法正常釋放。
對於JSValue
也不能直接從外部引用到Block中,因爲每個JSValue
上都有JSContext
的引用
(@property(readonly, retain) JSContext *context;
),JSContext
再引用Block同樣也會形成引用循環。
Garbage Collection
雖然Objetive-C和JavaScript都是面向對象的語言,而且它們都可以讓程序員專心於業務邏輯,不用擔心內存回收的問題。但是兩者的內存回首機制全是不同的,Objective-C是基於引用計數,之後Xcode編譯器又支持了自動引用計數(ARC, Automatic Reference Counting);JavaScript則如同Java/C#那樣用的是垃圾回收機制(GC, Garbage Collection)。當兩種不同的內存回收機制在同一個程序中被使用時就難免會產生衝突。
比如,在一個方法中創建了一個臨時的Objective-C對象,然後將其加入到JSContext
放在JavaScript中的變量中被使用。因爲JavaScript中的變量有引用所以不會被釋放回收,但是Objective-C上的對象可能在方法調用結束後,引用計數變0而被回收內存,因此JavaScript層面也會造成錯誤訪問。
同樣的,如果用JSContext
創建了對象或者數組,返回JSValue
到Objective-C,即使把JSValue
變量retain
下,但可能因爲JavaScript中因爲變量沒有了引用而被釋放內存,那麼對應的JSValue
也沒有用了。
怎麼在兩種內存回收機制中處理好對象內存就成了問題。JavaScriptCore提供了JSManagedValue
類型幫助開發人員更好地管理對象內存。
// Convenience method for creating JSManagedValues from JSValues.
+ (JSManagedValue *)managedValueWithValue:(JSValue *)value;
// Create a JSManagedValue.
- (id)initWithValue:(JSValue *)value;
// Get the JSValue to which this JSManagedValue refers. If the JavaScript value has been collected,
// this method returns nil.
- (JSValue *)value;
@end
在《iOS7新JavaScriptCore框架入門介紹》有提到JSVirtualMachine
爲整個JavaScriptCore的執行提供資源,所以當將一個JSValue
轉成JSManagedValue
後,就可以添加到JSVirtualMachine
中,這樣在運行期間就可以保證在Objective-C和JavaScript兩側都可以正確訪問對象而不會造成不必要的麻煩。
@interface JSVirtualMachine : NSObject
// Create a new JSVirtualMachine.
- (id)init;
// addManagedReference:withOwner and removeManagedReference:withOwner allow
// clients of JSVirtualMachine to make the JavaScript runtime aware of
// arbitrary external Objective-C object graphs. The runtime can then use
// this information to retain any JavaScript values that are referenced
// from somewhere in said object graph.
//
// For correct behavior clients must make their external object graphs
// reachable from within the JavaScript runtime. If an Objective-C object is
// reachable from within the JavaScript runtime, all managed references
// transitively reachable from it as recorded with
// addManagedReference:withOwner: will be scanned by the garbage collector.
//
- (void)addManagedReference:(id)object withOwner:(id)owner;
- (void)removeManagedReference:(id)object withOwner:(id)owner;
@end