The scenario is, in a macOS app (primarly), main thread needs to wait for some time for a certain 'event'. When that event occurs, the main thread is signaled, it gets unblocked and moves on.
An example is, during shutdown, a special thread known as shutdown thread waits for all other worker threads to return (thread join operation). When all threads have returned, the shutdown thread signals the main thread, which was waiting on a timer, to continue with the shutdown flow. If shutdown thread signals the main thread before the later's timer expires, it means all threads have returned. If main thread's timer expires first, it means some threads have failed to join (probably stuck in infinite loop due to bug, disk I/O etc.).
This post is to understand how main thread can wait for some time for the shutdown thread. There are two ways: a) dispatch_semaphore_t b) pthread conditional variable (pthread_cond_t) and mutex (pthread_mutex_t).
Expanding a bit on option (b) using conditional variable and mutex:
// This method is invoked on the main thread
bool ConditionSignal::TimedWait() noexcept
{
struct timespec ts;
pthread_mutex_t * mutex = (pthread_mutex_t *) (&vPosix.vMutexStorage[0]);
pthread_cond_t * cond = (pthread_cond_t *) (&vPosix.vCondVarStorage[0]);
// Set the timer to 3 sec.
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 3;
pthread_mutex_lock(mutex);
LOG("Main thread has acquired the mutex and is waiting!");
int wait_result = pthread_cond_timedwait(cond, mutex, &ts);
switch (wait_result) {
case 0:
LOG("Main thread signaled!");
return true;
case ETIMEDOUT:
LOG("Main thread's timer expired!");
return false;
default:
LOG("Error: Unexpected return value from pthread_cond_timedwait: " + std::to_string(wait_result));
break;
}
return false;
}
// This method is invoked on shutdown thread after all worker threads have returned.
void ConditionSignal::Raise() noexcept
{
pthread_mutex_t * mutex = (pthread_mutex_t *) (&vPosix.vMutexStorage);
pthread_cond_t * cond = (pthread_cond_t *) (&vPosix.vCondVarStorage);
pthread_mutex_lock(mutex);
LOG("[Shutdown thread]: Signalling main thread...");
pthread_cond_signal(cond);
pthread_mutex_unlock(mutex);
}
Both options allow the main thread to wait for some time (for shutdown thread) and continue execution. However, when using dispatch_semaphore_t, I get the following warning:
Thread Performance Checker: Thread running at User-interactive quality-of-service class waiting on a lower QoS thread running at Default quality-of-service class. Investigate ways to avoid priority inversions
Whereas, with conditional variables, there are no warnings. I understand the warning - holding the main thread can prevent it from responding to user events, thus causing the app to freeze. And in iOS, the process is killed if main thread takes more than 5 secs to return from applicationWillTerminate(_:) delegate method. But in this scenario, the main thread is on a timed-wait, for some milliseconds i.e., it is guaranteed to not get blocked indefinitely.
While this is described for macOS, this functionality is required for all Apple OSes. What is the recommend way?
AppKit
RSS for tagConstruct and manage a graphical, event-driven user interface for your macOS app using AppKit.
Posts under AppKit tag
200 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I am not very well versed in this area, so I would appreciate some guidance on what should be enabled or disabled. My app is an AppKit app. I have read the documentation and watched the video, but I find it hard to understand.
When I added the Enhanced Security capability in Xcode, the following options were enabled automatically:
Memory Safety
Enable Enhanced Security Typed Allocator
Runtime Protections
Enable Additional Runtime Platform Restrictions
Authenticate Pointers
Enable Read-only Platform Memory
The following options were disabled by default:
Memory Safety
Enable Hardware Memory Tagging
Memory Tag Pure Data
Prevent Receiving Tagged Memory
Enable Soft Mode for Memory Tagging
Should I enable these options? Is there anything I should consider disabling?
When building in Xcode on MacOS Tahoe, it seems it is no longer possible to dynamically specify a "small" size toolbar for NSToolbar/NSToolbarItem. It works in MacOS code compiled and linked on earlier systems.
I don't want to use "SFSymbol", or "templates". I have over 60 custom-made .png toolbars, in individual Image Set files, at the previous requisite sizes of 24x24 / 48x48, and 32x32 / 64x64.
Sure -- I can configure an NSToolbar with whatever size .png assets I want. I just can't dynamically switch between the two groups of sizes.
According to the Apple Coding Assistant, the only solution is to change the image names of each of the NSToolbarItems at runtime.
OK -- but even when attempting that, the NSToolbarItems refuse to take on their smaller size...
...unless: they are attached to a custom view NSToolbarItem with an NSButton of style "Bevel". I have about 10 of those, and YES -- I CAN change those to a "small" size. Is this REALLY what I'm forced to do?!
The Apple Coding Assistant just runs me around in circles, making coding suggestions that include properties that don't exists.
I've gone around and around on these issues for over a week -- it can't be this hard, right? Is there no way to make multiple NSToolbar objects, one for "large" and one for "small"?
NSWindow objects with custom styleMask configurations seem to behave erratically in macOS Tahoe 26.3 RC.
For example an NSWindow is not resizable after issuing .styleMask.remove(.titled) or some NSWindow-s become totally unresponsive (the NSWindow becomes transparent to mouse events) with custom styleMask-s.
This is a radical change compared to how all previous macOS versions or the 26.3 beta3 worked and seriously affects apps that might use custom NSWindows - this includes some system utilities, OSD/HUD apps etc, actually breaking some apps.
Such fundamental compatibility altering changes should not be introduced in an RC stage (if this is intentional and not a bug) imho.
macOS 26 "Tahoe" is allocating much more memory for apps than former macOS versions: A customer contacted me with my app's Thumbnail extension allocating so much memory that her 48 GB RAM Mac Mini ran into "out of application memory" state. I couldn't identify any memory leak in my extension's code nor reproduce the issue, but found the main app allocating as much as 5 times the memory compared to running on macOS 15 or lower.
This productive app is explicitly using "Liquid Glass" views as well as implicitly e.g. for an inspector pane. So I created a sample app, just based on Xcode's template of a document-based app, and the issue is still showing (although less dramatically): This sample app allocates 22 MB according to Tahoe's Activity Monitor, while Sequoia only requires 16 MB:
macOS 15.6.1
macOS 26.2
Is anyone experiencing similar issues? I suspect some massive leak in Tahoe's memory management, and just filed a corresponding feedback (FB21967167).
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Foundation
QuickLook Thumbnailing
AppKit
Are there workarounds for the following bugs in Xcode 16.3?
I'm looking for workarounds instead of trying newer versions of Xcode because, from what I read, the Xcode release notes do not mention these issues being fixed.
First bug:
Resizing a NSSplitView in the UI Editor is buggy. When you try to use the divider, it just does not work as expected. In the case of a vertical split, the bottom view gets shrunk whatever you try to do.
Second bug:
When you type:
(IBAction) in the source editor, you get the following buggy template (extra ')' on the right):
- (IBAction)<#selector#>:(id)sender)
This is a very basic macOS Finder-style test app using AppKit. I am experiencing a "jump" in the vertical scroll position of my NSTableView (inside an NSScrollView) specifically when the window is resized horizontally. This happens when columns are visually added or removed.
Framework: AppKit (Cocoa)
Xcode/macOS: 26.2
Code: https://github.com/MorusPatre/Binder/blob/main/ContentView%20-%20Scroll%20Jump%20during%20Resize.swift
Context:
I am building a macOS file (currently image only) browser using SwiftUI. The core view is a ScrollView containing a LazyVGrid. The layout is dynamic: as the window resizes, I calculate the optimal number of columns and spacing using a GeometryReader.
The Issue:
While scrolling is pretty smooth (thanks to custom image caching and prefetching), window resizing is significantly laggy. After having scrolled the UI stutters and drops frames heavily while dragging the window edge.
The Code:
https://github.com/MorusPatre/Binder
I started a project targeting macOS and used Storyboard app lifecycle. I also used Xcode Previews in this project.
If I:
Keep the entry point of the main storyboard to a window controller. (as default setup in macOS),
Turn the Xcode window into full-screen,
That window controller would pop up its window every time Xcode Previews refreshes.
I tested in Xcode 26.3 RC and many versions before.
I'd like to create a Quick Look extension for a file type for which a location or region on a Map should be shown as preview.
However the MapView would only show a grid without any map. From within the MapKit delegate I can see from the "Error" parameter (a server with this domain can not be found) that this seems to be a network issue. The Quick Look extension seems to have no access to the internet and therefore the MapView can not load any map data.
I've then also done some other tests via URLSession, which also only fails with connection errors.
I haven't seen any limitations or restrictions mentioned in the API documentation.
Is this the expected behavior? Is this a bug? Or am I missing something?
I have Mac apps that embed “Helper Apps” inside their main bundle. The helper apps do work on behalf of the main application.
The helper app doesn’t show a dock icon, it does show minimal UI like an open panel in certain situations (part of NSService implementation). And it does make use of the NSApplication lifecycle and auto quits after it completes all work.
Currently the helper app is inside the main app bundle at: /Contents/Applications/HelperApp.app
Prior to Tahoe these were never displayed to user in LaunchPad but now the Spotlight based AppLauncher displays them.
What’s the recommended way to get these out of the Spotlight App list on macOS Tahoe?
Thanks in advance.
Hello,
I have a .app NSApplication which is ran as a LaunchDaemon, in it's lifecycle I never call any AppKit functions (I start it with CFRunLoopRun). (mentioned on this post as well).
I intercept a couple of signals using signal(s) in order to trigger CFRunLoopStop(CFRunLoopGetCurrent()) to do some cleanup.
This LaunchDaemon has the purpose of providing VPN connectivity, as such I call connect functions that trigger the "Add VPN configuration" dialog (I can't provide extra details about this, as I integrate another SDK so I'm not sure what happens under the hood) and I noticed that whenever it is displayed, after allowing it, during the lifecycle of the application when it's time to send the signal, the signal isn't received.
I tried re-adding the NSApp callbacks in order to investigate, but it looks like those aren't called as well.
I'm interested in knowing more about this scenario and what happens... I couldn't really find information about this dialog...
Thanks!
That's a question for Mac app (Cocoa).
I want to change the standard highlighting.
I thought to use tableView.selectionHighlightStyle.
But there are only 2 values: .none and .regular. Cannot find how to define a custom one.
So I tried a workaround:
set tableView.selectionHighlightStyle to .none
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
tableView.selectionHighlightStyle = .none
keep track of previousSelection
Then, in tableViewSelectionDidChange
reset for previousSelection
func tableViewSelectionDidChange(_ notification: Notification) { }
if previousSelection >= 0 {
let cellView = theTableView.rowView(atRow: previousSelection, makeIfNecessary: false)
cellView?.layer?.backgroundColor = .clear
}
set for the selection to a custom color
let cellView = theTableView.rowView(atRow: row, makeIfNecessary: false)
cellView?.layer?.backgroundColor = CGColor(red: 0, green: 0, blue: 1, alpha: 0.4)
previousSelection = row
Result is disappointing :
Even though tableView.selectionHighlightStyle is set to .none, it does overlays the cellView?.layer
Is there a way to directly change the color for selection ?
I’m running macOS 15.7 with Xcode 26.2, and I’m trying to learn the basics of AppKit.
I’m fully aware that AppKit is considered a legacy / “old” technology and that Apple clearly does not promote it out of the box anymore. This is especially visible in recent versions of Xcode, where you can no longer create a macOS App template without Storyboards or SwiftUI.
That said, AppKit is still widely used under the hood, so I think it’s reasonable to at least understand its fundamentals.
Here’s the problem I’m facing.
I create a standard macOS App project using the Xcode template (AppKit App Delegate). Then I:
delete Main.storyboard
remove all storyboard references from Info.plist
try to create the window manually in applicationDidFinishLaunching
At this point, the project builds, but the app either:
does not show any window, or
behaves as if it were not a proper GUI app
While debugging, it looks like Xcode / Swift is treating my target more like a dylib / wrapper than a normal .app bundle (at least judging by runtime behavior and the lack of a proper AppKit lifecycle).
While searching for answers, I found this article:
https://ashidiqi.com/blog/how-to-setup-xcode-project-programmatically-with-appkit/
The author claims that in this scenario you must not use the @main attribute and instead need a main.swift file that manually calls NSApplicationMain.
However, the article also mentions that Xcode shows the error:
'main' attribute cannot be used in a module that contains top-level code
The confusing part is that I do not have any top-level code in my project when using @main.
At the same time, when I ask an LLM for clarification, it tells me that:
@main does not require a storyboard
removing a storyboard does not force you to switch to main.swift
@main should work fine for a fully programmatic AppKit app
So I’m left with two concrete questions:
What is the correct, modern answer here? When building a fully programmatic AppKit app (no storyboard, no SwiftUI), do you actually need main.swift, or is @main still fully supported and correct?
Is there any supported or recommended way in modern Xcode to get a “clean” AppKit app template without a storyboard? Either via project settings, templates, or some documented workflow — without having to fight the build system or end up with a half-broken target.
I’m not trying to fight Apple’s direction here — I just want to understand the correct way to work with AppKit in 2025, even if it’s not the preferred path anymore.
Thanks in advance for any clarification.
The documentation says:
The caching behavior of the NSURL and CFURL APIs differ. For NSURL, all cached values (not temporary values) are automatically removed after each pass through the run loop. You only need to call the removeCachedResourceValueForKey: method when you want to clear the cache within a single execution of the run loop. The CFURL functions, on the other hand, do not automatically clear cached resource values. The client has complete control over the cache lifetimes, and you must use CFURLClearResourcePropertyCacheForKey or CFURLClearResourcePropertyCache to clear cached resource values.
https://developer.apple.com/documentation/foundation/nsurl/removeallcachedresourcevalues()?language=objc
Is this really true? In my experience I've had to explicitly remove cached resource values via -removeAllCachedResourceValues or removeCachedResourceValueForKey: otherwise the URL contains stale values.
For example on a URL that no longer exists I attempted to read NSURLIsHiddenKey and the last value was already cached. Instead of getting a NSFileNoSuchFileError I get the old cache value unless explicitly call -removeCachedResourceValueForKey: first and I'm fairly certain the value was cached on a previous run loop churn.
I have a MacOS app which displays PDFs, and I want to create a New document from the Clipboard, if the clipboard contains valid graphical data.
My problem is that even if it doesn't, I still get a blank new document window. AppKit always creates a new document.
I've tried overriding the newDocument function; I've tried avoiding the built-in functions altogether.
Are there any general recommendations for going about this?
My app has a menu called "Document". On Tahoe, this appears as a lightbulb icon.
Is this deliberate? Is it possible to stop this? Or do I just have to use a different name if I want people to read words?
When I appendAttributedString to [textView textStorage] it does not appear on the scrollable TextView.
However when I NSLog the [textView textStorage] the Attributed string is outputted, and is therefore stored in the textView, see below.
Occurs every time I ask to see the AttributedString I send to the textView.
[textView textStorage] attributedString
I need to see the attributedString displayed on the ScrollableTextView, but I don't know why I cannot see it.
Running print operation on WKWebView I hit EXC_BREAKPOINT and there is all kinds of console spew that looks concerning:
ERROR: The NSPrintOperation view's frame was not initialized properly before knowsPageRange: returned. (WKPrintingView)
** CGContextClipToRect: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.**
WebContent[7743] networkd_settings_read_from_file Sandbox is preventing this process from reading networkd settings file at "/Library/Preferences/com.apple.networkd.plist", please add an exception.
CRASHSTRING: XPC_ERROR_CONNECTION_INVALID from launchservicesd
CRASHSTRING: rdar://problem/28724618 Process unable to create connection because the sandbox denied the right to lookup com.apple.coreservices.launchservicesd and so this process cannot talk to launchservicesd.
WebContent[7921] The sandbox in this process does not allow access to RunningBoard.
Safe to ignore all this?
Is there any way I can know that another app has gone full screen? Please note that this is not what NSWindowDidEnterFullScreenNotification does, that only works for my own windows.
As for why I need to know: Say you're playing a YouTube video full screen. The video fills up the main display, and if there's a second display, it goes black. Well, mostly. I have a utility app with small status windows that remain on top. I'd like to be polite and hide them in this scenario.