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

在FreePascal/Lazarus中实现NSURLConnectionDataDelegate遇编译及回调问题

解决FreePascal/Lazarus中NSURLConnectionDataDelegate异步实现的问题

我来帮你梳理并解决这些在FreePascal/Lazarus中实现NSURLConnection异步代理的问题,这些坑我之前也踩过不少:

核心问题分析

你遇到的三个问题本质上都是FreePascal对Objective-C协议/方法签名的处理细节没摸透:

  1. connectionDidFinishLoading不触发:大概率是代理对象被提前释放,或者方法签名完全错误,再或者没正确运行RunLoop处理异步事件;
  2. override关键字报错:NSObject本身并没有实现NSURLConnectionDelegate/DataDelegate的方法,这些是协议要求的方法,不需要加override,编译器的提示是对Objective-C协议机制的误解;
  3. 符号重定义:多个代理方法用了相同的message字符串,导致Objective-C选择器(SEL)冲突,每个方法的message必须严格对应Objective-C的原生选择器。

分步解决方案

1. 修正代理方法的message字符串(解决符号重定义)

Objective-C的选择器是精确匹配的,每个代理方法的message必须和原生选择器完全一致,不能随便简化。同时要显式声明类实现的协议:

{$mode objfpc} {$modeswitch objectivec1} {$modeswitch objectivec2}

type
  TmsMacRequestDelegate = objcclass(NSObject, NSURLConnectionDelegate, NSURLConnectionDataDelegate)
  public
    // 对应Objective-C的 - (void)connectionDidFinishLoading:(NSURLConnection *)connection;
    procedure connectionDidFinishLoading(connection: NSURLConnection); message 'connectionDidFinishLoading:';
    // 对应 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
    procedure connection(connection: NSURLConnection; didReceiveResponse: NSURLResponse); message 'connection:didReceiveResponse:';
    // 对应 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
    procedure connection(connection: NSURLConnection; didReceiveData: NSData); message 'connection:didReceiveData:';
    // 对应 - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite;
    procedure connection(connection: NSURLConnection; didSendBodyData: NSInteger; totalBytesWritten: NSInteger; totalBytesExpectedToWrite: NSInteger); message 'connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:';
    // 对应 - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;
    function connection(connection: NSURLConnection; willSendRequest: NSURLRequest; redirectResponse: NSURLResponse): NSURLRequest; message 'connection:willSendRequest:redirectResponse:';
    // 对应 - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse;
    function connection(connection: NSURLConnection; willCacheResponse: NSCachedURLResponse): NSCachedURLResponse; message 'connection:willCacheResponse:';
  end;

注意:带返回值的方法(比如重定向、缓存相关)要改成function,不能用procedure,否则会和Objective-C的方法签名不匹配。

2. 移除不必要的override关键字

这些方法是协议要求实现的,不是继承自NSObject的方法,所以完全不需要加override——加了反而会让编译器误以为你要重写父类不存在的方法,直接引发报错。

3. 确保代理对象的生命周期(解决方法不触发)

如果requestDelegate是局部变量,在urlConnection.start后很可能被FreePascal的内存管理机制释放,导致代理方法根本无法被调用。你需要把代理对象保存到持久化变量中(比如类成员变量、全局变量),或者手动retain它:

// 声明一个全局变量或者类成员变量来持有代理
var
  requestDelegate: TmsMacRequestDelegate;

// 创建代理时手动保持引用
requestDelegate := TmsMacRequestDelegate.alloc.init;
requestDelegate.retain;
urlConnection := NSURLConnection.connectionWithRequest_delegate(urlRequest, requestDelegate);
urlConnection.start;

然后在connectionDidFinishLoading方法里记得释放代理对象:

procedure TmsMacRequestDelegate.connectionDidFinishLoading(connection: NSURLConnection);
begin
  // 处理你的完成逻辑
  self.release; // 释放代理,避免内存泄漏
end;

4. 正确处理RunLoop(解决异步事件不触发)

NSURLConnection的异步操作依赖RunLoop来分发事件,如果你用自己的死循环等待,会阻塞RunLoop运行,导致代理方法无法被调用。正确的做法是让当前线程的RunLoop运行直到连接完成:

var
  isFinished: Boolean = False;

// 启动连接后
isFinished := False;
urlConnection.start;

// 运行RunLoop,每次等待0.1秒,直到完成
while not isFinished do
begin
  NSRunLoop.currentRunLoop.runUntilDate(NSDate.dateWithTimeIntervalSinceNow(0.1));
end;

然后在connectionDidFinishLoading里设置isFinished := True;来结束循环。

5. 实现重定向拦截(满足你的初始需求)

既然你不想自动跟随重定向,在connection:willSendRequest:redirectResponse:方法里返回nil即可阻止重定向:

function TmsMacRequestDelegate.connection(connection: NSURLConnection; willSendRequest: NSURLRequest; redirectResponse: NSURLResponse): NSURLRequest;
begin
  if redirectResponse <> nil then
  begin
    // 这里可以记录重定向地址,方便后续手动处理
    Writeln('拦截到重定向:', redirectResponse.URL.absoluteString.UTF8String);
    Result := nil; // 阻止自动重定向
  end
  else
  begin
    Result := willSendRequest; // 正常请求直接返回原请求
  end;
end;

完整测试代码示例

{$mode objfpc} {$modeswitch objectivec1} {$modeswitch objectivec2}

uses
  CocoaAll;

var
  isFinished: Boolean = False;
  requestDelegate: TmsMacRequestDelegate;

type
  TmsMacRequestDelegate = objcclass(NSObject, NSURLConnectionDelegate, NSURLConnectionDataDelegate)
  public
    procedure connectionDidFinishLoading(connection: NSURLConnection); message 'connectionDidFinishLoading:';
    procedure connection(connection: NSURLConnection; didReceiveResponse: NSURLResponse); message 'connection:didReceiveResponse:';
    procedure connection(connection: NSURLConnection; didReceiveData: NSData); message 'connection:didReceiveData:';
    function connection(connection: NSURLConnection; willSendRequest: NSURLRequest; redirectResponse: NSURLResponse): NSURLRequest; message 'connection:willSendRequest:redirectResponse:';
  end;

procedure TmsMacRequestDelegate.connectionDidFinishLoading(connection: NSURLConnection);
begin
  Writeln('连接完成!');
  isFinished := True;
  self.release;
end;

procedure TmsMacRequestDelegate.connection(connection: NSURLConnection; didReceiveResponse: NSURLResponse);
begin
  Writeln('收到响应');
end;

procedure TmsMacRequestDelegate.connection(connection: NSURLConnection; didReceiveData: NSData);
begin
  Writeln('收到数据,长度:', data.length);
end;

function TmsMacRequestDelegate.connection(connection: NSURLConnection; willSendRequest: NSURLRequest; redirectResponse: NSURLResponse): NSURLRequest;
begin
  if redirectResponse <> nil then
  begin
    Writeln('拦截重定向到:', redirectResponse.URL.absoluteString.UTF8String);
    Result := nil;
  end
  else
  begin
    Result := willSendRequest;
  end;
end;

var
  urlRequest: NSURLRequest;
  urlConnection: NSURLConnection;
begin
  urlRequest := NSURLRequest.requestWithURL(NSURL.URLWithString(NSString.stringWithUTF8String('https://example.com')));
  requestDelegate := TmsMacRequestDelegate.alloc.init;
  requestDelegate.retain;
  urlConnection := NSURLConnection.connectionWithRequest_delegate(urlRequest, requestDelegate);
  
  if urlConnection = nil then
  begin
    Writeln('创建连接失败');
    Exit;
  end;
  
  isFinished := False;
  urlConnection.start;
  
  while not isFinished do
  begin
    NSRunLoop.currentRunLoop.runUntilDate(NSDate.dateWithTimeIntervalSinceNow(0.1));
  end;
  
  Writeln('所有操作完成');
end.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:08:40