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<string, string> parameters = null, Callback<ServerData> successCallback = null, Callback<string> apiFailureCallback = null, Callback<string> 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<string, string> 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<WWW> WaitForRequest(WWW www, Callback<ServerData> successCallback = null, Callback<string> apiFailureCallback = null, Callback<string> 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<LaunchCallback> callbackToDelete = new List<LaunchCallback>();
		for (int i=0; i<len; i++)
		{
			LaunchCallback cb = _launchCallbacks*;*
  •  	RequestResult(cb.www, cb.hasTransaction,  cb.successCB, cb.apiFailureCB, cb.layerFailureCB, cb.timeOutError);*
    
  •  	callbackToDelete.Add(cb);*
    
  •  }*
    
  •  foreach(LaunchCallback call in callbackToDelete)*
    
  •  {*
    
  •  	_launchCallbacks.Remove(call);*
    
  •  	call.dispose();*
    
  •  }*
    
  • }*

  • private void RequestResult(WWW www, bool hasTransaction, Callback 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:
Imgur: The magic of the Internet
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:
    Imgur: The magic of the Internet
    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 Imgur: The magic of the Internet
    ---------------------------------------------------------------------------------------------------------------
    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

All right, it seems that I was not alone:

Looks like it’s solved in unity 4.5.6, just have to wait for the release. Anyway, thx for the help