Unity HashSet Deserialize 이슈... ArgumentNullException : Value cannot be null.
iOS에서 발생하는 HashSet의 역직렬화 시 발생하는 문제
2024.10.10
최근 프로젝트를 진행하면서, iOS 빌드를 진행했는데 로딩 Scene에서
ArgumentNullException: Value cannot be null 에러가 나며 넘어가지 않는 것을 확인했다.
ArgumentNullException: Value cannot be null.
Parameter name: method
좀 더 자세하게는 ArgumentNullException 이 발생한 것이었는데,
이를 해결하기 위한 방법을 찾아내어 블로그에 공유한다.
바로 해결 방법이 궁금한 사람은 아래 해결 방법 챕터를 참고하시길!
🔷 문제 발견
어느날 iOS에서 빌드를 진행하니, 아래와 같은 에러 메세지가 나타났다.
작업 환경은 Unity 2022 이다.
value가 null이라고? 이게 뭐지??
이 Exception이 참 난감했던 이유는,
Windows 에디터
, Mac 에디터
, Andorid
환경에서는 문제가 발생하지 않는다는 것이다!
플랫폼 디펜던시한 코드가 있다던가 하진 않아서 다른건 아무것도 없는데, 왜 안되는 것이지? 일단 조사해보기로 했다.
자세한 에러 메세지는 아래와 같다.
ArgumentNullException: Value cannot be null.
Parameter name: method
Newtonsoft.Json.Utilities.LateBoundReflectionDelegateFactory.CreateParameterizedConstructor (System.Reflection.MethodBase method) (at <00000000000000000000000000000000>:0)
Newtonsoft.Json.Serialization.JsonArrayContract.CreateWrapper (System.Object list) (at <00000000000000000000000000000000>:0)
Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.Object existingValue, System.String id) (at <00000000000000000000000000000000>:0)
... 중략 ...
도대체 왜 이런 문제가 발생하는지 찾아보기 위해 콜 스택을 분석해보았는데,
Newtonsoft.Json
에서 문제가 발생한 것으로 보아, 직렬화/역직렬화 하는 과정에서 이슈가 생긴것으로 보였다.
아래와 같은 GetUserData
메서드에서 에러가 발생하는 것을 확인했다.
... 중략 ...
Network.LocalServer.GetUserData (Network.Packets.RequestPacket packet) (at <00000000000000000000000000000000>:0)
Cysharp.Threading.Tasks.UniTaskCompletionSourceCore`1[TResult].TrySetResult (TResult result) (at <00000000000000000000000000000000>:0)
... 중략 ...
GetUserData
에서 Json과 관련된 코드는 아래 밖에 없었다..!
var userProperty = JsonConvert.DeserializeObject<UserProperty>(json);
이게 도대체 왜 문제가 된 것일까? 열심히 디버깅 하는 중,
HashSet<string>
을 역직렬화 하는데 문제가 있음을 알 수 있었다.
해당 내용을 지우고 빌드를 해 테스트를 하면, 정상적으로 동작하는 모습을 확인 할 수 있었다.
🔷 해결 방법
관련하여 비슷한 이슈가 있는걸 확인했다.
답변에 따르면, 사용하지 않는 코드를 제거하는 바이트 코드 스트리핑 때문에 발생한 문제라고 한다.
해결 방법은, 임의의 위치에 폴더를 하나 만들고, 아래와 같은 스크립트를 작성하는 것이다.
나의 경우엔 Scripts/Utils
라는 폴더에 IOSSerializeHelper.cs
스크립트를 추가했다.
using Newtonsoft.Json.Utilities;
using UnityEngine.Scripting;
namespace UserNameSpace /* 네임스페이스를 제거해도 당연히 동작한다. */
{
public static class IOSSerializeHelper
{
#if UNITY_IOS
[Preserve]
private static void FixSerialization()
{
AotHelper.EnsureList<string>();
AotHelper.EnsureList<int>();
AotHelper.EnsureList<float>();
AotHelper.EnsureList<double>();
}
#endif
}
}
[Preserve]
라는 어트리뷰트를 추가해서, 빌드시 제거되지 않도록 했다.
위 코드를 프로젝트에 추가한 뒤, 빌드하면 된다. ( 저 static 메서드를 어딘가에서 호출하지 않아도 버그를 고칠 수 있다. )
다만, 중요한 것은, 만약 본인이 HashSet<T>
를 사용하고 있으면,
AotHelper.EnsureList<T>
를 입력해주어야 한다고 한다.
예를 들어,
HashSet<MyClass>
를 역직렬화 해야한다면
AotHelper.EnsureList<MyClass>
를 추가로 입력해야 한다는 뜻이다.
해당 내용을 추가하고 빌드하니, 정상적으로 잘 동작하는 모습을 볼 수 있었다!