Sign in Join
Wiki copytemporary

Cocoa Internals: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11The page is about the 64 bit Cocoa Widgetset internal implementation. The page should be useful for Cocoa widgetset developers (maintainers and/or contributors) as well as any developers who need to use the Cocoa-specific API.
22
33## Minimum Version
44
55- Some parts of Cocoa are written using Objective C 2.0 features. Objective C 2.0 features used:
66
77
88
99[Enumeration in Objective-C classes](https://www.freepascal.org/docs-html/ref/refse76.html#x138-16000011.10)
1010
1111- Minimal macOS version is:
1212
1313:* 10.5 - spiritual
1414
1515:* 10.6 - de facto (a lot of 10.6 only APIs are used without any actual verification of the proper macOS version)
1616
1717There's a reason to keep the backwards compatibility for 10.6. This is the last system that supports Rosetta applications to allow running [PowerPC](https://en.wikipedia.org/wiki/PowerPC) applications on an Intel processor.
1818
1919### Compiler Support
2020
2121APIs that not (yet) provided by a corresponding version of FPC (delivered with Lazarus), should be declared at **cocoa_extra.pas** header
2222
2323It's desired that the legacy version (i.e. deprecated APIs) would be wrapped into the [[FPC New Features 2.6.2|minimum version defines]].
2424
2525If an application using LCL to be deployed to App Store, Apple would verify the used APIs and ban, if any older APIs are used.
2626
2727## Recompilation
2828
2929If you tried to modify Cocoa-Widget and the next attempt at the project or its Cocoa compilation throws an unexpected error like "cocoawscommon.pas(y:x) Error: identifier idents no member "lclFrame"", you need to delete all Cocoa compiled units.
3030
3131` ./lazarus/lcl/units/i386-darwin/cocoa`
3232
3333or
3434
3535` ./lazarus/lcl/units/x86_64-darwin/cocoa`
3636
3737(to be reported - compiler issue with ignoring extensions)
3838
3939### Reconfigure LCL package
4040
41-<figure>
42-<img src="LCL_configure_cocoa_to_removeunits.png" title="LCL_configure_cocoa_to_removeunits.png" />
43-<figcaption>LCL_configure_cocoa_to_removeunits.png</figcaption>
44-</figure>
41+![](/wiki-images/5/53/LCL_configure_cocoa_to_removeunits.png)
4542
4643Another way is to configure the LCL package to always remove .o files before recompilation of the package itself. (The same effect as removing it from command line):
4744
4845- Open LCL package ( Package -\> Open Loaded Package ... -\>LCL )
4946- Select (package) "Options"
5047- Select "Compiler Command"
5148- specify command for "Execute before"
5249
5350`sh -c "rm -f ../units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)/*"`
5451
5552Note that use of **sh** is necessary, because Lazarus IDE doesn't process * (asterisk)
5653
5754## LCL specific ObjC classes
5855
5956In order to control and handle an NSView's behavior LCL uses descendent classes from standard Cocoa controls. That is, for NSWindow, TCocoaWindow is introduced. The descedants are used for the purpose of "overriding" default class implementation, where it is needed. In some cases using delegate classes is not enough.
6057
6158There are two ways, in order to provide a common LCL(friendly) API interface for all Cocoa classes or sub-classes:
6259
6360- use **protocol** (similar to Interfaces in Object Pascal form). Any class, sub-classing from standard Cocoa classes, would have to implement the **protocol**. The downside of this approach is - a lot of code would be duplicated.
6461- use **categories** (similar to Javascript prototypes, not available in Object Pascal. Class-helpers looks alike, but are not the same). Categories allow to add methods (only methods! adding variables is not allowed) to any base class of Objective-C. Any descendants of this class automatically acquire the method, and it's default implementation. Also any subclass, can override the default implementation with its own. Apple doesn't recommend the use of categories (due to ambiguity and possible conflicts among different libraries introducing their own extensions). See more at the [[Cocoa Internals/Extensions|Cocoa Internals Extensions]] page
6562
6663## Code Style
6764
6865The following requirement applies for Cocoa widgetset. Other widgetset may be following some other rules (historically), but in general LCL follows the same requirements.
6966
7067- **Operators** Keep the operator separated by spaces between operands
7168
7269` A := B; `\
7370` A := B * C;`
7471
7572- **Blocks** The main rule is to make 2 character spacing from the code block start. Begin / Else should start on a new line.
7673
7774``` pascal
7875 procedure B;
7976 begin
8077 if A then // 2 character spacing from begin
8178 begin
8279 Start Here // 2 character spacing from begin
8380 Next Line
8481 end
8582 else
8683 begin
8784 Another Line
8885 end;
8986 end;
9087```
9188
9289- **Standard Function Name** please keep naming in ProperCase. Reserved words should be lower case. Name of (global/local) variables and fields should match declaration. Local variables should start with lower case.
9390
9491``` pascal
9592 if not Assigned(A) then
9693 begin
9794 Result := nil;
9895 end;
9996```
10097
10198## Using for-in instead of for
10299
103100When dealing with arrays, it's common for Pascal to write the code using "Integer" type array index like:
104101
105102```delphi
106103var
107104 i : integer;
108105 l : TList;
109106...
110107 for i:=0 to l.Count-1 do
111108 ...
112109```
113110
114111Cocoa provides it's own implementation of arrays using NSArray class. Writing the the loop in the same manner:
115112
116113```delphi
117114var
118115 i : integer;
119116 l : NSArray;
120117...
121118 for i:=0 to l.count-1 do
122119 ...
123120```
124121
125122is possible, though not desired.
126123
127124The reason for that, is that NSArray "count" is an NSUInteger type for Cocoa. This type has a different size on 32 and 64 platforms. (While "Integer" type is always 32 bit in FPC.)
128125
129126Thus while the code written above might work just fine on 32-platform, it might cause a range check error (if such check is enabled) on a 64 bit machine. For example, when count is zero. Here's a simplest (no ObjC) example of the problem:
130127
131128```delphi
132129{$R+}
133130function Count: qword;
134131begin
135132 Result := 0;
136133end;
137134var
138135 i: Integer;
139136begin
140137 for i := 0 to Count-1 do // <- throws a range check error, because QWord result doesn't fit for Integer index
141138 WriteLn(i);
142139end.
143140```
144141
145142However, running a loop from elements of an array is quite a common task. For example - changing attributes of all children controls, due to some action.
146143
147144Instead of using a higher size Index variable (i.e. PtrInt or PtrUInt), it's handy to use for-in loop. Most common Objective C classes provide a special feature (NSFastEnumerator protocol) to do enumeration through its member. It works for the 32 bit and well as the 64 bit platform, in the same manner, but requires the Objective C 2.0 runtime version (with objectivec2 modeswitch enabled).
148145
149146An iteration for-in loop might look like this:
150147
151148```delphi
152149var
153150 obj : NSObject;
154151 arr : NSArray;
155152...
156153 for obj in arr do
157154 ...
158155```
159156
160157## Writing a Cocoa app without the LCL
161158
162159This is useful for testing hard to debug bugs in the LCL. Here is an example program written in Pascal-Cocoa which creates a window with 2 buttons and a simple set of menus:
163160
164161```delphi
165162program cocoa_without_lcl;
166163
167164{$mode objfpc}{$H+}
168165{$modeswitch objectivec1}
169166
170167uses
171168 CocoaAll, classes, sysutils;
172169
173170type
174171 { TMyDelegate }
175172 TMyDelegate = objcclass(NSObject)
176173 public
177174 procedure HandleButtonClick_A(sender: id); message 'HandleButtonClick_A:';
178175 procedure HandleButtonClick_B(sender: id); message 'HandleButtonClick_B:';
179176 procedure HandleMenuClick_A(sender: id); message 'HandleMenuClick_A:';
180177 procedure HandleMenuClick_B(sender: id); message 'HandleMenuClick_B:';
181178 end;
182179
183180var
184181 appName: NSString;
185182 window: NSWindow;
186183 pool: NSAutoreleasePool;
187184 lText: NSTextField;
188185 lButton: NSButton;
189186 lDelegate: TMyDelegate;
190187 //
191188 MainMenu_A: NSMenu;
192189 TopItem_A: NSMenuItem;
193190 TopMenu_A: NSMenu;
194191 MenuItem_A: NSMenuItem;
195192 //
196193 MainMenu_B: NSMenu;
197194 TopItem_B: NSMenuItem;
198195 TopMenu_B: NSMenu;
199196 MenuItem_B: NSMenuItem;
200197
201198procedure TMyDelegate.HandleButtonClick_A(sender: id);
202199begin
203200 NSApp.setMainMenu(MainMenu_A);
204201end;
205202
206203procedure TMyDelegate.HandleButtonClick_B(sender: id);
207204begin
208205 NSApp.setMainMenu(MainMenu_B);
209206end;
210207
211208procedure TMyDelegate.HandleMenuClick_A(sender: id);
212209var
213210 Str: string;
214211begin
215212 Str := 'A '+inttostr(random(888));
216213 window.setTitle(NSSTR(@Str[1]));
217214end;
218215
219216procedure TMyDelegate.HandleMenuClick_B(sender: id);
220217var
221218 Str: string;
222219begin
223220 Str := 'B '+inttostr(random(888));
224221 window.setTitle(NSSTR(@Str[1]));
225222end;
226223
227224procedure CreateMenus;
228225var
229226 nsTitle: NSString;
230227 nsKey : NSString;
231228
232229 function CreateMenuItem(AName, AShortcut: string; ASelector: SEL): NSMenuItem;
233230 begin
234231 nsKey := NSSTR(PChar(AShortcut));
235232 nsTitle := NSSTR(PChar(AName));
236233 Result := NSMenuItem.alloc.initWithTitle_action_keyEquivalent(nsTitle, ASelector, nsKey);
237234 Result.setKeyEquivalentModifierMask(NSCommandKeyMask);
238235 nsTitle.release;
239236 nsKey.release;
240237 Result.setKeyEquivalentModifierMask(NSCommandKeyMask);
241238 Result.setTarget(lDelegate);
242239 end;
243240
244241begin
245242 MainMenu_A := NSMenu.alloc.initWithTitle(NSSTR('Menu A'));
246243 TopItem_A := CreateMenuItem('TopItem A', '', nil);
247244 MainMenu_A.insertItem_atIndex(TopItem_A, 0);
248245 TopMenu_A := NSMenu.alloc.initWithTitle(NSSTR('TopMenu A'));
249246 TopItem_A.setSubmenu(TopMenu_A);
250247 MenuItem_A := CreateMenuItem('MenuItem A', 'A', objcselector('HandleMenuClick_A:'));
251248 MenuItem_A.setTarget(lDelegate);
252249 TopMenu_A.insertItem_atIndex(MenuItem_A, 0);
253250
254251 MainMenu_B := NSMenu.alloc.initWithTitle(NSSTR('Menu B'));
255252 TopItem_B := CreateMenuItem('TopItem B', '', nil);
256253 MainMenu_B.insertItem_atIndex(TopItem_B, 0);
257254 TopMenu_B := NSMenu.alloc.initWithTitle(NSSTR('TopMenu B'));
258255 TopItem_B.setSubmenu(TopMenu_B);
259256 MenuItem_B := CreateMenuItem('MenuItem B', 'B', objcselector('HandleMenuClick_B:'));
260257 MenuItem_B.setTarget(lDelegate);
261258 TopMenu_B.insertItem_atIndex(MenuItem_B, 0);
262259end;
263260
264261begin
265262 // Autorelease pool, app and window creation
266263 pool := NSAutoreleasePool.new;
267264 NSApp := NSApplication.sharedApplication;
268265 NSApp.setActivationPolicy(NSApplicationActivationPolicyRegular);
269266 appName := NSProcessInfo.processInfo.processName;
270267 window := NSWindow.alloc.initWithContentRect_styleMask_backing_defer(NSMakeRect(0, 0, 200, 200),
271268 NSTitledWindowMask or NSClosableWindowMask or NSMiniaturizableWindowMask,
272269 NSBackingStoreBuffered, False).autorelease;
273270 lDelegate := TMyDelegate.alloc.init;
274271
275272 // text label
276273 lText := NSTextField.alloc.initWithFrame(NSMakeRect(50, 50, 120, 50)).autorelease;
277274 lText.setBezeled(False);
278275 lText.setDrawsBackground(False);
279276 lText.setEditable(False);
280277 lText.setSelectable(False);
281278 lText.setStringValue(NSSTR('NSTextField'));
282279 window.contentView.addSubview(lText);
283280
284281 // button
285282 lButton := NSButton.alloc.initWithFrame(NSMakeRect(50, 100, 120, 50)).autorelease;
286283 window.contentView.addSubview(lButton);
287284 lButton.setTitle(NSSTR('Button A!'));
288285 lButton.setButtonType(NSMomentaryLightButton);
289286 lButton.setBezelStyle(NSRoundedBezelStyle);
290287 // button event handler setting
291288 lButton.setTarget(lDelegate);
292289 lButton.setAction(ObjCSelector(lDelegate.HandleButtonClick_A));
293290
294291 // button B
295292 lButton := NSButton.alloc.initWithFrame(NSMakeRect(50, 150, 120, 50)).autorelease;
296293 window.contentView.addSubview(lButton);
297294 lButton.setTitle(NSSTR('Button B!'));
298295 lButton.setButtonType(NSMomentaryLightButton);
299296 lButton.setBezelStyle(NSRoundedBezelStyle);
300297 // button event handler setting
301298 lButton.setTarget(lDelegate);
302299 lButton.setAction(ObjCSelector(lDelegate.HandleButtonClick_B));
303300
304301 // Menus
305302 CreateMenus();
306303 NSApp.setMainMenu(MainMenu_A);
307304
308305 // Window showing and app running
309306 window.center;
310307 window.setTitle(appName);
311308 window.makeKeyAndOrderFront(nil);
312309 NSApp.activateIgnoringOtherApps(true);
313310 NSApp.run;
314311end.
315312```
316313
317314## Units
318315
319316The units should use the ".pas" extension, not ".pp" for consistency. Use of "include" is not welcome and is kept there for legacy reasons only (not to lose the history, if a file is merged).
320317
321-<table>
322-<thead>
323-<tr>
324-<th><p>Unit</p></th>
325-<th><p>Description</p></th>
326-</tr>
327-</thead>
328-<tbody>
329-<tr>
330-<td><p>CocoaPrivate</p></td>
331-<td><p>The unit contains most of the Cocoa classes overrides. The unit should stay LCL-classes free as much as possible (i.e. should not use Forms, StdCtrls, etc. Though using LCLTypes or LCLProc is ok).</p></td>
332-</tr>
333-<tr>
334-<td><p>CocoaUtils</p></td>
335-<td><p>The set of utility functions mostly to convert types (between LCL and Cocoa types)</p>
336-<p>Input constant conversion utilities are also placed here.</p></td>
337-</tr>
338-<tr>
339-<td><p>Cocoa_Extra</p></td>
340-<td><p>The purpose of the unit is to full-fill missing FPC headers declarations for macOS. (whatever is missing at CocoaAll).</p>
341-<p>Should not contain any additional functions, except for external and constant declarations.</p></td>
342-</tr>
343-<tr>
344-<td><p>CocoaButtons</p></td>
345-<td><p>Controls that are considered buttons (except for NSPopupButton) are declared in this unit.</p></td>
346-</tr>
347-<tr>
348-<td><p>CocoaCarent</p></td>
349-<td><p>The unit provides the code to implement caret for Cocoa. Cocoa itself doesn't have any "caret" specific API.</p></td>
350-</tr>
351-<tr>
352-<td><p>CocoaDatepicker</p></td>
353-<td><p>The unit contains the implementation of cocoa calendar control</p></td>
354-</tr>
355-<tr>
356-<td><p>CocoaGDIObjects</p></td>
357-<td><p>Highlever wrappers over Cocoa object to implement LCL GDI handles.</p></td>
358-</tr>
359-<tr>
360-<td><p>CocoaScrollers</p></td>
361-<td><p>All controls related to scrolling: TScrollBar and variants of ScrollView are implemented here</p></td>
362-</tr>
363-<tr>
364-<td><p>CocoaTabControls</p></td>
365-<td><p>Classes that are used to implement tabcontrol (and tabpage) are declared here</p></td>
366-</tr>
367-<tr>
368-<td><p>CocoaTables</p></td>
369-<td><p>The master table class is declared here. (used for TListView and TListBox).</p>
370-<p>Supplemental classes for NSCell and NSView based tables are also in this unit</p></td>
371-</tr>
372-<tr>
373-<td><p>CocoaTextEdits</p></td>
374-<td><p>Text editting controls are implemented here. Including Combobox and NSpopupBox (readonly combobox)</p></td>
375-</tr>
376-<tr>
377-<td><p>CocoaTheme</p></td>
378-<td><p>Theme drawing class.(todo: get rid of "customdrawing" parts)</p></td>
379-</tr>
380-<tr>
381-<td><p>CocoaWindows</p></td>
382-<td><p>Classes related to the window and its contents are implemented here</p></td>
383-</tr>
384-<tr>
385-<td><p>CocoaInt</p>
386-<p>cocoawinapih.inc / cocoawinapi.inc</p>
387-<p>cocoalclapih.inc /cocoalclapi.inc</p></td>
388-<td><p>The Widgetset class API (winApi-style) are implemented here.</p>
389-<p><strong>TCocoaApplication</strong> class is also declared and implemented in this unit. Both Widgetset object and code of TCocoaApplication class are mutually using each other.</p></td>
390-</tr>
391-<tr>
392-<td colspan="2"><p>Widgetset interfacing units</p></td>
393-</tr>
394-<tr>
395-<td><p>CocoaWSCommon</p></td>
396-<td><p>The unit contains common bindings between native Cocoa (obcjclasses implemented in CocoaPrivate) and LCL. The actual callbacks interfaces are implemented here. Most of the "messaging" to LCL is also implemented here</p></td>
397-</tr>
398-</tbody>
399-</table>
318+**Unit**
319+
320+**Description**
321+
322+CocoaPrivate
323+
324+The unit contains most of the Cocoa classes overrides. The unit should stay LCL-classes free as much as possible (i.e. should not use Forms, StdCtrls, etc. Though using LCLTypes or LCLProc is ok).
325+
326+CocoaUtils
327+
328+The set of utility functions mostly to convert types (between LCL and Cocoa types)
329+
330+Input constant conversion utilities are also placed here.
331+
332+Cocoa_Extra
333+
334+The purpose of the unit is to full-fill missing FPC headers declarations for macOS. (whatever is missing at CocoaAll).
335+
336+Should not contain any additional functions, except for external and constant declarations.
337+
338+CocoaButtons
339+
340+Controls that are considered buttons (except for NSPopupButton) are declared in this unit.
341+
342+CocoaCarent
343+
344+The unit provides the code to implement caret for Cocoa. Cocoa itself doesn't have any "caret" specific API.
345+
346+CocoaDatepicker
347+
348+The unit contains the implementation of cocoa calendar control
349+
350+CocoaGDIObjects
351+
352+Highlever wrappers over Cocoa object to implement LCL GDI handles.
353+
354+CocoaScrollers
355+
356+All controls related to scrolling: TScrollBar and variants of ScrollView are implemented here
357+
358+CocoaTabControls
359+
360+Classes that are used to implement tabcontrol (and tabpage) are declared here
361+
362+CocoaTables
363+
364+The master table class is declared here. (used for TListView and TListBox).
365+
366+Supplemental classes for NSCell and NSView based tables are also in this unit
367+
368+CocoaTextEdits
369+
370+Text editting controls are implemented here. Including Combobox and NSpopupBox (readonly combobox)
371+
372+CocoaTheme
373+
374+Theme drawing class.(todo: get rid of "customdrawing" parts)
375+
376+CocoaWindows
377+
378+Classes related to the window and its contents are implemented here
379+
380+CocoaInt
381+
382+cocoawinapih.inc / cocoawinapi.inc
383+
384+cocoalclapih.inc /cocoalclapi.inc
385+
386+The Widgetset class API (winApi-style) are implemented here.
387+
388+**TCocoaApplication** class is also declared and implemented in this unit. Both Widgetset object and code of TCocoaApplication class are mutually using each other.
389+
390+Widgetset interfacing units
391+
392+CocoaWSCommon
393+
394+The unit contains common bindings between native Cocoa (obcjclasses implemented in CocoaPrivate) and LCL. The actual callbacks interfaces are implemented here. Most of the "messaging" to LCL is also implemented here
400395
401396## Callback Objects
402397
403398- **ICommonCallback** is the primary object for Cocoa objects to report back actions taken. It prevents "the handlers" from dealing with Objective-C APIs over all.
404399- Some controls might introduce an additional interface to process more events. They all should inherit from ICommonCallback
405400- For each HANDLE created there should be exactly one CallBack object allocated, because
406401- Callback object is released on Destruction of the HANDLE (from LCL perspective). (Even though the actual ObjC object can be released later.)
407402
408403## Why so Boolean?
409404
410405Cocoa Widgetset introduces two ObjC-specific boolean types:
411406
412407- LCLObjCBoolean - declared at CocoaPrivate and is commonly used for any Cocoa overridden methods
413408- ObjCBool - declared at Cocoa_Extra and is used under BOOL8FIX.
414409
415410Both types were introduced to handle the issues with the Boolean type parameter. (Resulting Boolean value seems to be working as expected on x86_64).
416411
417412However, the intent of each type is different:
418413
419414- LCLObjCBoolean - is to compensate for FPC backwards incompatible change (targeted for 3.2.0 release) that's changing all "Boolean" type parameters to "Boolean8". Thus any overridden methods in LCL would stop compile. (Boolean8 is not an alias for Boolean.) Unfortunatelly the type is expected to stay likely forever.
420415- ObjCBool - is a need to resolve Boolean parameter problem with 3.0.4. For any call to boolean parameter method, additional methods were introduced to pass the parameter as byte parameter (**unsigned char**), and pass the actual value property. The type can be dropped as soon as LCL support for 3.0.4 is dropped. The type should not be used in any 3rd party components.
421416
422417## See also
423418
424419- [[Cocoa Interface]] - general information regarding the Cocoa Interface.
425420- [[Cocoa Internals/Application]] - details about use of NSApplication and clipboard.
426421- [[Cocoa Internals/Buttons]] - provides the information about internal implementation of Button controls.
427422- [[Cocoa Internals/Dialogs]] - implementation of common dialogs
428423- [[Cocoa Internals/Extensions]] - additional method introduced to ObjC classes, to prevent code duplication and if-conditions.
429424- [[Cocoa Internals/Forms]] - provides the information about internal implementation of Forms (Windows)
430425- [[Cocoa Internals/Graphics]] - graphics, fonts related topics.
431426- [[Cocoa Internals/Input]] - covers topics of Cocoa specific input processing implementation. Cocoa input processing is somewhat foreign to LCL (or Windows style) processing.
432427- [[Cocoa Internals/Memory Management]] - guidelines of memory management tactics, whenever writing/understanding Cocoa code. (provides a lot of general Cocoa information (also applicable for UIKit))
433428- [[Cocoa Internals/Menu]] - macOS menu bar
434429- [[Cocoa Internals/OS Versions]] - suggestions around macOS API deprecations.
435430- [[Cocoa Internals/Scroll]] - Scroll controls.
436431- [[Cocoa Internals/Text Controls]] - Cocoa text controls.
437432- [[Cocoa Internals/Theme]] - Cocoa Themes.
438433- [[Cocoa Internals/Widgetset]] - information about Handle implementation and focus change.
439434
440435---
441436*From the Free Pascal wiki, page "Cocoa Internals", as it stood on 2022-01-15 after a change by Skalogryz, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
442437Category: Cocoa
443438Category: Lazarus internals
444439Category: macOS

Back to history