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

Delphi中REST与JSON类的内存释放问题:是否需手动释放?

Delphi Xero令牌刷新过程的内存管理问题解析

1. 释放jObject触发访问违规的原因

你的代码存在双重释放问题:

  • 先执行jObject := TJSONObject.Create;创建了独立的TJSONObject对象,但紧接着jObject := xeroResponse.JSONValue as TJSONObject;将变量指向xeroResponse内部管理的JSON对象,导致之前创建的对象丢失(内存泄漏)。
  • xeroResponse.JSONValue返回的对象所有权属于xeroResponse,手动jObject.Free后,xeroResponse自身Free时会再次尝试销毁已被释放的对象,直接触发访问违规。

2. 内存泄漏与对象释放规则

REST组件(TRESTClient/TRESTRequest/TRESTResponse)

你通过Create(nil)手动创建这些对象(未指定Owner),因此必须手动调用Free释放,当前代码的释放逻辑是正确的。释放顺序建议遵循「反向创建顺序」:先释放xeroRequest,再xeroResponse,最后xeroClient——因为xeroRequest依赖xeroClient和xeroResponse,反向释放能避免潜在引用问题,即使顺序不对也大概率不会出错,但这是更规范的做法。

JSON对象(TJSONValue/TJSONArray/TJSONObject)

  • 手动创建的对象:如果是你通过TJSONObject.Create/TJSONArray.Create创建的对象,必须手动Free,否则会造成内存泄漏。
  • 从RESTResponse获取的对象:xeroResponse.JSONValue返回的对象由xeroResponse负责生命周期管理,无需手动释放,否则会导致双重释放。
  • JSON子节点:通过GetValue获取的TJSONValue子节点(比如jObject.GetValue('access_token'))是父JSON对象的一部分,所有权属于父对象,不需要单独释放,父对象销毁时会自动清理所有子节点。

3. 代码修复与优化

以下是修复后的代码,解决了内存泄漏和访问违规问题:

procedure CheckXeroTokenValidity;
var
  xeroClient: TRESTClient;
  xeroRequest: TRESTRequest;
  xeroResponse: TRESTResponse;
  jObject : TJSONObject;
  sRefreshToken : string;
begin
  // 检查访问令牌是否超过30分钟,若刷新令牌未超过60天则刷新
  with dmProcs do begin
    ResetTable(tXero, '', True);
    tXero.First;
    if not tXero.Eof then begin
      // 令牌即将过期(超过25分钟)
      if MinutesBetween(tXeroDateToken.AsDateTime, Now) > 25 then begin
        // 刷新令牌仍有效(小于59天)
        if DaysBetween(tXeroDateToken.AsDateTime, Date) < 59 then begin
          sRefreshToken := tXeroRefreshToken.AsString;

          xeroClient := TRESTClient.Create(nil);
          xeroRequest := TRESTRequest.Create(nil);
          xeroResponse := TRESTResponse.Create(nil);

          Screen.Cursor := crHourGlass;
          try
            xeroClient.ContentType := 'application/json';
            xeroResponse.ContentType := 'application/json';
            xeroRequest.Client := xeroClient;
            xeroRequest.Response := xeroResponse;
            xeroRequest.Method := TRESTRequestMethod.rmPOST;
            xeroRequest.Params.Clear;
            xeroRequest.Params.AddItem('grant_type', 'refresh_token');
            xeroRequest.Params.AddItem('client_id', sClientID);
            xeroRequest.Params.AddItem('refresh_token', sRefreshToken);
            xeroClient.BaseURL := 'https://identity.xero.com/connect/token';

            try
              xeroRequest.Execute;
              if Assigned(xeroResponse.JSONValue) then begin
                // 直接转换RESTResponse的JSON对象,无需手动创建
                jObject := xeroResponse.JSONValue as TJSONObject;
                if Assigned(jObject) and (xeroResponse.StatusCode = 200) and Assigned(jObject.GetValue('access_token')) then begin
                  try
                    tXero.Edit;
                    tXeroAccessToken.AsString := jObject.GetValue('access_token').Value;
                    tXeroRefreshToken.AsString := jObject.GetValue('refresh_token').Value;
                    tXeroDateToken.AsDateTime := Now;
                    tXero.Post;
                  except on E:Exception do
                    ShowMessage('Error: ' + E.Message);
                  end;
                end else begin
                  // 出错或未返回有效令牌,删除Xero记录
                  tXero.First;
                  if not tXero.Eof then begin
                    tXero.Delete;
                  end;

                  if Assigned(jObject) and Assigned(jObject.GetValue('error')) then begin
                    ShowMessage('Error: ' + jObject.GetValue('error').ToString);
                  end;
                end;
              end;
            except on E:Exception do
              ShowMessage('Error: ' + xeroResponse.ErrorMessage + ' - ' + E.Message);
            end;
          finally
            Screen.Cursor := crDefault;
            // 按反向创建顺序释放REST组件
            xeroRequest.Free;
            xeroResponse.Free;
            xeroClient.Free;
            // 无需释放jObject,其所有权属于xeroResponse
          end;
        end else begin
          Information('请联系管理员重新建立会计系统连接。');
        end;
      end;
    end;
  end;
end;

修复点说明:

  • 移除jObject := TJSONObject.Create;,避免内存泄漏;
  • 增加Assigned(jObject)判断,防止类型转换失败导致空指针;
  • 调整REST组件释放顺序,符合规范;
  • 明确不手动释放jObject,交由xeroResponse管理。

内容的提问来源于stack exchange,提问作者Paul Coshott

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 23:48:16