Hi there,
I have a nifty memory leak when I run my project on the IOS platform. I have checked it using instruments leak tool and debug using Xcode 6. In some cases it looks like the WWW object is never disposed.
-------------------------------------------------------------------
I°) Here is the part of code that handle sending WWW requests:
private void issueRequestWithoutQueuing(string url, Dictionary parameters = null, Callback successCallback = null, Callback apiFailureCallback = null, Callback layerFailureCallback = null, bool showSpinner = false)
{
QueuedWWWObject queueWWW = new QueuedWWWObject();
queueWWW.url = url;
queueWWW.successCB = successCallback;
queueWWW.apiFailureCB = apiFailureCallback;
queueWWW.layerFailureCB = layerFailureCallback;
if (parameters != null)
{
queueWWW.parameters = parameters;
}
issueRequest(queueWWW, showSpinner);
}
private void issueRequest(QueuedWWWObject queueWWW, bool showSpinner = false)
{
numberRequestInProgress ++;
WWW www;
if (queueWWW.parameters != null && queueWWW.parameters.Count != 0)
{
WWWForm form = new WWWForm();
foreach (KeyValuePair pair in queueWWW.parameters)
{
form.AddField(pair.Key, pair.Value);
}
www = new WWW(queueWWW.url, form);
}
else
{
www = new WWW(queueWWW.url);
}
NetworkManager.instance.StartCoroutine(WaitForRequest(www, queueWWW, queueWWW.successCB, queueWWW.apiFailureCB, queueWWW.layerFailureCB));
}
We are waiting for request until we receive either that the request is done, there has been an error, or the request has timed out
private IEnumerator WaitForRequest(WWW www, Callback successCallback = null, Callback apiFailureCallback = null, Callback layerFailureCallback = null)
{
int startLaunchRequestTime = ServerTime.instance.getRealGameTime();
while (!www.isDone && www.error == null && ((ServerTime.instance.getRealGameTime() - startLaunchRequestTime) < REQUEST_TIMEOUT))
{
yield return www;
}
LaunchCallback cb = new LaunchCallback();
cb.www = www;
cb.successCB = successCallback;
cb.apiFailureCB = apiFailureCallback;
cb.layerFailureCB = layerFailureCallback;
if ((ServerTime.instance.getRealGameTime() - startLaunchRequestTime) >= REQUEST_TIMEOUT)
{
cb.timeOutError = true;
}
_launchCallbacks.Add(cb);
}
-------------------------------------------------------------------
II°) And then, in the update function, where we request the results for the launchcallback, handle the callbacks, and dispose the WWW object:
public void update()
{
int len = _launchCallbacks.Count;
List callbackToDelete = new List();
for (int i=0; i successCallback, Callback apiFailureCallback , Callback layerFailureCallback, bool timeOutError)
{
// handles success/ failure callbacks, not relevant here
numberRequestInProgress --;
}
-------------------------------------------------------------------
III°) The LauncCcallback class is just a container for the WWW object and its callbacks:
public class LaunchCallback
{
public Callback successCB;
public Callback apiFailureCB;
public Callback layerFailureCB;
public WWW www;
public bool hasTransaction;
public bool timeOutError = false;
public void dispose()
{
Debug.Log (" DISPOSING WWW object " + www.url );
www.Dispose();
successCB = null;
apiFailureCB = null;
layerFailureCB = null;
}
}
-------------------------------------------------------------------
When I run the project on IOS, I get the debug output of DISPOSING WWW object " + www.url, but the leak instrument tells me that the issueRequest function has leaked several strings, dictionary etc etc, as if the WWW has not been disposed:
http://imgur.com/RWBbYwH
When I put a breakpoint in Xcode, we pass in the destroy function for every request:
extern "C" void UnityDestroyWWWConnection(void* connection)
{
UnityWWWConnectionDelegate* delegate = (UnityWWWConnectionDelegate*)connection;
[delegate cleanup];
[delegate release];
}
which calls this:
- (void)cleanup
{
[_connection cancel];
_connection = nil;
[_data release];
_data = nil;
}
I really don't know what's going on, because I do pass in the dispose function of the launchcallback. Is there any references that could lead the WWW object to not be disposed?
PS: I am using unity 4.5.5 & monodevelop 4.0.1
Many thanks,
Down
-------------------------------------------------------------------------------------------------------------------
UPDATE 1:
A little update:
After reviewing the code generated by unity in xcode and analyzing it: xcode tells me that there is a potential leak in the WWWConnection.mm, particulary here, the delegate.connection is never cleaned:
extern "C" void* UnityStartWWWConnectionGet(void* udata, const void* headerDict, const char* url)
{
UnityWWWConnectionDelegate* delegate = [UnityWWWConnectionDelegate newDelegateWithCStringURL:url udata:udata];
NSMutableURLRequest* request =
[UnityWWWConnectionDelegate newRequestForHTTPMethod:@"GET" url:delegate.url headers:(NSDictionary*)headerDict];
delegate.connection = [NSURLConnection connectionWithRequest:request delegate:delegate];
return delegate;
}
the same occurs for POST methods:
extern "C" void* UnityStartWWWConnectionPost(void* udata, const void* headerDict, const char* url, const void* data, unsigned length)
{
UnityWWWConnectionDelegate* delegate = [UnityWWWConnectionDelegate newDelegateWithCStringURL:url udata:udata];
NSMutableURLRequest* request =
[UnityWWWConnectionDelegate newRequestForHTTPMethod:@"POST" url:delegate.url headers:(NSDictionary*)headerDict];
[request setHTTPBody:[NSData dataWithBytes:data length:length]];
[request setValue:[NSString stringWithFormat:@"%d", length] forHTTPHeaderField:@"Content-Length"];
delegate.connection = [NSURLConnection connectionWithRequest:request delegate:delegate];
return delegate;
}
Which correlates nicely with what I see:
http://imgur.com/vIdeg54
I don't really know what to do, as it looks like its related to the unity code. Any help would be appreciated.
------------------------------------------------------------------------------------------------------------------------------------------
UPDATE 2
I have a similar leak when I call this function:
private void OnEnable()
{
StartCoroutine(doGet(facebookURL, onGetFacebookAvatar));
}
public IEnumerator doGet(string url, Callback callback = null)
{
using (WWW www = new WWW(url))
{
yield return www;
if (callback != null)
{
callback(www);
callback = null;
}
}
}
private void onGetFacebookAvatar(WWW result)
{
if (!string.IsNullOrEmpty (result.error))
{
ErrorManager.instance.logWarning (ErrorManager.ERROR_FACEBOOK_API, "Get facebook avatar failed : " + result.error, "AvatarBehaviour");
}
else
{
Texture2D texture = new Texture2D(frame.width-20, frame.height-20);
if (!_facebookAvatar.ContainsKey(_userID))
{
// Never use www.texture as it cause memory leak (WWW object is never released)
result.LoadImageIntoTexture(texture);
_facebookAvatar.Add(_userID, texture);
}
avatarTexture.mainTexture = texture;
avatarTexture.width = frame.width - 20;
avatarTexture.height = frame.height - 20;
NGUITools.SetActiveSelf(avatarSprite.gameObject, false);
NGUITools.SetActiveSelf(avatarTexture.gameObject, true);
}
}
see http://imgur.com/vIdeg54
---------------------------------------------------------------------------------------------------------------
UPDATE 3:
After some investigation, adding the line:
[request release];
here
extern "C" void* UnityStartWWWConnectionGet(void* udata, const void* headerDict, const char* url)
{
UnityWWWConnectionDelegate* delegate = [UnityWWWConnectionDelegate newDelegateWithCStringURL:url udata:udata];
NSMutableURLRequest* request =
[UnityWWWConnectionDelegate newRequestForHTTPMethod:@"GET" url:delegate.url headers:(NSDictionary*)headerDict];
delegate.connection = [NSURLConnection connectionWithRequest:request delegate:delegate];
[request release];
return delegate;
}
Does helps, but still some leak, maybe because what's inside the request is not fully released (string and stuff), still investigating
↧