Cocoa Internals/OS Versions

From Lazarus wiki
Revision as of 22:22, 11 June 2019 by Jwdietrich (talk | contribs) (→‎AppKit Version: Adding release versions)
Jump to navigationJump to search

The page coverages on issues of (backwards) compatibility and API deprecation across different macOS version

AppStore critical

If you're building an application with LCL Cocoa, and having issues with uploading to AppStore, due to "Found private symbol usage"

Please bug report the issue, if it is caused by a function or a method used in LCL Cocoa.

Cocoa Widgetset is expected to be AppStore friendly. (as AppStore is being made the core environment for Apple apps distribution).

Please note that the application can still be deployed outside of the AppStore (unlike iOS).

macOS API Deprecation

It's quite common for Apple to introduce methods in one version of the system and deprecate in another version. Chances are high, that a certain method would be removed in later version completely (Thus, if method is not verified in run-time, the application could stop working, i.e. failing to load the app).

Suggestions:

  • use non-deprecated methods (there are plenty of APIs that are in effect since macOS 10.0 and are not planned for deprecation)
  • if deprecated method is in use and being replaced by some other newly introduced method - check the availability in runtime
if the latest method is available - use it first
if the latest method is not available - fallback to the original method

That would allow to execute the same code on earlier and later macOS versions.

Objective-C methods

use ObjCSelector with respondsToSelector() check, prior to the explicit call. If an object gets a selector it's unable to handle, ObjectiveC would throw an exception and the application would crash.

  if Assigned(win) then
  begin
    if win.respondsToSelector( ObjCSelector('backingScaleFactor')) then
      Result := win.backingScaleFactor
    else if win.respondsToSelector( ObjCSelector('userSpaceScaleFactor')) then // for older OSX
      Result := win.userSpaceScaleFactor;
  end;

(Plain) Functions

It's possible that a new feature requires a function that has been introduced in a later version of macOS. In this case a straight use of the function would cause a load-time fail on earlier versions of OSX.

In order to prevent that, a dynamic loading of the function needs to be used.

uses ... 
  dl, dynlibs;
...
  p := GetProcedureAddress(TLibHandle(RTLD_DEFAULT), 'CGDisplayCreateImage');

The following needs to be taken into consideration:

  • the use of RTLD_DEFAULT - searches for the function name across all loaded libraries. The system library would like be loaded by that time. However, if not the actual library name needs to be loaded. (unlike Linux, macOS doesn't require the full library path to be specified)
  • if the symbol is not present, then the code should handle it gracefully (instead of calling to unspecified function)

AppKit Version

In many cases you can call a certain functionality based on the AppKit version. NSAppKitVersionNumber is a shared global C-variable, thus the check of the value is performance friendly.

Values to be compared to are constants (declared at cocoa_extra.pas):

NSAppKitVersionNumber10_4 = 824;
NSAppKitVersionNumber10_5 = 949;
NSAppKitVersionNumber10_6 = 1038;
NSAppKitVersionNumber10_7 = 1138;
NSAppKitVersionNumber10_8 = 1187;
NSAppKitVersionNumber10_9 = 1265;
NSAppKitVersionNumber10_10 = 1343;
NSAppKitVersionNumber10_11 = 1404;
NSAppKitVersionNumber10_12 = 1504;
NSAppKitVersionNumber10_13 = 1561;
NSAppKitVersionNumber10_14 = 1641.10; 

Each version corresponds to a particular macOS release (or even sub-release in some cases). Major versions are denoted by an integer number, minor versions and release by the fractional part. To use macOS 10.13 as an example this is expressed as follows:

macOS version Value of NSAppKitVersionNumber
macOS 10.13 1561
macOS 10.13.1 1561.1
macOS 10.13.2 1561.2
macOS 10.13.3 1561.3
macOS 10.13.4 1561.4

The values are taken from macOS header files (provided with Xcode)

Version Specific

Mojave (10.14)

Drawing Issues

According to AppKit release notes for 10.14 there are views are drawn Layer-backed.

Windows in apps linked against the macOS 10.14 SDK are displayed using Core Animation when the app is running in macOS 10.14. 
This doesn’t mean that all views are layer-backed; rather, it means that all views are either layer-backed or draw into 
a shared layer with other layers.
 
This change should be mostly invisible to most apps, but you might notice one or more subtle changes as a result.

Views that depend on drawing in the same backing store as their ancestors or lower-ordered siblings may find 
that they are instead drawing in separate layers. Views shouldn’t rely on being able to draw into the same 
backing store as their ancestors; instead, those ancestors should change as required. For example, to affect 
the background of a window, use the NSWindow properties opaque and backgroundColor

The change seems to impact CocoaWS a lot, causing problem with area invalidation. A number of Mojave-specific issues ([1][2]) were reported.

LCL solution - make sure the code does the right thing. or force all LCL driven controsl to return wantsUpdateLayer as true.

Dark Mode

Dark mode is supported "out of the box", but only if an application was built using SDK for 10.14. For example, if an application was built with an earlier SDK (i.e. was compiled on macOS 10.13 High Sierra or earlier), then it would not pick up Dark Mode by default.

When an app links on the macOS 10.14 SDK, it’s automatically opted in to supporting the dark appearance, 
with its NSApp inheriting the NSAppearanceNameDarkAqua appearance from System Preferences.
LCL solution: Cocoa WS needs to be updated, to check if NSApplication object supports "appearance" and if it does, then enable the currently selected system theme. That allows to support "Dark Mode" even for appliactions built on an earlier version of the system.
No LCL solution: simply add a switch into Info.plist NSRequiresAquaSystemAppearance and set the value to "false". (The key must be specified explicitly, if an app is build for an earlier SDK)
No dark mode: the key NSRequiresAquaSystemAppearance should be set to "true"

See Also

  • #33672 - the bug covers some problems linking the app