You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 17:51:29