为什么我们需要保留Objective-C对象字段?

时间:2017-02-14 09:37:58

标签: ios delphi firemonkey delphi-10.1-berlin

这是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;时,它会触发访问冲突(奇怪的是并非总是如此)。

注意:我仍然无法理解为什么我有一个eaccessviolation,所以我决定把完整的代码放在这里:

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;

2 个答案:

答案 0 :(得分:2)

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

因此需要FPlayerItem.retain,因为在函数退出后不应释放FPlayerItem。它已分配给FPlayerItem,因此您希望将其保持活动状态。

通常,如果您使用CreatealloccopymutableCopynew...创建此类,则会为您调用retain。然后,您需要致电releaseautorelease

如果使用其他函数(如fileUrlWithPath)创建此类,则会将其添加到自动释放池中。您的保留计数仍然为1,但它将为您释放。如果你也发布它,那么你会崩溃。

如果您在课堂上致电retain,那么该通话必须与release平衡。

答案 1 :(得分:1)

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

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

虽然保持对FPlayerFPlayerItem的强引用可确保Delphi包装器实例的生命周期,但调用retain会增加包装的Objective-C对象实例的引用计数,并在此期间保持该对象实例的活动状态包装器本身的生命周期。

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

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

至于在FPlayerItem.release;期间发生异常的原因很难说清楚。它可能是线程问题,FMX部分中的错误甚至是底层操作系统框架。

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

但如果我必须编写上面的析构函数代码,我会使用以下模式来避免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;