Sign in Join
Wiki copytemporary

Cocoa Internals: what changed

Revision 2 to 3. 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
31-` ./lazarus/lcl/units/i386-darwin/cocoa`
31+```
32+ ./lazarus/lcl/units/i386-darwin/cocoa
33+```
3234
3335or
3436
35-` ./lazarus/lcl/units/x86_64-darwin/cocoa`
37+```
38+ ./lazarus/lcl/units/x86_64-darwin/cocoa
39+```
3640
3741(to be reported - compiler issue with ignoring extensions)
3842
3943### Reconfigure LCL package
4044
4145![](/wiki-images/5/53/LCL_configure_cocoa_to_removeunits.png)
4246
4347Another 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):
4448
4549- Open LCL package ( Package -\> Open Loaded Package ... -\>LCL )
4650- Select (package) "Options"
4751- Select "Compiler Command"
4852- specify command for "Execute before"
4953
50-`sh -c "rm -f ../units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)/*"`
54+```
55+sh -c "rm -f ../units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)/*"
56+```
5157
5258Note that use of **sh** is necessary, because Lazarus IDE doesn't process * (asterisk)
5359
5460## LCL specific ObjC classes
5561
5662In 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.
5763
5864There are two ways, in order to provide a common LCL(friendly) API interface for all Cocoa classes or sub-classes:
5965
6066- 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.
6167- 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
6268
6369## Code Style
6470
6571The following requirement applies for Cocoa widgetset. Other widgetset may be following some other rules (historically), but in general LCL follows the same requirements.
6672
6773- **Operators** Keep the operator separated by spaces between operands
6874
69-` A := B; `\
70-` A := B * C;`
75+``` pascal
76+ A := B;
77+ A := B * C;
78+```
7179
7280- **Blocks** The main rule is to make 2 character spacing from the code block start. Begin / Else should start on a new line.
7381
7482``` pascal
7583 procedure B;
7684 begin
7785 if A then // 2 character spacing from begin
7886 begin
7987 Start Here // 2 character spacing from begin
8088 Next Line
8189 end
8290 else
8391 begin
8492 Another Line
8593 end;
8694 end;
8795```
8896
8997- **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.
9098
9199``` pascal
92100 if not Assigned(A) then
93101 begin
94102 Result := nil;
95103 end;
96104```
97105
98106## Using for-in instead of for
99107
100108When dealing with arrays, it's common for Pascal to write the code using "Integer" type array index like:
101109
102110```delphi
103111var
104112 i : integer;
105113 l : TList;
106114...
107115 for i:=0 to l.Count-1 do
108116 ...
109117```
110118
111119Cocoa provides it's own implementation of arrays using NSArray class. Writing the the loop in the same manner:
112120
113121```delphi
114122var
115123 i : integer;
116124 l : NSArray;
117125...
118126 for i:=0 to l.count-1 do
119127 ...
120128```
121129
122130is possible, though not desired.
123131
124132The 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.)
125133
126134Thus 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:
127135
128136```delphi
129137{$R+}
130138function Count: qword;
131139begin
132140 Result := 0;
133141end;
134142var
135143 i: Integer;
136144begin
137145 for i := 0 to Count-1 do // <- throws a range check error, because QWord result doesn't fit for Integer index
138146 WriteLn(i);
139147end.
140148```
141149
142150However, 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.
143151
144152Instead 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).
145153
146154An iteration for-in loop might look like this:
147155
148156```delphi
149157var
150158 obj : NSObject;
151159 arr : NSArray;
152160...
153161 for obj in arr do
154162 ...
155163```
156164
157165## Writing a Cocoa app without the LCL
158166
159167This 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:
160168
161169```delphi
162170program cocoa_without_lcl;
163171
164172{$mode objfpc}{$H+}
165173{$modeswitch objectivec1}
166174
167175uses
168176 CocoaAll, classes, sysutils;
169177
170178type
171179 { TMyDelegate }
172180 TMyDelegate = objcclass(NSObject)
173181 public
174182 procedure HandleButtonClick_A(sender: id); message 'HandleButtonClick_A:';
175183 procedure HandleButtonClick_B(sender: id); message 'HandleButtonClick_B:';
176184 procedure HandleMenuClick_A(sender: id); message 'HandleMenuClick_A:';
177185 procedure HandleMenuClick_B(sender: id); message 'HandleMenuClick_B:';
178186 end;
179187
180188var
181189 appName: NSString;
182190 window: NSWindow;
183191 pool: NSAutoreleasePool;
184192 lText: NSTextField;
185193 lButton: NSButton;
186194 lDelegate: TMyDelegate;
187195 //
188196 MainMenu_A: NSMenu;
189197 TopItem_A: NSMenuItem;
190198 TopMenu_A: NSMenu;
191199 MenuItem_A: NSMenuItem;
192200 //
193201 MainMenu_B: NSMenu;
194202 TopItem_B: NSMenuItem;
195203 TopMenu_B: NSMenu;
196204 MenuItem_B: NSMenuItem;
197205
198206procedure TMyDelegate.HandleButtonClick_A(sender: id);
199207begin
200208 NSApp.setMainMenu(MainMenu_A);
201209end;
202210
203211procedure TMyDelegate.HandleButtonClick_B(sender: id);
204212begin
205213 NSApp.setMainMenu(MainMenu_B);
206214end;
207215
208216procedure TMyDelegate.HandleMenuClick_A(sender: id);
209217var
210218 Str: string;
211219begin
212220 Str := 'A '+inttostr(random(888));
213221 window.setTitle(NSSTR(@Str[1]));
214222end;
215223
216224procedure TMyDelegate.HandleMenuClick_B(sender: id);
217225var
218226 Str: string;
219227begin
220228 Str := 'B '+inttostr(random(888));
221229 window.setTitle(NSSTR(@Str[1]));
222230end;
223231
224232procedure CreateMenus;
225233var
226234 nsTitle: NSString;
227235 nsKey : NSString;
228236
229237 function CreateMenuItem(AName, AShortcut: string; ASelector: SEL): NSMenuItem;
230238 begin
231239 nsKey := NSSTR(PChar(AShortcut));
232240 nsTitle := NSSTR(PChar(AName));
233241 Result := NSMenuItem.alloc.initWithTitle_action_keyEquivalent(nsTitle, ASelector, nsKey);
234242 Result.setKeyEquivalentModifierMask(NSCommandKeyMask);
235243 nsTitle.release;
236244 nsKey.release;
237245 Result.setKeyEquivalentModifierMask(NSCommandKeyMask);
238246 Result.setTarget(lDelegate);
239247 end;
240248
241249begin
242250 MainMenu_A := NSMenu.alloc.initWithTitle(NSSTR('Menu A'));
243251 TopItem_A := CreateMenuItem('TopItem A', '', nil);
244252 MainMenu_A.insertItem_atIndex(TopItem_A, 0);
245253 TopMenu_A := NSMenu.alloc.initWithTitle(NSSTR('TopMenu A'));
246254 TopItem_A.setSubmenu(TopMenu_A);
247255 MenuItem_A := CreateMenuItem('MenuItem A', 'A', objcselector('HandleMenuClick_A:'));
248256 MenuItem_A.setTarget(lDelegate);
249257 TopMenu_A.insertItem_atIndex(MenuItem_A, 0);
250258
251259 MainMenu_B := NSMenu.alloc.initWithTitle(NSSTR('Menu B'));
252260 TopItem_B := CreateMenuItem('TopItem B', '', nil);
253261 MainMenu_B.insertItem_atIndex(TopItem_B, 0);
254262 TopMenu_B := NSMenu.alloc.initWithTitle(NSSTR('TopMenu B'));
255263 TopItem_B.setSubmenu(TopMenu_B);
256264 MenuItem_B := CreateMenuItem('MenuItem B', 'B', objcselector('HandleMenuClick_B:'));
257265 MenuItem_B.setTarget(lDelegate);
258266 TopMenu_B.insertItem_atIndex(MenuItem_B, 0);
259267end;
260268
261269begin
262270 // Autorelease pool, app and window creation
263271 pool := NSAutoreleasePool.new;
264272 NSApp := NSApplication.sharedApplication;
265273 NSApp.setActivationPolicy(NSApplicationActivationPolicyRegular);
266274 appName := NSProcessInfo.processInfo.processName;
267275 window := NSWindow.alloc.initWithContentRect_styleMask_backing_defer(NSMakeRect(0, 0, 200, 200),
268276 NSTitledWindowMask or NSClosableWindowMask or NSMiniaturizableWindowMask,
269277 NSBackingStoreBuffered, False).autorelease;
270278 lDelegate := TMyDelegate.alloc.init;
271279
272280 // text label
273281 lText := NSTextField.alloc.initWithFrame(NSMakeRect(50, 50, 120, 50)).autorelease;
274282 lText.setBezeled(False);
275283 lText.setDrawsBackground(False);
276284 lText.setEditable(False);
277285 lText.setSelectable(False);
278286 lText.setStringValue(NSSTR('NSTextField'));
279287 window.contentView.addSubview(lText);
280288
281289 // button
282290 lButton := NSButton.alloc.initWithFrame(NSMakeRect(50, 100, 120, 50)).autorelease;
283291 window.contentView.addSubview(lButton);
284292 lButton.setTitle(NSSTR('Button A!'));
285293 lButton.setButtonType(NSMomentaryLightButton);
286294 lButton.setBezelStyle(NSRoundedBezelStyle);
287295 // button event handler setting
288296 lButton.setTarget(lDelegate);
289297 lButton.setAction(ObjCSelector(lDelegate.HandleButtonClick_A));
290298
291299 // button B
292300 lButton := NSButton.alloc.initWithFrame(NSMakeRect(50, 150, 120, 50)).autorelease;
293301 window.contentView.addSubview(lButton);
294302 lButton.setTitle(NSSTR('Button B!'));
295303 lButton.setButtonType(NSMomentaryLightButton);
296304 lButton.setBezelStyle(NSRoundedBezelStyle);
297305 // button event handler setting
298306 lButton.setTarget(lDelegate);
299307 lButton.setAction(ObjCSelector(lDelegate.HandleButtonClick_B));
300308
301309 // Menus
302310 CreateMenus();
303311 NSApp.setMainMenu(MainMenu_A);
304312
305313 // Window showing and app running
306314 window.center;
307315 window.setTitle(appName);
308316 window.makeKeyAndOrderFront(nil);
309317 NSApp.activateIgnoringOtherApps(true);
310318 NSApp.run;
311319end.
312320```
313321
314322## Units
315323
316324The 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).
317325
318326**Unit**
319327
320328**Description**
321329
322330CocoaPrivate
323331
324332The 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).
325333
326334CocoaUtils
327335
328336The set of utility functions mostly to convert types (between LCL and Cocoa types)
329337
330338Input constant conversion utilities are also placed here.
331339
332340Cocoa_Extra
333341
334342The purpose of the unit is to full-fill missing FPC headers declarations for macOS. (whatever is missing at CocoaAll).
335343
336344Should not contain any additional functions, except for external and constant declarations.
337345
338346CocoaButtons
339347
340348Controls that are considered buttons (except for NSPopupButton) are declared in this unit.
341349
342350CocoaCarent
343351
344352The unit provides the code to implement caret for Cocoa. Cocoa itself doesn't have any "caret" specific API.
345353
346354CocoaDatepicker
347355
348356The unit contains the implementation of cocoa calendar control
349357
350358CocoaGDIObjects
351359
352360Highlever wrappers over Cocoa object to implement LCL GDI handles.
353361
354362CocoaScrollers
355363
356364All controls related to scrolling: TScrollBar and variants of ScrollView are implemented here
357365
358366CocoaTabControls
359367
360368Classes that are used to implement tabcontrol (and tabpage) are declared here
361369
362370CocoaTables
363371
364372The master table class is declared here. (used for TListView and TListBox).
365373
366374Supplemental classes for NSCell and NSView based tables are also in this unit
367375
368376CocoaTextEdits
369377
370378Text editting controls are implemented here. Including Combobox and NSpopupBox (readonly combobox)
371379
372380CocoaTheme
373381
374382Theme drawing class.(todo: get rid of "customdrawing" parts)
375383
376384CocoaWindows
377385
378386Classes related to the window and its contents are implemented here
379387
380388CocoaInt
381389
382390cocoawinapih.inc / cocoawinapi.inc
383391
384392cocoalclapih.inc /cocoalclapi.inc
385393
386394The Widgetset class API (winApi-style) are implemented here.
387395
388396**TCocoaApplication** class is also declared and implemented in this unit. Both Widgetset object and code of TCocoaApplication class are mutually using each other.
389397
390398Widgetset interfacing units
391399
392400CocoaWSCommon
393401
394402The 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
395403
396404## Callback Objects
397405
398406- **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.
399407- Some controls might introduce an additional interface to process more events. They all should inherit from ICommonCallback
400408- For each HANDLE created there should be exactly one CallBack object allocated, because
401409- Callback object is released on Destruction of the HANDLE (from LCL perspective). (Even though the actual ObjC object can be released later.)
402410
403411## Why so Boolean?
404412
405413Cocoa Widgetset introduces two ObjC-specific boolean types:
406414
407415- LCLObjCBoolean - declared at CocoaPrivate and is commonly used for any Cocoa overridden methods
408416- ObjCBool - declared at Cocoa_Extra and is used under BOOL8FIX.
409417
410418Both types were introduced to handle the issues with the Boolean type parameter. (Resulting Boolean value seems to be working as expected on x86_64).
411419
412420However, the intent of each type is different:
413421
414422- 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.
415423- 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.
416424
417425## See also
418426
419427- [[Cocoa Interface]] - general information regarding the Cocoa Interface.
420428- [[Cocoa Internals/Application]] - details about use of NSApplication and clipboard.
421429- [[Cocoa Internals/Buttons]] - provides the information about internal implementation of Button controls.
422430- [[Cocoa Internals/Dialogs]] - implementation of common dialogs
423431- [[Cocoa Internals/Extensions]] - additional method introduced to ObjC classes, to prevent code duplication and if-conditions.
424432- [[Cocoa Internals/Forms]] - provides the information about internal implementation of Forms (Windows)
425433- [[Cocoa Internals/Graphics]] - graphics, fonts related topics.
426434- [[Cocoa Internals/Input]] - covers topics of Cocoa specific input processing implementation. Cocoa input processing is somewhat foreign to LCL (or Windows style) processing.
427435- [[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))
428436- [[Cocoa Internals/Menu]] - macOS menu bar
429437- [[Cocoa Internals/OS Versions]] - suggestions around macOS API deprecations.
430438- [[Cocoa Internals/Scroll]] - Scroll controls.
431439- [[Cocoa Internals/Text Controls]] - Cocoa text controls.
432440- [[Cocoa Internals/Theme]] - Cocoa Themes.
433441- [[Cocoa Internals/Widgetset]] - information about Handle implementation and focus change.
434442
435443---
436444*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).*
437445Category: Cocoa
438446Category: Lazarus internals
439447Category: macOS

Back to history