Jump to content

vfbb

Members
  • Content Count

    266
  • Joined

  • Last visited

  • Days Won

    30

Posts posted by vfbb


  1. 20 hours ago, David Heffernan said:

    Not so simple. For small collections the dictionary can be slower. 

    Exactly! The Dictionary is only O(1) in the best case, as there can be many collisions of the key hashes, and this creates a loop that can reach O (n * 3/4) in the worst case, although it is very difficult. But what makes losing more performance are the grows that can occur as teams are added because the implementation maintain the count 50% -75% of the capacity, which I consider high value.

    But to avoid the many grows, and allow fewer collisions, just create it with the desired capacity. For example, for a collection of 100 items, I simply start with a capacity of 200.

     

    LDicionary := TDictionary<TKey, TValue>.Create(200);

     

    Remember, when you call Clear from the dictionary, it also removes the capacity, so you better recreate it instead of Clear.


  2. @Dave Nottage

    Hi Dave, I tested and works perfectly!! Thanks you so much, you are the man!!

     

    Just to register here the complete solution. (I preferred to patch the delphi source files because I already have others patchs in that files)

     

     

    iOS - Handle incoming url (custom schemes or universal links)

     

    In the file iOSapi.Foundation.pas put the code:

      // Dave Nottage code (https://www.delphiworlds.com/)
      NSUserActivityPersistentIdentifier = NSString;
    
      TNSUserActivityBlockMethod1 = procedure(inputStream: NSInputStream; outputStream: NSOutputStream; error: NSError) of object;
      TNSUserActivityBlockMethod2 = procedure of object;
    
      NSUserActivityClass = interface(NSObjectClass)
        ['{412EAEBF-5927-4D01-B83F-69D3B5DFE7B5}']
        {class} procedure deleteAllSavedUserActivitiesWithCompletionHandler(handler: TNSUserActivityBlockMethod2); cdecl;
        [MethodName('deleteSavedUserActivitiesWithPersistentIdentifiers:completionHandler:')]
        {class} procedure deleteSavedUserActivitiesWithPersistentIdentifiers(persistentIdentifiers: NSArray; handler: TNSUserActivityBlockMethod2); cdecl;
      end;
    
      NSUserActivity = interface(NSObject)
        ['{B8C2F6C9-31FE-4282-B7CA-98C96E163033}']
        function activityType: NSString; cdecl;
        procedure addUserInfoEntriesFromDictionary(otherDictionary: NSDictionary); cdecl;
        procedure becomeCurrent; cdecl;
        function delegate: Pointer; cdecl;
        function expirationDate: NSDate; cdecl;
        procedure getContinuationStreamsWithCompletionHandler(completionHandler: TNSUserActivityBlockMethod1); cdecl;
        function initWithActivityType(activityType: NSString): Pointer; cdecl;
        procedure invalidate; cdecl;
        function isEligibleForHandoff: Boolean; cdecl;
        function isEligibleForPrediction: Boolean; cdecl;
        function isEligibleForPublicIndexing: Boolean; cdecl;
        function isEligibleForSearch: Boolean; cdecl;
        function keywords: NSSet; cdecl;
        function needsSave: Boolean; cdecl;
        function persistentIdentifier: NSUserActivityPersistentIdentifier; cdecl;
        function referrerURL: NSURL; cdecl;
        function requiredUserInfoKeys: NSSet; cdecl;
        procedure resignCurrent; cdecl;
        procedure setDelegate(delegate: Pointer); cdecl;
        procedure setEligibleForHandoff(eligibleForHandoff: Boolean); cdecl;
        procedure setEligibleForPrediction(eligibleForPrediction: Boolean); cdecl;
        procedure setEligibleForPublicIndexing(eligibleForPublicIndexing: Boolean); cdecl;
        procedure setEligibleForSearch(eligibleForSearch: Boolean); cdecl;
        procedure setExpirationDate(expirationDate: NSDate); cdecl;
        procedure setKeywords(keywords: NSSet); cdecl;
        procedure setNeedsSave(needsSave: Boolean); cdecl;
        procedure setPersistentIdentifier(persistentIdentifier: NSUserActivityPersistentIdentifier); cdecl;
        procedure setReferrerURL(referrerURL: NSURL); cdecl;
        procedure setRequiredUserInfoKeys(requiredUserInfoKeys: NSSet); cdecl;
        procedure setSupportsContinuationStreams(supportsContinuationStreams: Boolean); cdecl;
        procedure setTargetContentIdentifier(targetContentIdentifier: NSString); cdecl;
        procedure setTitle(title: NSString); cdecl;
        procedure setUserInfo(userInfo: NSDictionary); cdecl;
        procedure setWebpageURL(webpageURL: NSURL); cdecl;
        function supportsContinuationStreams: Boolean; cdecl;
        function targetContentIdentifier: NSString; cdecl;
        function title: NSString; cdecl;
        function userInfo: NSDictionary; cdecl;
        function webpageURL: NSURL; cdecl;
      end;
      TNSUserActivity = class(TOCGenericImport<NSUserActivityClass, NSUserActivity>) end;
      
    ...  
    function NSUserActivityTypeBrowsingWeb: NSString;
    ...
    implementation
    ...
    function NSUserActivityTypeBrowsingWeb: NSString;
    begin
      result := CocoaNSStringConst(FoundationFwk, 'NSUserActivityTypeBrowsingWeb');
    end;

     

    In the file FMX.Platform.iOS.pas, in the TApplicationDelegate class, in the private section, put the code:

        class function applicationContinueUserActivityRestorationHandler(self: id; _cmd: SEL; application: PUIApplication;
          userActivity: Pointer; restorationHandler: Pointer; restorableObjects: Pointer): Boolean; cdecl; static;

     

    In the file FMX.Platform.iOS.pas, in the implementation of the method TApplicationDelegate.CreateDelegateMetaClass, before the line "objc_registerClassPair(DelegateClass);", put the code:

      class_addMethod(DelegateClass, sel_getUid('application:continueUserActivity:restorationHandler:'),
          @applicationContinueUserActivityRestorationHandler, 'B@:@@@@');

     

    In the file FMX.Platform.iOS.pas, in the TApplicationDelegate implementation, put the code:

    class function TApplicationDelegate.applicationContinueUserActivityRestorationHandler(
      self: id; _cmd: SEL; application: PUIApplication; userActivity,
      restorationHandler, restorableObjects: Pointer): Boolean;
    var
      LUserActivity: NSUserActivity;
      LURLString: string;
    begin
      Result := False;
      if Assigned(userActivity) then
      begin
        LUserActivity := TNSUserActivity.Wrap(userActivity);
        if NSStrToStr(LUserActivity.activityType) = NSStrToStr(NSUserActivityTypeBrowsingWeb) then
        begin
          if Assigned(LUserActivity.webpageURL) then
            LURLString := NSStrToStr(LUserActivity.webpageURL.absoluteString)
          else
            LURLString := string.Empty;
    
          Result := PlatformCocoaTouch.HandleApplicationEvent(TApplicationEvent.OpenURL,
            TiOSOpenApplicationContext.Create(string.Empty, LURLString, nil));
        end;
      end;
    end;

     

     

    Usage

     

    uses
      System.Messaging,
      FMX.Platform,
      FMX.Platform.iOS,
      FMX.Dialogs;
    
    constructor TipUrlHandler.Create;
    begin
      inherited Create;
      TMessageManager.DefaultManager.SubscribeToMessage(TApplicationEventMessage, ApplicationEventMessageHandler);
    end;
    
    destructor TipUrlHandler.Destroy;
    begin
      TMessageManager.DefaultManager.Unsubscribe(TApplicationEventMessage, ApplicationEventMessageHandler, True);
      inherited;
    end;
    
    procedure TipUrlHandler.ApplicationEventMessageHandler(const ASender: TObject;
      const AMessage: TMessage);
    begin
      case TApplicationEventData(TApplicationEventMessage(AMessage).Value).Event of
        TApplicationEvent.OpenUrl:
          begin
            Showmessage(TiOSOpenApplicationContext(TApplicationEventData(TApplicationEventMessage(AMessage).Value).Context).URL);
          end;
      else
      end;
    end;

     

    • Like 4

  3. Goal: I have configured my app to be opened by some links in iOS, like myapp://lala (using custom scheme), or https://myapp.com/c/lala using universal links, and is already opening my app, but i need to know which url that opened my app. (There is even a tutorial in FMX Express)

    Problem: I'm trying to handle incoming url from my ios app unsuccessfully.

     

    On iOS this should be done by capturing event TApplicationEvent.OpenURL like:

    procedure TipUrlHandler.ApplicationEventMessageHandler(const ASender: TObject;
      const AMessage: TMessage);
    begin
      case TApplicationEventData(TApplicationEventMessage(AMessage).Value).Event of
        TApplicationEvent.OpenUrl: HandleUrl(TiOSOpenApplicationContext(TApplicationEventData(TApplicationEventMessage(AMessage).Value).Context).URL);
      else
      end;
    end;

    Mas o TApplicationEvent.OpenUrl nunca é chamado. So I checked the class TApplicationDelegate in the unit FMX.Platform.iOS.pas in which the TApplicationDelegate.applicationOpenURLWithOptions function should be called every time the app is opened by a url as Apple documents: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application?language=objc

    class function TApplicationDelegate.applicationOpenURLWithOptions(self: id; _cmd: SEL; application: PUIApplication;
      url: Pointer; options: PNSDictionary): Boolean;

    So I went to check if the applicationOpenURLWithOptions method is being added correctly in TApplicationDelegate.CreateDelegateMetaClass:

    class procedure TApplicationDelegate.CreateDelegateMetaClass;
    begin
      ...
      // Opening a URL-Specified Resource
      if TOSVersion.Major >= 9 then
        class_addMethod(DelegateClass, sel_getUid('application:openURL:options:'),
          @applicationOpenURLWithOptions, 'B@:@@@@')
      else
      ...
    end;

    And then I was able to prove it wrong! The number of arguments declared is wrong, as the correct one should be B@:@@@

    Then I made the modification to FMX.Platform.iOS.pas, being as follows:

    class procedure TApplicationDelegate.CreateDelegateMetaClass;
    begin
      ...
      // Opening a URL-Specified Resource
      if TOSVersion.Major >= 9 then
        class_addMethod(DelegateClass, sel_getUid('application:openURL:options:'),
          @applicationOpenURLWithOptions, 'B@:@@@')
      else
      ...
    end;

    But the TApplicationDelegate.applicationOpenURLWithOptions function is not yet called.

    What is wrong?

     

     

     

    @EDIT

    Sorry! It is working for custom schemes like "myapp://lala" (with or without the correction in FMX.Platform.iOS.pas) but it is not working for universal links like https://myapp.com/c/lalala.

     

    Although I have already configured the universal link correctly and when I click on this universal link, the iOS open my app, but the url can't be handle with TApplicationEvent.OpenUrl, but I discovered that the handling of incoming url of a universal link is different: Handling Universal Links


  4. Out of memory

    I really like to use Grijjy Cloud Logger. You can monitor all objects in your program in real time, and each time you update this list it will indicate the growth (in number of instances and memory) of each class. Best of all, this can be done in both debug mode and release mode. Use this only in the development environment as there will be a small loss of performance.

     

    Move to 64 bits

    I advise you to start writing code with 64-bit support as soon as possible, this is the future, because although microsoft is not forcing this migration yet, several others have already done it, such as Android, iOS and OSX which today only allow 64 bits. It is a trend even from Embarcadero itself since it added support for Linux only 64 bits. But, of course, keep support for 32-bit too, as there may be users with 32-bit windows still and also because the Win32 debugger is better than Win64. 😉


  5. 5 hours ago, Kas Ob. said:

    You should lock with the same lock so it must be Lock..Unlock or BeginUpdate..EndUpdate for both reading and writing.

    Yes, the BeginUpdate call Lock internally, and the EndUpdate call Unlock. The same critical section. Just one critical section per object.

     

    20 hours ago, Georgge Bakh said:

    Additionally, with some logic in change event handler there is a risk of deadlock.

    Yes, my Change event is completely asynchronous, similar to the fmx messaging system (subscribe / unsubscribe), but it uses X threads to send these messages from one thread to another.

     

    2 hours ago, Lars Fosdal said:

    Multiple threads, multiple objects, cross-thread cross-object access and micro locking per field using the same single lock per object = high risk of deadlock.

    I don't like the term "big risk" or "low risk" of deadlock. A good design has to be done with the chance of zero deadlock, locking the object just to copy the data to the local var of the thread or else locking only to write local var of the thread in the object (without executing anything inside locking).

     

    Example:

    procedure TipThread.Execute
    var
      LName: string;
      LId: Cardinal;
    begin
      // Read copying to local var
      LUser.Lock;
      try
        LName := LUser.Name;
        LId := LUser.Id;
      finally
        LUser.Unlock;
      end;
    
      // Run your code...
    
      // Write from local var
      LUser.BeginUpdate;
      try
        LUser.Name := LName;
        LUser.Id := LId;
      finally
        LUser.EndUpdate;
      end;
    end;

    This will never cause a deadlock.

    • Like 1

  6. I have a base data class that is used for multithreaded data classess in my project.
    I made an implementation to reduce thousands of lines of code, which theoretically should work, but even with some successful tests, I am afraid to release a version to the end user without being sure.

    function TipCustomData.GetSafeValue(var AField: string): string;
    begin
      Lock; // Enter critical section
      try
        Result := AField;
      finally
        Unlock; // Leave critical section
      end;
    end;
    
    procedure TipCustomData.SetSafeValue(var AField: string; const AValue: string);
    begin
      BeginUpdate; // Enter critical section
      try
        if AField <> AValue then
        begin
          AField := AValue;
          Change;
        end;
      finally
        EndUpdate; // Leave critical section triggering change event
      end;
    end;

    In the code above, the most intuitive in these cases would be to use the field pointer, but as "var" parameters internally it is already passed as a pointer (regardless of type), so both implementations should work in the same theoretical way (correct me if i'm wrong). Of course, I only access the parameters within the critical section.

    The implementation of my multithreaded data class, descendant of this, would be for example like this:

    type
      TipUser = class(TipCustomData)
      private
        FName: string;
        function GetName: string;
        procedure SetName(const AValue: string);
      public
        property Name: string read GetName write SetName;
      end;
    
    function TipUser.GetName: string;
    begin
      Result := GetSafeValue(FName);
    end;
    
    function TipUser.SetName(const AValue: string);
    begin
      Result := SetSafeValue(FName, AValue);
    end;

    So the question is, is this cleaner implementation that could exist of a multithreaded data class, is it really thread-safe?

     

    Sorry for my bad english. 😉

×