Recent

Author Topic: Objective Pascal delegates problem?  (Read 5389 times)

MISV

  • Hero Member
  • *****
  • Posts: 772
Objective Pascal delegates problem?
« on: January 16, 2020, 01:38:53 pm »
I have for some time tried to solve an issue implementing a not-basic-but-standard delegate, and I run into problems which is not covered by examples. Posting here, but also want to know if you have a better suggestion for posting (e.g. mailing list?)



Code: Pascal  [Select][+][-]
  1. {$mode objfpc} // changes default *string* to *ShortString*
  2. {$LONGSTRINGS ON} // enforces *string* to be a long string (native string type)
  3. {$modeswitch objectivec1}
  4. {$modeswitch objectivec2}
  5.  

Code: Pascal  [Select][+][-]
  1. (*  
  2.   https://developer.apple.com/documentation/foundation/nsurlconnectiondatadelegate  
  3.   NSURLConnectionDataDelegate inherits from NSURLConnectionDelegate
  4.   // Maybe? TmsMacRequestDelegate = objcclass(NSObject, NSURLConnectionDataDelegate)
  5. *)
  6.  

Code: Pascal  [Select][+][-]
  1.   TmsMacRequestDelegate = objcclass(NSObject)
  2.   public
  3.     procedure connectionDidFinishLoading(ANSUC: NSURLConnection); message 'connectionDidFinishLoading:';
  4.     procedure connection(ANSUC: NSURLConnection; didReceive: NSURLResponse); message 'connection::';
  5.     procedure connection(ANSUC: NSURLConnection; didReceive: NSData); message 'connection::';
  6.     // actually need to implement more but the above is enough to trigger compile time errors
  7.   end;
  8.  

So if I compile the above I get error

Quote
Error: Inherited methods can only be overridden in Objective-C and Java, add "override" (inherited method defined in NSURLConnectionDelegateCategory

If I add "override" like suggested I get
Quote
Error: <stdin>:219:1: error: invalid symbol redefinition
Error: "-TmsMacRequestDeletegate connection::]":
Error: ^

For reference I need to implement the deletegate and use it like this:

Code: Pascal  [Select][+][-]
  1. requestDelegate := TmsMacRequestDelete.alloc.init;
  2. urlConnection := NSURLConnection.connectionWithRequest_delete(urlRequest, requestDelegate)
  3. ...
  4. urlConnection.start;
  5.  

The reason is that using NSURLConnection without the delegate, you can not "track" redirects (instead these are silently accepted/handled/processed)

skalogryz

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 2770
    • havefunsoft.com
Re: Objective Pascal delegates problem?
« Reply #1 on: January 16, 2020, 03:44:30 pm »
try to change this
Code: Pascal  [Select][+][-]
  1.     procedure connection(ANSUC: NSURLConnection; didReceive: NSURLResponse); message 'connection::';
  2.     procedure connection(ANSUC: NSURLConnection; didReceive: NSData); message 'connection::';
  3.  
into
Code: Pascal  [Select][+][-]
  1.     procedure connection(ANSUC: NSURLConnection; didReceive: NSURLResponse); message 'connection:didReceiveResponse:';
  2.     procedure connection(ANSUC: NSURLConnection; didReceive: NSData); message 'connection:didReceiveData:';
  3.  
or even better
Code: Pascal  [Select][+][-]
  1.     procedure connection_didReceiveResponse(ANSUC: NSURLConnection; didReceive: NSURLResponse); message 'connection:didReceiveResponse:';
  2.     procedure connection_didReceiveData(ANSUC: NSURLConnection; didReceive: NSData); message 'connection:didReceiveData:';
  3.  

MISV

  • Hero Member
  • *****
  • Posts: 772
Re: Objective Pascal delegates problem?
« Reply #2 on: January 16, 2020, 04:05:47 pm »
Doing as you suggested + adding override to the first method (as suggested by compiler) actual seems to work.... This is the furthest it has been to compile in two years :)



Hereby posting the complete delcaration which I (in past) concluded I have to implement to satisfy requirements when implementing the delegates

i.e. if I understood/guessed correctly, it is not sufficient to only partially implement the ones you need.

Code: Pascal  [Select][+][-]
  1.   TmsMacRequestDelegate = objcclass(NSObject)
  2.   public
  3.     procedure connectionDidFinishLoading(ANSUC: NSURLConnection); message 'connectionDidFinishLoading:'; override;
  4.     procedure connection(ANSUC: NSURLConnection; didReceive: NSURLResponse); message 'connection:didReceiveResponse:';
  5.     procedure connection(ANSUC: NSURLConnection; didReceive: NSData); message 'connection:didReceiveData:';
  6.     // declaration not yet fixed
  7.     procedure connection(ANSUC: NSURLConnection; didSendBodyData: Integer; totalBytesWritten: Integer; totalBytesExpectedToWrite: Integer); message 'connection::::';
  8.     procedure connection(ANSUC: NSURLConnection; willSend: NSURLRequest; redirectResponse: PmsNSURLResponse); message 'connection:::';
  9.     procedure connection(ANSUC: NSURLConnection; willCacheResponse: NSCachedURLResponse); message 'connection::';
  10.     // declaration not yet fixed  // optional?
  11.     procedure connection(ANSUC: NSURLConnection; didFailWithError: NSError); message 'connection::';
  12.   end;
  13.  

...

Is this something FPC compiler makes work?

Code: Pascal  [Select][+][-]
  1. procedure connection_didReceiveResponse(ANSUC: NSURLConnection; didReceive: NSURLResponse); message 'connection:didReceiveResponse:';
  2. procedure connection_didReceiveData(ANSUC: NSURLConnection; didReceive: NSData); message 'connection:didReceiveData:';

The reason is that the Apple API does not give different names...

I like this naming better of course :) but I feel I am left a bit guessing on the innerworkings of FPC/ObjectivePascal.

Do you know if the details are documented anywhere? (I have Google'd many times) or is the source the only way?

...

Can I ask what you would translate

Code: Pascal  [Select][+][-]
  1.     procedure connection(ANSUC: NSURLConnection; didSendBodyData: Integer; totalBytesWritten: Integer; totalBytesExpectedToWrite: Integer); message 'connection::::';

to?

And I will do the rest tonight and give it a go if I can get it all working and post results, so others can find this thread and hopefully it can help them as well :)

skalogryz

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 2770
    • havefunsoft.com
Re: Objective Pascal delegates problem?
« Reply #3 on: January 16, 2020, 04:30:04 pm »
Here https://wiki.freepascal.org/FPC_PasCocoa
and here https://wiki.freepascal.org/FPC_PasCocoa/Differences#Marking_methods_as_override

Long story:
1)
The reason is that the Apple API does not give different names...

I like this naming better of course :) but I feel I am left a bit guessing on the innerworkings of FPC/ObjectivePascal.
Apple API gives different names. As a matter of fact, it HAS to.
In Objective-C the name of the method is also an "address" of the method aka "SELECTOR".

Thus it's impossible in ObjC to do a method overloading, similar to Pascal manner.

Here's an example;
Code: [Select]
procedure foo(a: Integer); overload;
procedure foo(d: double); overload;
It's perfectly normal way of overloading in pascal. (behind the scenes, the compiler will distinguish between to procedures, by giving them a symbol names, like:
FOO_INT
FOO_DOUBLE.

In ObjC, the declaration of the method, also dictates its name:
Code: [Select]
void foo:(int) a
However, the name will not contain type information, but also a presence of an parameter.
So the "selector" for the method will be "foo:"

For this particular reason an attempt to declare overloaded method:
Code: [Select]
void foo:(double) a
will fail (or get runtime confused), as the selector is identical "foo:"

As a result, the actual "names" (selectors) in Apples are pretty long:
Code: [Select]
- (void)connection:(NSURLConnection *)connection
    didReceiveData:(NSData *)data;
selectors is "connection:didReceiveData:".
But, due to ObjC syntax, calling a method and passing parameters are somewhat mixed, and makes the code look compact.
Code: [Select]
  [obj connection: conn didReceiveData: data]
unlike similar call in pascal (type out the name first and then entire list of parameters)
Code: [Select]
  connection_didReceiveData(conn, data);

2)
Now, when dealing with Objective Pascal, the FPC compiler needs to what ObjC selector needs to be called for each method.
The FPC CANNOT use the pascal "part" of the declaration.


It cannot automatically translated
Code: [Select]
procedure connection(ANSUC: NSURLConnection;
  didSendBodyData: Integer;
  totalBytesWritten: Integer;
  totalBytesExpectedToWrite: Integer
  )
into a selected, for the following reasons:
1) Pascal is case insensitive, while ObjC is.
2) The name of the parameter is pascal is meaningless for the call (and so it can be later changed). While in ObjC it should "matter".
3) ...and others, which i don't remember...

For this particular reason a special keyword "message" is used in Objective Pascal. (the "keyword" was introduced in Delphi for a somewhat similar task in Windows event handling".
the keyword "message" specifies the exact ObjC selector that needs to be called.

That helps to make "Pascal syntax declaration" to be independent of ObjC.
Thus you can help a somewhat friendly pascal name for you method
Code: [Select]
procedure dataBytesWritten(ANSUC: NSURLConnection;
  bytesWritten: Integer;
  totalBytesWritten: Integer;
  totalBytesExpectedToWrite: Integer
  ); message 'connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:'
instead of
Code: [Select]
procedure connection_didSendBodyData_totalBytesWritten_totalBytesExpectedToWrite(ANSUC: NSURLConnection;
  bytesWritten: Integer;
  totalBytesWritten: Integer;
  totalBytesExpectedToWrite: Integer
  ); message 'connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:'
or naming everything "connection"

3)
Can I ask what you would translate
Code: Pascal  [Select][+][-]
  1.     procedure connection(ANSUC: NSURLConnection;
  2.   didSendBodyData: Integer;
  3.   totalBytesWritten: Integer;
  4.   totalBytesExpectedToWrite: Integer
  5.   ); message 'connection::::';

You need to refer to Apple documentation for the answer.
The delegate declaration can be found here: https://developer.apple.com/documentation/foundation/nsurlconnectiondatadelegate?language=objc
or more specifically here: https://developer.apple.com/documentation/foundation/nsurlconnectiondatadelegate/1418264-connection?language=objc

The page actually starts with the selector name: "connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:"
All you need to do, is to put into your pascal declaration as a value of 'message'

Code: Pascal  [Select][+][-]
  1.     procedure connection(ANSUC: NSURLConnection;
  2.   didSendBodyData: Integer;
  3.   totalBytesWritten: Integer;
  4.   totalBytesExpectedToWrite: Integer
  5.   ); message 'connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:';
« Last Edit: January 16, 2020, 04:42:40 pm by skalogryz »

MISV

  • Hero Member
  • *****
  • Posts: 772
Re: Objective Pascal delegates problem?
« Reply #4 on: January 17, 2020, 08:07:10 am »
*Thank you* Will be back ASAP when I have worked through it, caught up on further reading etc. and given it my best at making it work.

MISV

  • Hero Member
  • *****
  • Posts: 772
Re: Objective Pascal delegates problem?
« Reply #5 on: January 18, 2020, 01:39:04 am »
I see I have been looking at:
https://developer.apple.com/documentation/foundation/nsurlconnectiondatadelegate

which gives generic "connection" methods instead of objective-c
https://developer.apple.com/documentation/foundation/nsurlconnectiondatadelegate?language=objc

I believe I can get the declaration correct now - something like:

Code: Pascal  [Select][+][-]
  1.   TmsMacRequestDelegate = objcclass(NSObject)
  2.   public
  3.     procedure connectionDidFinishLoading(ANSUC: NSURLConnection);
  4.       message 'connectionDidFinishLoading:'; override;
  5.     procedure connection(ANSUC: NSURLConnection; didReceive: NSURLResponse);
  6.       message 'connection:didReceiveResponse:';
  7.     procedure connection(ANSUC: NSURLConnection; didReceive: NSData);
  8.       message 'connection:didReceiveData:';
  9.     procedure connection(ANSUC: NSURLConnection; didSendBodyData: Integer; totalBytesWritten: Integer; totalBytesExpectedToWrite: Integer);
  10.       message 'connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:';
  11.     procedure connection(ANSUC: NSURLConnection; willSend: NSURLRequest; redirectResponse: PmsNSURLResponse);
  12.       message 'connection:willSendRequest:redirectResponse:';
  13.     procedure connection(ANSUC: NSURLConnection; willCacheResponse: NSCachedURLResponse);
  14.       message 'connection:willCacheResponse:';
  15.     procedure connection(ANSUC: NSURLConnection; didFailWithError: NSError);
  16.       message 'connection:didFailWithError:';
  17.   end;
  18.  



I may be way too tired, but I am still puzzled about the "override" in

Code: Pascal  [Select][+][-]
  1.     procedure connectionDidFinishLoading(ANSUC: NSURLConnection);
  2.       message 'connectionDidFinishLoading:'; override;
  3.  

1)
If I understand this correct:
https://wiki.freepascal.org/FPC_PasCocoa/Differences#Marking_methods_as_override

The override should only be necessary if the ObjC class we are inheriting from has a function/selector of the same name...

But here is our declaration

Code: Pascal  [Select][+][-]
  1. TmsMacRequestDelegate = objcclass(NSObject)

and I doubt NSObject has a selector called connectionDidFinishLoading

Or is the "override" related to we are implementing NSURLConnectionDataDelegate (interface'ish) which "inherits" from NSURLConnectionDelegate? S
till, how does FPC *know* - we are not anywhere letting FPC explicitly know the delegate.

2)
Under all circumstances, I am not sure why "override" is then only necessary for the first function

MISV

  • Hero Member
  • *****
  • Posts: 772
Re: Objective Pascal delegates problem?
« Reply #6 on: January 19, 2020, 07:36:21 pm »
Everything compiles. However, delegate methods are not called...

But searching Google it seems that when NSURlConnection is used with delegates it *has* to called from main thread / NSDefaultRunLoopMode  or it simply won't work.

(And I am calling it from a worker thread. Not that I mind if delegate methods are called in main thread, I use critical sections anyhow to access data, so I make/made no assumptions in my code about what thread will call delegate methods. It is just so that the core design of my app means the code creating/starting the urlconnection will be from non-main-thread)

https://github.com/rs/SDWebImage/issues/213#issuecomment-10102989
https://stackoverflow.com/questions/5787170/nsurlconnection-delegate-methods-are-not-called
https://stackoverflow.com/questions/706355/whats-wrong-on-following-urlconnection

...

However, using one of the proposed solution does not make any difference:

I have tried:

Code: Pascal  [Select][+][-]
  1. urlConnection.scheduleInRunLoop_forMode(NSRunLoop.mainRunLoop, NSDefaultRunLoopMode);
  2. urlConnection.start
  3.  
...

Can anyone thing of any other reason delegate methods are not being called?

Maybe something specific to FPC/LCL-Cocoa / Mac API  / threading / runloops?

Or maybe something maybe still not correct how I declared the delegate?
« Last Edit: January 19, 2020, 08:05:44 pm by MISV »

skalogryz

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 2770
    • havefunsoft.com
Re: Objective Pascal delegates problem?
« Reply #7 on: January 20, 2020, 01:10:53 am »
not related to your last issue, but

Quote
Error: Inherited methods can only be overridden in Objective-C and Java, add "override" (inherited method defined in NSURLConnectionDelegateCategory
the issue is with Cocoa headers in FPC.
Delegates are not implemented as Categories, instead they're typically implemented as Protocols.
NSURLConnectionDelegate protocol.

This needs to be reported to FPC team.

MISV

  • Hero Member
  • *****
  • Posts: 772
Re: Objective Pascal delegates problem?
« Reply #8 on: January 20, 2020, 09:32:51 am »
One quick question

Do you know if
Code: Pascal  [Select][+][-]
  1. urlConnection.scheduleInRunLoop_forMode(NSRunLoop.mainRunLoop, NSDefaultRunLoopMode);
  2. urlConnection.start

will work in LCL-Cooca?

i.e. reading up on internals, I can see there have been many different ways to handle run/loops in Cocoa?

Would above code work in LCL-Cooca? The solution above is the one I see for ObjectiveC users, but if LCL-Cooca maybe does something trickery, it could explain why I do not have delegate methods being called (since apparently if you call *urlConnection.start* from anywhere but mainloop, it simply won't work)

MISV

  • Hero Member
  • *****
  • Posts: 772
Re: Objective Pascal delegates problem?
« Reply #9 on: January 20, 2020, 10:14:57 am »
This needs to be reported to FPC team.

Do you mean that it is an error I am receiving any compiler error? (i..e override should not be necessary)

If so I can report it

MISV

  • Hero Member
  • *****
  • Posts: 772
Re: Objective Pascal delegates problem?
« Reply #10 on: January 20, 2020, 01:54:38 pm »
One quick question

Do you know if
Code: Pascal  [Select][+][-]
  1. urlConnection.scheduleInRunLoop_forMode(NSRunLoop.mainRunLoop, NSDefaultRunLoopMode);
  2. urlConnection.start

will work in LCL-Cooca?

I also tried

This hackish solution also does not work

Code: Pascal  [Select][+][-]
  1.       TmpURLConnectionSync := TmsURLConnectionSync.Create;
  2.       TmpURLConnectionSync.UrlConnRef := urlConnection;
  3.       TThread.Synchronize(nil,@TmpURLConnectionSync.Start); // UrlConnRef.start;
  4.       TmpURLConnectionSync.Free;
  5.  


This also does not work. No method delegates fired. But I can't be sure if any of the two solutions correctly ensures the correct runloop is used and executed

But seeing I tried two different ways, one could start suspect there is something not working with the delegates methods. (I have urlconnection working fine when not using delegates, but I need the delegate methods) 

MISV

  • Hero Member
  • *****
  • Posts: 772
Re: Objective Pascal delegates problem?
« Reply #11 on: January 20, 2020, 11:14:38 pm »
Nevermind,. I made progress. I will post code later.

MISV

  • Hero Member
  • *****
  • Posts: 772
Re: Objective Pascal delegates problem?
« Reply #12 on: January 21, 2020, 01:14:28 pm »
Code: Pascal  [Select][+][-]
  1.       requestDelegate := TmsMacRequestDelegate.alloc.init;
  2.       urlConnection := NSURLConnection.alloc.initWithRequest_delegate_startImmediately(urlRequest, requestDelegate, False);
  3.       urlConnection.scheduleInRunLoop_forMode(NSRunLoop.mainRunLoop, NSDefaultRunLoopMode);
  4.  

MISV

  • Hero Member
  • *****
  • Posts: 772
Re: Objective Pascal delegates problem?
« Reply #13 on: January 21, 2020, 06:13:38 pm »
Code: Pascal  [Select][+][-]
  1.   TmsMacRequestDelegate = objcclass(NSObject)
  2.   public
  3.     procedure connectionDidFinishLoading(ANSUC: NSURLConnection);
  4.       message 'connectionDidFinishLoading:'; override;
  5.     procedure connection(ANSUC: NSURLConnection; didReceive: NSURLResponse);
  6.       message 'connection:didReceiveResponse:';
  7.     procedure connection(ANSUC: NSURLConnection; didReceive: NSData);
  8.       message 'connection:didReceiveData:';
  9.     procedure connection(ANSUC: NSURLConnection; didSendBodyData: Integer; totalBytesWritten: Integer; totalBytesExpectedToWrite: Integer);
  10.       message 'connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:';
  11.     function connection(ANSUC: NSURLConnection; willSend: NSURLRequest; redirectResponse: NSURLResponse): NSURLRequest;
  12.       message 'connection:willSendRequest:redirectResponse:';
  13.     function connection(ANSUC: NSURLConnection; willCacheResponse: NSCachedURLResponse): NSCachedURLResponse;
  14.       message 'connection:willCacheResponse:';
  15.     procedure connection(ANSUC: NSURLConnection; didFailWithError: NSError);
  16.       message 'connection:didFailWithError:';
  17.   end;
  18.  

One question as it stoped working at all again... And I suspect that is because I have been fiddling with declarations  >:D

1)
Does functions in FreePascal/ObjC binding require a different kind of selector string?

2)
Also If the ObjectiveC API states
Code: Pascal  [Select][+][-]
  1. (NSUrlConnection *) connection

I currently translate that to NSURLConnection .... but should it be PNSURLConnection?

e.g. this from Apple help in ObjectiveC and Swift
Code: Pascal  [Select][+][-]
  1. -(void)connectionDidFinishLoading:(NSURLConnection *)connection;
  2.  
Code: Pascal  [Select][+][-]
  1. optional func connectionDidFinishLoading(_ connection: NSURlConnection)

MISV

  • Hero Member
  • *****
  • Posts: 772
Re: Objective Pascal delegates problem?
« Reply #14 on: January 22, 2020, 10:37:12 am »
I believe this answers question #2 of mine... if if a Objective-C function (NSURLConnection *)connection; that is in objective pascal connection: NSURLConnection

From
https://wiki.freepascal.org/FPC_PasCocoa/Differences#Class_instances_are_implicit_pointers

Quote
Class instances are implicit pointers
Description: In (Delphi-style) Object Pascal, all class instance variables are implicit pointers, and they are also implicitly dereferenced. In Objective-C, class instances are explicitly declared as being "a pointer to a class instance", e.g. NSObject *obj; and need to be explicitly dereferenced when accessing instance variables (although this seldom happens, since most of the time accessors or properties are used). We have chosen to follow the Object Pascal way for Objective-Pascal to make it more familiar for people coming from Object Pascal, and because (given the identifier naming constraints outlined below) using a non-dereferenced form of Objective-C class would hardly ever be wanted in Pascal.
What to do: Do not use any explicit indirection when dealing with Objective-Pascal class instances.

 

TinyPortal © 2005-2018