Sign in Join
Wiki copytemporary

Cocoa Internals: what changed

Revision 3 to 4. 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-```
31+```text
3232 ./lazarus/lcl/units/i386-darwin/cocoa
3333```
3434
3535or
3636
37-```
37+```text
3838 ./lazarus/lcl/units/x86_64-darwin/cocoa
3939```
4040
4141(to be reported - compiler issue with ignoring extensions)
4242
4343### Reconfigure LCL package
4444
4545![](/wiki-images/5/53/LCL_configure_cocoa_to_removeunits.png)
4646
4747Another 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):
4848
4949- Open LCL package ( Package -\> Open Loaded Package ... -\>LCL )
5050- Select (package) "Options"
5151- Select "Compiler Command"
5252- specify command for "Execute before"
5353
54-```
54+```text
5555sh -c "rm -f ../units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)/*"
5656```
5757
5858Note that use of **sh** is necessary, because Lazarus IDE doesn't process * (asterisk)
5959
6060## LCL specific ObjC classes
6161
6262In 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.
6363
6464There are two ways, in order to provide a common LCL(friendly) API interface for all Cocoa classes or sub-classes:
6565
6666- 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.
6767- 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
6868
6969## Code Style
7070
7171The following requirement applies for Cocoa widgetset. Other widgetset may be following some other rules (historically), but in general LCL follows the same requirements.
7272
7373- **Operators** Keep the operator separated by spaces between operands
7474
7575``` pascal
7676 A := B;
7777 A := B * C;
7878```
7979
8080- **Blocks** The main rule is to make 2 character spacing from the code block start. Begin / Else should start on a new line.
8181
8282``` pascal
8383 procedure B;
8484 begin
8585 if A then // 2 character spacing from begin
8686 begin
8787 Start Here // 2 character spacing from begin
8888 Next Line
8989 end
9090 else
9191 begin
9292 Another Line
9393 end;
9494 end;
9595```
9696
9797- **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.
9898
9999``` pascal
100100 if not Assigned(A) then
101101 begin
102102 Result := nil;
103103 end;
104104```
105105
106106## Using for-in instead of for
107107
108108When dealing with arrays, it's common for Pascal to write the code using "Integer" type array index like:
109109
110110```delphi
111111var
112112 i : integer;
113113 l : TList;
114114...
115115 for i:=0 to l.Count-1 do
116116 ...
117117```
118118
119119Cocoa provides it's own implementation of arrays using NSArray class. Writing the the loop in the same manner:
120120
121121```delphi
122122var
123123 i : integer;
124124 l : NSArray;
125125...
126126 for i:=0 to l.count-1 do
127127 ...
128128```
129129
130130is possible, though not desired.
131131
132132The 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.)
133133
134134Thus 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:
135135
136136```delphi
137137{$R+}
138138function Count: qword;
139139begin
140140 Result := 0;
141141end;
142142var
143143 i: Integer;
144144begin
145145 for i := 0 to Count-1 do // <- throws a range check error, because QWord result doesn't fit for Integer index
146146 WriteLn(i);
147147end.
148148```
149149
150150However, 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.
151151
152152Instead 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).
153153
154154An iteration for-in loop might look like this:
155155
156156```delphi
157157var
158158 obj : NSObject;
159159 arr : NSArray;
160160...
161161 for obj in arr do
162162 ...
163163```
164164
165165## Writing a Cocoa app without the LCL
166166
167167This 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:
168168
169169```delphi
170170program cocoa_without_lcl;
171171
172172{$mode objfpc}{$H+}
173173{$modeswitch objectivec1}
174174
175175uses
176176 CocoaAll, classes, sysutils;
177177
178178type
179179 { TMyDelegate }
180180 TMyDelegate = objcclass(NSObject)
181181 public
182182 procedure HandleButtonClick_A(sender: id); message 'HandleButtonClick_A:';
183183 procedure HandleButtonClick_B(sender: id); message 'HandleButtonClick_B:';
184184 procedure HandleMenuClick_A(sender: id); message 'HandleMenuClick_A:';
185185 procedure HandleMenuClick_B(sender: id); message 'HandleMenuClick_B:';
186186 end;
187187
188188var
189189 appName: NSString;
190190 window: NSWindow;
191191 pool: NSAutoreleasePool;
192192 lText: NSTextField;
193193 lButton: NSButton;
194194 lDelegate: TMyDelegate;
195195 //
196196 MainMenu_A: NSMenu;
197197 TopItem_A: NSMenuItem;
198198 TopMenu_A: NSMenu;
199199 MenuItem_A: NSMenuItem;
200200 //
201201 MainMenu_B: NSMenu;
202202 TopItem_B: NSMenuItem;
203203 TopMenu_B: NSMenu;
204204 MenuItem_B: NSMenuItem;
205205
206206procedure TMyDelegate.HandleButtonClick_A(sender: id);
207207begin
208208 NSApp.setMainMenu(MainMenu_A);
209209end;
210210
211211procedure TMyDelegate.HandleButtonClick_B(sender: id);
212212begin
213213 NSApp.setMainMenu(MainMenu_B);
214214end;
215215
216216procedure TMyDelegate.HandleMenuClick_A(sender: id);
217217var
218218 Str: string;
219219begin
220220 Str := 'A '+inttostr(random(888));
221221 window.setTitle(NSSTR(@Str[1]));
222222end;
223223
224224procedure TMyDelegate.HandleMenuClick_B(sender: id);
225225var
226226 Str: string;
227227begin
228228 Str := 'B '+inttostr(random(888));
229229 window.setTitle(NSSTR(@Str[1]));
230230end;
231231
232232procedure CreateMenus;
233233var
234234 nsTitle: NSString;
235235 nsKey : NSString;
236236
237237 function CreateMenuItem(AName, AShortcut: string; ASelector: SEL): NSMenuItem;
238238 begin
239239 nsKey := NSSTR(PChar(AShortcut));
240240 nsTitle := NSSTR(PChar(AName));
241241 Result := NSMenuItem.alloc.initWithTitle_action_keyEquivalent(nsTitle, ASelector, nsKey);
242242 Result.setKeyEquivalentModifierMask(NSCommandKeyMask);
243243 nsTitle.release;
244244 nsKey.release;
245245 Result.setKeyEquivalentModifierMask(NSCommandKeyMask);
246246 Result.setTarget(lDelegate);
247247 end;
248248
249249begin
250250 MainMenu_A := NSMenu.alloc.initWithTitle(NSSTR('Menu A'));
251251 TopItem_A := CreateMenuItem('TopItem A', '', nil);
252252 MainMenu_A.insertItem_atIndex(TopItem_A, 0);
253253 TopMenu_A := NSMenu.alloc.initWithTitle(NSSTR('TopMenu A'));
254254 TopItem_A.setSubmenu(TopMenu_A);
255255 MenuItem_A := CreateMenuItem('MenuItem A', 'A', objcselector('HandleMenuClick_A:'));
256256 MenuItem_A.setTarget(lDelegate);
257257 TopMenu_A.insertItem_atIndex(MenuItem_A, 0);
258258
259259 MainMenu_B := NSMenu.alloc.initWithTitle(NSSTR('Menu B'));
260260 TopItem_B := CreateMenuItem('TopItem B', '', nil);
261261 MainMenu_B.insertItem_atIndex(TopItem_B, 0);
262262 TopMenu_B := NSMenu.alloc.initWithTitle(NSSTR('TopMenu B'));
263263 TopItem_B.setSubmenu(TopMenu_B);
264264 MenuItem_B := CreateMenuItem('MenuItem B', 'B', objcselector('HandleMenuClick_B:'));
265265 MenuItem_B.setTarget(lDelegate);
266266 TopMenu_B.insertItem_atIndex(MenuItem_B, 0);
267267end;
268268
269269begin
270270 // Autorelease pool, app and window creation
271271 pool := NSAutoreleasePool.new;
272272 NSApp := NSApplication.sharedApplication;
273273 NSApp.setActivationPolicy(NSApplicationActivationPolicyRegular);
274274 appName := NSProcessInfo.processInfo.processName;
275275 window := NSWindow.alloc.initWithContentRect_styleMask_backing_defer(NSMakeRect(0, 0, 200, 200),
276276 NSTitledWindowMask or NSClosableWindowMask or NSMiniaturizableWindowMask,
277277 NSBackingStoreBuffered, False).autorelease;
278278 lDelegate := TMyDelegate.alloc.init;
279279
280280 // text label
281281 lText := NSTextField.alloc.initWithFrame(NSMakeRect(50, 50, 120, 50)).autorelease;
282282 lText.setBezeled(False);
283283 lText.setDrawsBackground(False);
284284 lText.setEditable(False);
285285 lText.setSelectable(False);
286286 lText.setStringValue(NSSTR('NSTextField'));
287287 window.contentView.addSubview(lText);
288288
289289 // button
290290 lButton := NSButton.alloc.initWithFrame(NSMakeRect(50, 100, 120, 50)).autorelease;
291291 window.contentView.addSubview(lButton);
292292 lButton.setTitle(NSSTR('Button A!'));
293293 lButton.setButtonType(NSMomentaryLightButton);
294294 lButton.setBezelStyle(NSRoundedBezelStyle);
295295 // button event handler setting
296296 lButton.setTarget(lDelegate);
297297 lButton.setAction(ObjCSelector(lDelegate.HandleButtonClick_A));
298298
299299 // button B
300300 lButton := NSButton.alloc.initWithFrame(NSMakeRect(50, 150, 120, 50)).autorelease;
301301 window.contentView.addSubview(lButton);
302302 lButton.setTitle(NSSTR('Button B!'));
303303 lButton.setButtonType(NSMomentaryLightButton);
304304 lButton.setBezelStyle(NSRoundedBezelStyle);
305305 // button event handler setting
306306 lButton.setTarget(lDelegate);
307307 lButton.setAction(ObjCSelector(lDelegate.HandleButtonClick_B));
308308
309309 // Menus
310310 CreateMenus();
311311 NSApp.setMainMenu(MainMenu_A);
312312
313313 // Window showing and app running
314314 window.center;
315315 window.setTitle(appName);
316316 window.makeKeyAndOrderFront(nil);
317317 NSApp.activateIgnoringOtherApps(true);
318318 NSApp.run;
319319end.
320320```
321321
322322## Units
323323
324324The 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).
325325
326326**Unit**
327327
328328**Description**
329329
330330CocoaPrivate
331331
332332The 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).
333333
334334CocoaUtils
335335
336336The set of utility functions mostly to convert types (between LCL and Cocoa types)
337337
338338Input constant conversion utilities are also placed here.
339339
340340Cocoa_Extra
341341
342342The purpose of the unit is to full-fill missing FPC headers declarations for macOS. (whatever is missing at CocoaAll).
343343
344344Should not contain any additional functions, except for external and constant declarations.
345345
346346CocoaButtons
347347
348348Controls that are considered buttons (except for NSPopupButton) are declared in this unit.
349349
350350CocoaCarent
351351
352352The unit provides the code to implement caret for Cocoa. Cocoa itself doesn't have any "caret" specific API.
353353
354354CocoaDatepicker
355355
356356The unit contains the implementation of cocoa calendar control
357357
358358CocoaGDIObjects
359359
360360Highlever wrappers over Cocoa object to implement LCL GDI handles.
361361
362362CocoaScrollers
363363
364364All controls related to scrolling: TScrollBar and variants of ScrollView are implemented here
365365
366366CocoaTabControls
367367
368368Classes that are used to implement tabcontrol (and tabpage) are declared here
369369
370370CocoaTables
371371
372372The master table class is declared here. (used for TListView and TListBox).
373373
374374Supplemental classes for NSCell and NSView based tables are also in this unit
375375
376376CocoaTextEdits
377377
378378Text editting controls are implemented here. Including Combobox and NSpopupBox (readonly combobox)
379379
380380CocoaTheme
381381
382382Theme drawing class.(todo: get rid of "customdrawing" parts)
383383
384384CocoaWindows
385385
386386Classes related to the window and its contents are implemented here
387387
388388CocoaInt
389389
390390cocoawinapih.inc / cocoawinapi.inc
391391
392392cocoalclapih.inc /cocoalclapi.inc
393393
394394The Widgetset class API (winApi-style) are implemented here.
395395
396396**TCocoaApplication** class is also declared and implemented in this unit. Both Widgetset object and code of TCocoaApplication class are mutually using each other.
397397
398398Widgetset interfacing units
399399
400400CocoaWSCommon
401401
402402The 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
403403
404404## Callback Objects
405405
406406- **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.
407407- Some controls might introduce an additional interface to process more events. They all should inherit from ICommonCallback
408408- For each HANDLE created there should be exactly one CallBack object allocated, because
409409- Callback object is released on Destruction of the HANDLE (from LCL perspective). (Even though the actual ObjC object can be released later.)
410410
411411## Why so Boolean?
412412
413413Cocoa Widgetset introduces two ObjC-specific boolean types:
414414
415415- LCLObjCBoolean - declared at CocoaPrivate and is commonly used for any Cocoa overridden methods
416416- ObjCBool - declared at Cocoa_Extra and is used under BOOL8FIX.
417417
418418Both types were introduced to handle the issues with the Boolean type parameter. (Resulting Boolean value seems to be working as expected on x86_64).
419419
420420However, the intent of each type is different:
421421
422422- 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.
423423- 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.
424424
425425## See also
426426
427427- [[Cocoa Interface]] - general information regarding the Cocoa Interface.
428428- [[Cocoa Internals/Application]] - details about use of NSApplication and clipboard.
429429- [[Cocoa Internals/Buttons]] - provides the information about internal implementation of Button controls.
430430- [[Cocoa Internals/Dialogs]] - implementation of common dialogs
431431- [[Cocoa Internals/Extensions]] - additional method introduced to ObjC classes, to prevent code duplication and if-conditions.
432432- [[Cocoa Internals/Forms]] - provides the information about internal implementation of Forms (Windows)
433433- [[Cocoa Internals/Graphics]] - graphics, fonts related topics.
434434- [[Cocoa Internals/Input]] - covers topics of Cocoa specific input processing implementation. Cocoa input processing is somewhat foreign to LCL (or Windows style) processing.
435435- [[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))
436436- [[Cocoa Internals/Menu]] - macOS menu bar
437437- [[Cocoa Internals/OS Versions]] - suggestions around macOS API deprecations.
438438- [[Cocoa Internals/Scroll]] - Scroll controls.
439439- [[Cocoa Internals/Text Controls]] - Cocoa text controls.
440440- [[Cocoa Internals/Theme]] - Cocoa Themes.
441441- [[Cocoa Internals/Widgetset]] - information about Handle implementation and focus change.
442442
443443---
444444*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).*
445445Category: Cocoa
446446Category: Lazarus internals
447447Category: macOS

Back to history