【问题标题】:Why we need to do retain for Objective-C object field?为什么我们需要为 Objective-C 对象字段做保留?
【发布时间】:2017-02-14 09:37:57
【问题描述】:

这是Delphi媒体播放器的代码:

type
  TAVMedia = class(TMedia)
  private
    FPlayer: AVPlayer;
    FPlayerItem: AVPlayerItem;
  public
    constructor Create(const AFileName: string); override;
    destructor Destroy; override;
  end;

constructor TAVMedia.Create(const AFileName: string);
var aURL: NSUrl;
begin
  inherited Create(AFileName);
  FPlayerItem := TAVPlayerItem.Wrap(TAVPlayerItem.OCClass.playerItemWithURL(URL));
  FPlayerItem.retain;
  FPlayer := TAVPlayer.Wrap(TAVPlayer.OCClass.playerWithPlayerItem(FPlayerItem));
  FPlayer.retain;
end;

destructor TAVMedia.Destroy;
begin
  FPlayer.release;
  FPlayer := nil;
  FPlayerItem.release;
  FPlayerItem := nil;
  inherited Destroy;
end;

我不太明白他们为什么需要做FPlayerItem.retainFPlayer.retainFPlayerItemFPlayer 是对象字段,而不是局部变量,因此始终存在对它们的强引用。那么这里retain的目的是什么?

似乎执行FPlayer.release; 也会释放FPlayerItem,因此当稍后调用FPlayerItem.release; 时,有时会触发访问冲突(奇怪的是并非总是如此)。

注意:我仍然无法理解为什么我会遇到电子访问违规,所以我决定将我所做的完整代码放在这里:

type
  TMyMedia = class(TObject)
  private
    FPlayer: AVPlayer;
    FPlayerItem: AVPlayerItem;
  public
    constructor Create;
    destructor Destroy; override;
  end;

constructor TMyMedia.Create;
begin
  inherited Create;

  P := TNSUrl.OCClass.URLWithString(StrToNSStr(aDataSource)); // Creates and returns an NSURL object initialized with a provided URL string
  if P = nil then raise EFileNotFoundException.Create(SFileNotFound); // If the URL string was malformed or nil, returns nil.
  aURL := TNSUrl.Wrap(P);
  try

    FPlayerItem := TAVPlayerItem.Wrap(TAVPlayerItem.OCClass.playerItemWithURL(URL));
    FPlayerItem.retain;

  finally
    aURL.release; // << if i don't do this then i will not have any exception at the end ??? 
    aURL := nil;  // <<
  end;


  FPlayer := TAVPlayer.Wrap(TAVPlayer.OCClass.playerWithPlayerItem(FPlayerItem));
  FPlayer.retain;
end;

destructor TAVMedia.Destroy;
begin

  ALLog('FPlayer.retainCount', inttostr(FPlayer.retainCount)); // => show 1
  ALLog('FPlayerItem.retainCount', inttostr(FPlayerItem.retainCount)); // => show 6

  FPlayer.release;
  FPlayer := nil;

  ALLog('FPlayerItem.retainCount', inttostr(FPlayerItem.retainCount)); // => show 1
  FPlayerItem.release; => here i receive Access violation at address 2156565 accessing address 68684458

  FPlayerItem := nil;
  inherited Destroy;
end;

【问题讨论】:

    标签: ios delphi firemonkey delphi-10.1-berlin


    【解决方案1】:

    TNSUrl.OCClass.URLWithStringTAVPlayerItem.OCClass.playerItemWithURL 将项目添加到自动释放池中。因此,您的保留计数为 1。当自动释放池释放它包含的项目时,它们将被释放,这通常发生在当前事件执行完成之后。

    因此需要FPlayerItem.retain,因为FPlayerItem 在函数退出后不应该被释放。它被分配给FPlayerItem,所以你想让它保持活跃。

    一般来说,如果您使用CreatealloccopymutableCopynew... 创建这样的类,那么会为您调用retain。然后你需要打电话给releaseautorelease

    如果您使用fileUrlWithPath 等其他函数创建这样的类,那么它会被添加到自动释放池中。您的保留计数仍为 1,但它会为您释放。如果你也释放它,那么你会崩溃。

    如果您在某个类上调用 retain,则该调用必须与 release 平衡。

    【讨论】:

      【解决方案2】:

      FPlayerFPlayerItem 是围绕 Objective-C 原始对象指针的 Delphi 对象包装器。

      虽然 Delphi for iOS 和底层 iOS 框架都使用引用计数来管理对象实例的生命周期,但所有相似之处都到此为止。这是两个独立的引用计数机制。

      在保持对FPlayerFPlayerItem 的强引用确保Delphi 包装器实例的生命周期的同时,调用retain 会增加被包装的Objective-C 对象实例的引用计数,并使该对象实例在包装器本身的生命周期内保持活动状态。

      如果不调用 retain 包装的对象可能会被操作系统释放,而 Delphi 包装器仍在使用它。

      当然,为了减少被包装对象的引用计数,必须在包装器被销毁时使用匹配的release 调用。


      至于为什么在FPlayerItem.release; 期间会发生异常,很难说。这可能是线程问题、FMX 部分中的错误,甚至是底层操作系统框架。

      就包装的 Objective-C 实例而言,它们在需要时保留自己的强引用,因此就它们而言,释放顺序并不重要,而且操作系统也不太可能是这里的罪魁祸首(我不能肯定地说)。

      但如果我必须编写上面的析构函数代码,我会使用以下模式来避免 Delphi 方面的问题。

      destructor TAVMedia.Destroy;
      var
        tmpPlayer: AVPlayer;
        tmpPlayerItem: AVPlayerItem;
      begin
        tmpPlayer := FPlayer;
        tmpPlayerItem := FPlayerItem;
        FPlayer := nil;
        FPlayerItem := nil;
        tmpPlayer.release;
        tmpPlayerItem.release;
        inherited Destroy;
      end;
      

      【讨论】:

      • 感谢 Dalija,我现在非常了解调用保留的必要性。但这个例外让我抓狂。就在做 FPlayerItem.release 之前,我打印 FPlayerItem.retainCount 并且它显示为 1。但是一旦我做 FPlayerItem.release 我有一个 eaccessViolation :(
      • 你有回调或线程吗?如果在您调用 FPlayerItem.release 后将 FPlayerItem 设置为 nil,则可以使用 FPlayerItem 包装器对象,该对象现在不再使用现有的 Objective-C 实例。
      • 没什么,我删除了所有内容(所有委托等)以使代码尽可能简单。我只是编辑这个问题来向你展示完整的代码。我无法理解...
      • 这太疯狂了,因为我确实发布了用于创建播放器项的 NSUrl!我不明白:(
      • 那个很简单......你不应该在那个url上调用release,因为你从来没有在它上面调用retain。
      猜你喜欢
      • 2021-02-15
      • 2023-04-05
      • 1970-01-01
      • 2016-10-20
      • 2022-01-08
      • 2018-01-07
      • 2012-12-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多