Unity登录后IOS端JSON解析空引用错误排查求助
iOS端Newtonsoft.Json解析JSON抛出NullReferenceException问题排查与修复
问题描述
登录流程中,通过Newtonsoft.Json.NET将用户设置JSON转换为对应类的功能,在Unity编辑器及Android端运行正常,但iOS端登录成功后获取会话数据时触发JSON解析异常,抛出NullReferenceException。
Xcode报错信息:
d__12:MoveNext()
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)NullReferenceException: Object reference not set to an instance of an object.
at System.Linq.Expressions.Interpreter.LightLambda.MakeRunDelegateCtor (System.Type delegateType) [0x00000] in <00000000000000000000000000000000>:0
相关代码:
using UnityEngine; using UnityEngine.UI; using UnityEngine.Networking; using System.Collections; using UnityEngine.SceneManagement; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; public class DatabaseConnection : MonoBehaviour { public MainMenu menu; public string musername; public string mpassword; public string returnArray; public int currentSession; public int lastSessUnlcoked=1; public UserProf userInfo; public List <SessionSettings> settingsList = new List<SessionSettings> (); public static DatabaseConnection Instance; void Awake (){ if(Instance!=null){ Destroy(this); }else{ Instance=this; } } void Start (){ DontDestroyOnLoad(gameObject); } public void Login(){ musername=LoginManager.username; mpassword=LoginManager.pass; StartCoroutine(UploadLogin(musername,mpassword)); } IEnumerator GetSessionData() { WWWForm form = new WWWForm(); form.AddField("user_id",userInfo.id); using (UnityWebRequest www = UnityWebRequest.Post("http://localhost/spark/clases/UnityGetSettings.php", form)) { yield return www.SendWebRequest(); if (www.result != UnityWebRequest.Result.Success) { Debug.Log(www.error); } else { Debug.Log (www.downloadHandler.text); returnArray=www.downloadHandler.text; var sts = JObject.Parse(returnArray); var AllSsessions = sts["Items"]; foreach(var currentSession in AllSsessions){ var initials = currentSession["initialAssessment"]; List<Question> InitialAssessment = new List<Question> (); List<Question> FinalAssessment = new List<Question> (); List<LevelSettings> levelsList = new List<LevelSettings> (); List<idStringClass> Summary = new List<idStringClass> (); List<idStringClass> Debrief = new List<idStringClass> (); foreach(var initial in initials){ string quest = initial["question"].ToObject<string>(); Debug.Log(initial["Id"]); var qid = initial["Id"].Value<int>(); string type = initial["typeOfQuestion"].ToObject<string>(); List<Answer>answers=new List<Answer>(); var answrs = initial["answers"]; foreach(var answr in answrs){ var answer = answr.ToObject<Answer>(); answers.Add(answer); } var question = new Question(qid, quest, answers, type); InitialAssessment.Add(question); } var finals = currentSession["finalAssessment"]; foreach(var final in finals){ int qid = final["Id"].ToObject<int>(); string quest = final["question"].ToObject<string>(); string type = final["typeOfQuestion"].ToObject<string>(); List<Answer>answers=new List<Answer>(); var answrs = final["answers"]; foreach(var answr in answrs){ int id = answr["Id"].ToObject<int>(); string text = answr["Text"].ToObject<string>(); string feedback = answr["Feedback"].ToObject<string>(); int imgId = answr["imgId"].ToObject<int>(); var answer = new Answer(id, text, feedback, imgId); answers.Add(answer); } var question = new Question(qid,quest, answers, type); FinalAssessment.Add(question); } var levels = currentSession["levels"]; foreach(var level in levels){ string type = level["gameName"].ToObject<string>(); if(type == "gonogo"){ var lvl = level.ToObject<GonogoSettings>(); levelsList.Add(lvl); }else if (type == "treasure_hunt"){ var lvl = level.ToObject<THuntSettings>(); levelsList.Add(lvl); } } var summaries = currentSession["assessment_summary"]; foreach(var summary in summaries){ var sum = summary.ToObject<idStringClass>(); Summary.Add(sum); } var debriefs = currentSession["assessment_debrief"]; foreach(var debrief in debriefs){ var deb = debrief.ToObject<idStringClass>(); Debrief.Add(deb); } settingsList.Add(new SessionSettings(1, 1, InitialAssessment, FinalAssessment, levelsList, Summary, Debrief)); } var json = JsonConvert.SerializeObject(settingsList[0]); Debug.Log("json"+json); SceneManager.LoadScene("LevelSelection"); } } } IEnumerator UploadLogin(string username, string password) { WWWForm form = new WWWForm(); form.AddField("username",username); form.AddField("password",password); using (UnityWebRequest www = UnityWebRequest.Post("http://localhost/spark/clases/UnityLogin.php", form)) { yield return www.SendWebRequest(); if (www.result != UnityWebRequest.Result.Success) { Debug.Log(www.error); } else { Debug.Log (www.downloadHandler.text); returnArray=www.downloadHandler.text; userInfo=JsonUtility.FromJson<UserProf>(returnArray); StartCoroutine(GetSessionData()); } } } }
修复方案
1. 适配iOS的AOT编译限制
Unity iOS采用IL2CPP编译,对反射支持有限,Newtonsoft.Json的默认反射解析逻辑会触发异常:
- 替换为Unity专用的Newtonsoft.Json版本(如NuGet的
Newtonsoft.Json-for-Unity),而非标准.NET版本 - 在项目根目录创建
link.xml文件,防止核心类被IL2CPP裁剪:
<linker> <assembly fullname="Newtonsoft.Json" preserve="all"/> <assembly fullname="System.Core" preserve="all"/> </linker>
- 在PlayerSettings的iOS面板中,开启Allow 'unsafe' Code选项
2. 添加空引用防护逻辑
代码中直接访问JSON节点未做空判断,一旦接口返回字段缺失就会触发异常,修改解析逻辑:
// 检查Items节点是否存在 var AllSsessions = sts["Items"]; if (AllSsessions == null || !AllSsessions.HasValues) { Debug.LogWarning("JSON无有效Items数据"); yield break; } // 遍历initialAssessment前先判断节点是否为空 var initials = currentSession["initialAssessment"]; if (initials != null && initials.HasValues) { foreach(var initial in initials){ // 原解析逻辑 } } // finalAssessment、levels等节点需添加同样的空判断
3. 替换反射式解析为手动赋值
避免频繁使用.ToObject<T>()这类依赖反射的方法,改用手动赋值减少AOT冲突:
// 原代码:var answer = answr.ToObject<Answer>(); // 改为手动赋值并添加空值默认 var answer = new Answer( answr["Id"]?.Value<int>() ?? 0, answr["Text"]?.ToObject<string>() ?? "", answr["Feedback"]?.ToObject<string>() ?? "", answr["imgId"]?.Value<int>() ?? 0 );
4. 修复网络与ATS配置
iOS对HTTP请求有严格限制:
- 将测试用的
http://localhost替换为公网HTTPS地址,或在Info.plist中添加ATS例外:
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>
- 打印iOS端获取的完整JSON内容,确认和编辑器/Android端返回的格式、编码完全一致
5. 优化实体类序列化配置
确保实体类字段与JSON键完全匹配,添加[JsonProperty]显式绑定:
public class UserProf { [JsonProperty("id")] public int id { get; set; } // 其他字段同理添加JsonProperty特性 }
内容的提问来源于stack exchange,提问作者DaiF
相关产品推荐
相关产品推荐

