我创建了一个过滤器并将其设置为全局变量,为什么我需要保留调用?

时间:2016-04-13 12:11:09

标签: ios macos delphi

在Panel paint方法中,我想在第一次调用时创建一个TCIFilter,然后在后续调用中重用过滤器。所以这是代码。它只是切换一个表单级变量FFirst。所以这里有一些代码

if FFirst then
  begin
    ...
    filter := TCIFilter.Wrap(TCIFilter.OCClass.filterWithName(NSSTR('CISepiaTone')));
    ...
    filter.retain;
    FFirst := false;
  end;

如果我省略了retain,那么后来对paint方法的调用会在我尝试使用过滤器时抛出异常(无法识别的选择器被发送到实例 - 因为我猜过滤器不再是TCFilter)。

但是过滤器我已经创建了一个全局变量,因此它永远不会超出范围,所以为什么我需要保留?为什么界面会丢失参考?我缺少的东西,这是在OSX上使用XE6,但我认为这同样适用于iOS,tia

编辑:这是所有的代码 - 也许还有别的我做错了。删除表单上的面板和轨迹栏并连接面板.OnPaint和trackbar.OnChange事件

unit Unit3;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  system.Rtti, FMX.Platform.Mac, FMX.Canvas.Mac, Macapi.CoreGraphics,
  FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
  Macapi.Helpers, MacApi.Foundation, Macapi.QuartzCore,
   MacApi.CocoaTypes, Macapi.ObjectiveC;

type
  TForm3 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Panel1: TPanel;
    TrackBar1: TTrackBar;
    procedure Panel1Paint(Sender: TObject; Canvas: TCanvas;
      const ARect: TRectF);
    procedure FormCreate(Sender: TObject);
    procedure TrackBar1Change(Sender: TObject);
  private
    cgImage : CGImageRef;
    FFirst : Boolean;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form3: TForm3;

const
     CoreImageFwk = '/System/Library/Frameworks/CoreImage.framework/CoreImage';

implementation

{$R *.fmx}

type
CIFilterClass = interface(NSObjectClass)
    ['{FB4A9CD9-7D60-482D-A0F4-4F78FC6E7E8D}']
    {class} function filterNamesInCategories(categories: NSArray): NSArray; cdecl;
    {class} function filterNamesInCategory(category: NSString): NSArray; cdecl;
    {class} function filterWithImageData(data: NSData; options: NSDictionary): Pointer; cdecl;
    {class} function filterWithImageURL(url: NSURL; options: NSDictionary): Pointer; cdecl;
    {class} function filterWithName(name: NSString): Pointer; cdecl; overload;
    {class} function filterWithName(name: NSString; keysAndValues: Pointer): Pointer; cdecl; overload;
    {class} function localizedDescriptionForFilterName(filterName: NSString): NSString; cdecl;
    {class} function localizedNameForCategory(category: NSString): NSString; cdecl;
    {class} function localizedNameForFilterName(filterName: NSString): NSString; cdecl;
    {class} function localizedReferenceDocumentationForFilterName(filterName: NSString): NSURL; cdecl;
  end;
  CIFilter = interface(NSObject)
    ['{2ACA27E7-D365-4AAC-A474-E72867CDE89A}']
    function apply(apply: CIKernel): CIImage; cdecl; overload;
    function apply(k: CIKernel; arguments: NSArray; options: NSDictionary): CIImage; cdecl; overload;
    function attributes: NSDictionary; cdecl;
    function inputKeys: NSArray; cdecl;
    function isEnabled: Boolean; cdecl;
    function outputKeys: NSArray; cdecl;
    procedure setDefaults; cdecl;
    // I added these 2 methods because they're not in FMX
    procedure setValue(value: pointer; forKey : NSString); cdecl;
    function outputImage : Pointer {CIImage}; cdecl;
  end;
  TCIFilter = class(TOCGenericImport<CIFilterClass, CIFilter>)  end;

procedure TForm3.FormCreate(Sender: TObject);
begin
  Ffirst := true;
end;

var
  fileNameAndPath : Macapi.Foundation.NSURL;
  beginImage, outputImage : CIImage;
  context : CIContext;
  filter : CIFilter;
  pImage : Pointer;
  p : CGPoint;
  r : CGRect;

procedure TForm3.Panel1Paint(Sender: TObject; Canvas: TCanvas;
  const ARect: TRectF);
 var
 d : double;

  function GetCGContextFromCanvas(ACanvas: TCanvas): CGContextRef;
  var
    Context: TRttiContext;
    Field: TRttiField;
  begin
    Field := Context.GetType(ACanvas.ClassType).GetField('FContext');
    Assert(Field <> nil);
    Result := PPointer(Field.GetValue(ACanvas).GetReferenceToRawData)^;
  end;



function kCIContextOutputColorSpace: NSString;
begin
  Result := CocoaNSStringConst(CoreImageFwk, 'kCIContextOutputColorSpace');
end;

begin
  if FFirst then
  begin
    fileNameAndPath := TNSUrl.Wrap(TNSUrl.OCClass.fileURLWithPath(StrToNSStr('/path/to/an/image.png')));

    beginImage := TCIImage.Wrap(TCIImage.OCClass.imageWithContentsOfURL(fileNameAndPath));
    context := TCIContext.Wrap(TCIContext.OCClass.contextWithCGContext( GetCGContextFromCanvas(Canvas), nil ));
    filter := TCIFilter.Wrap(TCIFilter.OCClass.filterWithName(NSSTR('CISepiaTone')));
    filter.setValue( (beginImage as ILocalObject).GetObjectID, NSSTR('inputImage'));
    filter.retain; // comment out this line and it crashes
    context.retain; // same with this one
    FFirst := false;
  end;

  d := TrackBar1.value/100;
  filter.setValue( TNSNumber.OCClass.numberWithFloat(d), NSSTR('inputIntensity'));


   outputImage := TCIImage.Wrap(filter.outputImage);

   cgImage :=  context.createCGImage(outputImage, outputImage.extent);
   p := CGPointMake(0,0);
   r := CGRectMake(0,0, outputImage.extent.size.width, outputImage.extent.size.height);
   CGContextDrawImage(GetCGContextFromCanvas(Canvas), r, cgImage);
   CGImageRelease(cgImage);
end;

procedure TForm3.TrackBar1Change(Sender: TObject);
begin
  Panel1.Repaint;
end;

end.

编辑2:在这里查看基本问题 Delphi XE6 ARC on OSX releasing variables

1 个答案:

答案 0 :(得分:0)

您必须记住Delphi在您的项目中使用ARC 为了做到这一点,它插入代码以增加和减少适当点的引用计数 它仅为需要引用计数的类型(接口,字符串等)插入此代码 它不会为其他类型添加此代码;特别是指针。

我强烈怀疑你的问题在这里:

procedure setValue(value: pointer; forKey : NSString); cdecl;

您将对象提供给setValue,但您将其伪装成普通指针 这意味着将发生以下情况(我在这里推测,因为您没有包含SetValue的代码)。

您复制在SetValue中传递的对象并将其分配给Filter。 (这不会增加引用计数,因为您使用的是普通指针) 旧的过滤器由value替换,并且超出范围并被销毁(这应该是这样) Filter现在是SetValue中传递的对象的副本。

同时原始对象超出范围;它的引用数量变为零并且它被破坏了 现在Filter指向已销毁的引用。

解决方案
通过将value参数设为refcounted类型或在value.retain内调用SetValue来确保自动引用计数。