-
Content Count
266 -
Joined
-
Last visited
-
Days Won
30
Posts posted by vfbb
-
-
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;
- 4
-
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
-
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. 😉
-
@Hans♫ in the iTunes you can download each user crash report file and view the call stack to identify the problem. You will need to symbolize the crash file using the dSYM file of the version of your app thats crashed.
Read about:
-
Exploring the .crash files generated by the iOS symbolizing it to identify the call stack
https://github.com/viniciusfbb/fmx_tutorials/blob/master/delphi_ios_exploring_app_crashes/README.md
- 3
- 3
-
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.
- 1
-
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. 😉
Dictionaries, Hashing and Performance
in Algorithms, Data Structures and Class Design
Posted · Edited by vfbb
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.
Remember, when you call Clear from the dictionary, it also removes the capacity, so you better recreate it instead of Clear.