Archive for the 'Cocoa' Category

I have a question

Saturday, December 8th, 2007

See also the follow-up: And an answer: What to do if your table view shows the whole array in every row.

Normally, on this blag, I give answers.

This time, I have a question.

Here’s my problem:

I have a window containing a table view. The table view has two columns, labeled “Keys” and “Values”.

Each column’s value binding is bound to an NSArrayController; each array controller’s contentArray binding is bound to a property of my application delegate; each property’s value is an NSArray of NSStrings.

The Keys column correctly has one of its strings in each row. The Values column incorrectly has its array in every row. This is the problem that has stumped me.

The object class is NSString in both array controllers. There’s no Core Data in this app.

I have 12-tuple-checked the bindings in IB; they are the same between the two array controllers and between the two table columns.

The accessors are automatically generated by my accessor-generator services. Even so (having added some debug logging previously), I double-checked them, and they are the same.

I have logged the contents of both arrays in my setters. Both arrays are arrays of strings, and both contain the same number of strings.

This problem manifests on both Tiger and Leopard.

Here are screenshots of my Bindings Inspectors:

The array controllers' contentArray bindings are bound to the app delegate's lastMessageKeys and lastMessageValues properties. There is no value transformer in use. The options that are turned on are Conditionally Sets Editable and Raises For Not Applicable Keys; the options that are turned off are Always Presents Application Modal Alerts, Deletes Objects On Remove, Handles Content As Compound Value, Selects All When Setting Content, and Validates Immediately.

The table columns' value bindings are bound to the array controllers' arrangedObjects properties. There is no value transformer in use. The options that are turned on are Allows Editing Multiple Values Selection, Conditionally Sets Editable, Creates Sort Descriptor, and Raises For Not Applicable Keys; the options that are turned off are Always Presents Application Modal Alerts, Conditionally Sets Enabled, Continuously Updates Value, and Validates Immediately.

Each screenshot has two Inspectors superimposed on each other, with the upper Inspector set to half opacity; the result is that pixels that are the same look normal, whereas pixels that differ look ghostly. For your ease of reading, I knocked out the parts that differ and staggered them, so you don’t have to try to make out ghostly text.

Any suggestions?

A possible solution to the stolen-focus problem

Wednesday, December 5th, 2007

Jeff Atwood posted an article today titled Please Don’t Steal My Focus, about dialog boxes coming up while you’re typing.

The problem, in a nutshell, is that these dialog boxes steal keyboard focus. Sometimes it’s for a text field; sometimes it’s just for Full Keyboard Access (i.e., a button gets the focus—usually, these are alert boxes). Regardless of what control gets keyboard focus, it gets it by stealing it from your app, which is a problem if the user is typing when that happens. Most likely, either some text will go to the wrong place, or the dialog box will beep for every keystroke; either result will annoy the user.

We’ve had the same problem for a long time in Adium: we’ll throw up a dialog box, but the user is typing a message, and notices that half his message isn’t in the inputline. We believe we’ve fixed most of these, but we still get complaints about this problem from time to time.

Currently, we solve the problem by simply ordering the dialog front without making it key. This works for most people, but it doesn’t help people who really do need Full Keyboard Access, since you have to click on the dialog box to dismiss it (or make it key) if it isn’t key. We (by which I mean Mac programmers, not just Adium) need a better solution.

I propose an addition to NSAlert. Here’s how it would work:

  1. From the creation of the NSAlert instance, it watches for key events.
  2. When any key goes down, the alert resets the timer (if one is present) and sets a flag.
  3. When all keys are up, the alert creates and starts the timer (with an interval of, say, a couple of seconds).
  4. If you tell the alert to show, it checks the flag. If the flag is clear, it simply performs makeKeyAndOrderFront: like usual. If the flag is set, it only orders front, and it sets a second flag.
  5. When the timer fires, the alert clears the first flag, and if the second flag is set, makes itself key.

From the user’s perspective, this results in the alert waiting a couple of seconds for them to pause in their typing before it takes keyboard focus.

Does anybody know of an existing free implementation of this, or plan to write it themselves?

Why you should use spaces before your method arguments

Monday, November 12th, 2007

You can select a rectangle that intersects the spaces. You can't do this with tabs.

Backstory: I had moved the method being called there from a file of NSCalendarDate additions to a file to NSDate additions, so I needed to remove “Calendar”, and wanted to also take out the now-extraneous alignment spacing. One rectangular edit did the job, thanks to using spaces for the alignment.

Of course, you should always use tabs for that initial indent.

An invalid property list

Monday, October 1st, 2007

If you work on an [added: a document-based] app that uses a plist-based format, you should test your plist-reading code to see how it handles an invalid (e.g., eaten by the user’s dog) plist. Here’s one:

<plist>
<qux></qux>
</plist>

The binary plist parser won’t even touch it because it doesn’t have the bplist header, the XML parser will choke on it because it contains an element that it doesn’t recognize, and the OpenStep parser will choke on it because the first line doesn’t end with a semicolon or backslash.

When you feed this to your app, your app should present an error message. Anything else is a bug. (UPDATE 2007-10-02: OK, maybe not. There’s at least one circumstance where you can ignore it, as pointed out in the comments. I still think I’m right in most circumstances, just clearly not all.)

Report-an-Apple-Bug Friday! 70

Friday, September 14th, 2007

This bug is Borken implementation of -setNilValueForKey: in Model Object Impl Guide. It was filed on 2007-09-14 at 12:24 PDT.

(more…)

Quickie: Bindings and KVV

Tuesday, September 11th, 2007

If you want Bindings to call your Key-Value Validation methods, you need to check the Validates Immediately checkbox in the binding editor in IB.

In my case, this was with an NSTableColumn’s value binding in an NSTableView.

Apple Bug Friday! 69

Friday, August 31st, 2007

This bug is Member variable is not an Objective-C term. It was filed on 2007-08-10 at 12:13 PDT.

(more…)

Report-an-Apple-Bug Friday! 68

Friday, August 10th, 2007

This bug is Either you can use a sdef directly, or you cannot. It was filed on 2007-08-10 at 11:51 PDT.

(more…)

Four headers worth reading

Tuesday, August 7th, 2007
  • <Foundation/FoundationErrors.h>
  • <AppKit/AppKitErrors.h>
  • <CoreData/CoreDataErrors.h>
  • <WebKit/WebKitErrors.h>

These four headers define error codes in NSCocoaErrorDomain, which you can use in and expect from NSError instances.

There’s also <QuickTime/QuickTimeErrors.h>, but its error codes are OSStatuses (usable with NSOSStatusErrorDomain), which makes them better-known to begin with.

A complete raw list of KVC accessor selector formats

Tuesday, August 7th, 2007

Implementing and using (i.e., calling) these accessors will generate appropriate KVO notifications for free. (UPDATE 2008-11-10: If you’re targeting Leopard, declare and use (i.e., self.foo = bar) Obj-C 2 properties. Exception: Not in your init and dealloc methods, and that goes for regular accessors as well.)

Some are for arrays, some are for sets, some are only for mutable properties, and some are for all keys.

  • validate%s:error:
  • %sForKeyPath:
  • _%sForKeyPath:
  • get%s
  • is%s
  • _get%s
  • countOf%s
  • objectIn%sAtIndex:
  • %sAtIndexes:
  • get%s:range:
  • enumeratorOf%s
  • memberOf%s:
  • _is%s
  • set%s:
  • _set%s:
  • getPrimitive%s
  • primitive%s
  • setPrimitive%s:
  • add%sObject:
  • remove%s:
  • remove%sObject:
  • add%s:
  • intersect%s:
  • insertObject:in%sAtIndex:
  • insert%s:atIndexes:
  • removeObjectFrom%sAtIndex:
  • remove%sAtIndexes:
  • replaceObjectIn%sAtIndex:withObject:
  • replace%sAtIndexes:with%s:

Source: strings /S/L/F/Foundation.framework/Foundation.

Note that NSArrayController will not call the finer-grained array methods (e.g., insertObject:in%sAtIndex:) if you provide the basic get and set methods (%s and set%s:).

WWDC 2007 session videos are out

Monday, July 30th, 2007

If you attended WWDC, you can head over to ADC on iTunes and see what you missed.

How to make your app’s Dock tile highlight

Monday, July 23rd, 2007

So, no matter what you do, your Dock tile doesn’t highlight when you drag a document onto it. You’ve hexadecuple-checked your CFBundleDocumentTypes list, and everything looks correct, but Dock is not co-operating and you just want to kick it.

Finally, you decide to remove the LSItemContentTypes key, and it works just fine! How can this be?


Well, since Tiger, LSItemContentTypes shuts out all the other document-type tag keys. Your CFBundleTypeOSTypes, your CFBundleTypeExtensions—all of those are ignored when LSItemContentTypes is present; it looks at nothing but the LSItemContentTypes list. Taking out LSItemContentTypes forces LS to look at your OSTypes and extensions lists instead.

More to the point, your problem is that the set of UTIs you’ve specified does not contain the UTI of the would-be document that you’re dragging.

You could be forgiven for expecting that having com.apple.application in your set of UTIs would allow you to open applications as your documents. This is not true, because a standardly-constructed Mac OS X application is of type com.apple.application-bundle. That type does conform to com.apple.application, but it is not equal to com.apple.application, so the Dock refuses your drag with a dismissive wave of its kerchief bundle and an AIFF file of a sharp “hmph!”.

So how do you find out the true UTI of your document?

The easy way is mdls. Ask for the kMDItemContentType property:

% mdls -name kMDItemContentType Foo.app
Foo.app -------------
kMDItemContentType = "com.apple.application-bundle"

But this only works on a volume with a Spotlight index; for example, it doesn’t work on disk images or RAM disks (same thing). Another way is Nicholas Riley’s launch, with its -f option:

% launch -f Foo.app
Foo.app: Mac OS X application package 
        type: 'APPL'    creator: …
        architecture: PowerPC, Intel 80x86
        bundle ID: …
        version: …
        kind: Application
        content type ID: com.apple.application-bundle
        contents: 1 item
        created: 2007-07-23 07:12:31
        modified: 2007-07-23 07:12:31
        accessed: 2007-07-23 07:12:41 [only updated by Mac OS X]
        backed up: 1903-12-31 17:00:00

Either way, put that UTI into your list. Then drags will work.

In summary, the Dock checks for equality of the prospective document’s UTI to the UTIs listed under LSItemContentTypes, not conformity. You need to list every UTI you support in your Info.plist, including those that conform to the ones you expect. It’s either that or give up on UTIs entirely.

(Summary suggested by wootest.)

UPDATE 08:44: ssp suggested mdls instead of launch.

A simple way to make your NSLogs and NSAsserts more informative

Wednesday, June 13th, 2007

OK, so I’m not totally radio-silent. I learned about this in a WWDC session, but since it’s already public API in Tiger (actually, it’s a GCC extension), I can talk about it.

It’s a built-in macro called __PRETTY_FUNCTION__. This is a fully-qualified human-readable signature of the function you’re in. The GCC docs don’t mention this part, but it even works in Objective-C, in addition to C++ and plain C. Here’s a test app, containing this code:

@implementation Blah(blah)
- (void)blah {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}
@end

And the output:

2007-06-14 07:50:37.733 printmethod[1800] -[Blah(blah) blah]

Notice that it includes the class name, category name (if any), and method selector.

Note that that’s a C-string, not an NSCFString. Be sure to set up your format string accordingly.

What if we had a language keyword for ownership?

Tuesday, May 29th, 2007

For example:

retained NSImage *myImage;

mutablyCopied NSTextStorage *myTextStorage;
NSLayoutManager *layoutManager; //Owned by myTextStorage
NSTextContainer *textContainer; //Owned by layoutManager

Or instead of retained, we could have a keyword for non-ownership, with retain being the default for instance variables:

NSImage *myImage; //Retained by default

mutablyCopied NSTextStorage *myTextStorage;
borrowed NSLayoutManager *layoutManager; //Owned by myTextStorage
borrowed NSTextContainer *textContainer; //Owned by layoutManager

(Hopefully, this is already common, but using comments rather than language keywords.)

Perhaps these could be implemented using something like Python’s decorator syntax:

#define retained @ownership(retain)
#define copied @ownership(copy)
#define mutablyCopied @ownership(mutableCopy)

//What the code looks like after macro-expansion, or without using macros at all:
@ownership(copy) NSString *myTitle;

Either way, the correct retain/copy/mutableCopy/release magic would happen automatically on any assignment. The only manual work still involved would be:

- (void)dealloc {
	[myTimer invalidate];
	myTimer = nil; //Implicitly releases myTimer

	myTextStorage = nil; //Implicitly releases myTS (and on death, it releases its LM, and that releases its TC)

	[super dealloc];
}

This would make it totally impossible to forget to retain something (forgetting to retain or copy things leads to double-releases or zombie objects), and make it much harder to retain when you should copy or vice-versa. It would also make your header much richer in documentation, and the more you can learn from the header, the easier reading the implementation will be.

Now that’s what I call a speed-up

Tuesday, May 29th, 2007

CPU Usage 0.4 creates and throws away an NSImage every time a view (one per processor) updates. This is incredibly wasteful: The application uses about 0.7% of a CPU on my system.

But I decided that I can make that faster. Profiling (thanks, Shark!) revealed that the hot spot was creating, measuring, drawing, and throwing away the attributed string that goes into the NSImage. That’s not hard to optimize away: as the old doctor joke goes, “stop doing that”.

So I created a couple of branches, and went in two different directions:

  • Direction A uses NSLayoutManager—specifically, one NSTextStorage, one NSLayoutManager, and one NSTextContainer for every (CPU, percentage) pair. A view can display any of 101 percentages (0–100%), so on my four-core system, this branch creates 404 storages, 404 managers, and 404 containers.
  • Direction B uses an NSImage, just like 0.4 does, but keeps it around.

Both branches are written to create the (NSImage|NSTS, NSLM, and NSTC) lazily, but for testing, I added this code to force the creation of all the cached objects up front so that my time-trials would represent normal usage (that is, usage after all the objects have been created and cached):

- (void)drawRect:(NSRect)rect {
    //BEGIN TEMP
    if(!(percentageImages[0U])) {
        static BOOL isInDrawRect;
        if(!isInDrawRect) {
            isInDrawRect = YES;

            NSLog(@"Preloading percentage images for CPU %u", CPUNumber);
            NSRect tempImageBounds = [self bounds];
            NSImage *tempImage = [[NSImage alloc] initWithSize:tempImageBounds.size];
            [tempImage lockFocus];
            for(float usage = 0.0f, maxUsage = 1.01f; usage < maxUsage; usage += 0.01f) {
                [self setCPUUsage:usage];
                [self drawRect:tempImageBounds];
            }
            [tempImage unlockFocus];
            [tempImage release];
            NSLog(@"Done preloading percentage images for CPU %u", CPUNumber);

            isInDrawRect = NO;
        }
    }
    //END TEMP

    ⋮
    (The rest of -drawRect: is here)
}

That’s from the NSImage branch, but the code in the NSLayoutManager branch is basically the same. (Note: tempImage is not the cached image; it’s just a throwaway destination for the drawing done by -drawRect: in the inner call.)

Once I had finished this, and fully optimized both branches using Shark, the next step was to try them out and see how they fare.

I launched all three versions of CPU Usage, and then did the following:

# Watch the CPU usage of the CPU Usage processes for five minutes (300 seconds)
top -l 300 | fgrep 'CPU Usage' > top-CPU_Usage.txt      %/Volumes/RAM Disk(130)
___
# Fourth column (as determined by whitespace) is CPU usage
fgrep 221 < top-CPU_Usage.txt  | awk '{ print $4; }' > top-CPU_Usage-0.4.txt
___
fgrep 1121 < top-CPU_Usage.txt | awk '{ print $4; }' > top-CPU_Usage-NSLM.txt
___
fgrep 1118 < top-CPU_Usage.txt | awk '{ print $4; }' > top-CPU_Usage-NSImage.txt
___
# The results, in percent of one CPU
~/Python/avg.py < top-CPU_Usage-0.4.txt                   %/Volumes/RAM Disk(0)
0.671
___
~/Python/avg.py < top-CPU_Usage-NSLM.txt                  %/Volumes/RAM Disk(0)
0.667
___
~/Python/avg.py < top-CPU_Usage-NSImage.txt               %/Volumes/RAM Disk(0)
0.737

Note: For timing purposes (since top’s CPU-usage display is in tenths of a second), I divided CPU Usage’s sample interval by ten. Normally, it samples every 0.5 sec; the two prototypes above sample every 0.05 second (that is, ¹⁄₂₀ sec instead of ¹⁄₂ sec).

This means that to compare them to 0.4, you must divide the results by ten to adjust them back to the half-second interval that a release would have. Here are the *real* results:

0.4 0.671%
NSImage 0.0737%
NSLayoutManager 0.0667%

So CPU Usage 0.5, with the cached-NSLayoutManager behavior, will use ¹⁄₁₀ as much CPU as 0.4 does. And here’s what that looks like:

All four processors show 0% usage.

Sweet!

Apple Bug Friday! 60

Friday, May 25th, 2007

This bug is NSImageInterlaced has no effect. It was filed on 2007-05-12 at 00:50 PDT.

(more…)

Apple Bug Friday! 59

Friday, May 25th, 2007

This bug is The NSImageInterlaced, it does nothing!. It was filed on 2007-05-12 at 00:29 PDT.

(more…)

Report-an-Apple-Bug Friday! 58

Saturday, May 12th, 2007

Slightly late because I had to devise a way to determine whether a GIF file is interlaced. (I settled on GifBuilder, in case you’re curious.) This ties in with the next two bugs; I’ll blog both at once next week.

This bug is NSImageInterlaced documented as working on half of known interlaceable types. It was filed on 2007-05-12 at 00:27 PDT.

(more…)

How do I swap thy bytes? Let me count the ways

Saturday, April 28th, 2007
  1. swab

    swab(3) is a function that copies some bytes from one location to another, swapping each pair of bytes during the copy. Handy for structures.

    It has a feature that isn’t mentioned in the Darwin manpage for swab: If you pass a negative size, it does not swap. I have no idea why this magic behavior was added; if you want a swab that doesn’t swap bytes, just use bcopy. I shake my head at this use of a magic argument.

  2. ntohs, htons, ntohl, htonl

    These four functions swap the bytes of a 16-bit (‘s’) or 32-bit (‘l’, in ignorance of LP64) integer and return the transformed value.

    They are mainly used in network-I/O contexts, as they transform between network byte order (big-endian) and host byte order (whatever you’re running). But there’s nothing stopping you from using them for any other 16-bit/32-bit integral byte-swapping.

  3. OSByteOrder (Darwin)

    The Darwin kernel provides a number of handy-dandy macros for byte-swapping:

    • OSSwap{Const}?Int{16,32,64}
    • OSSwap{Host,Big,Little}To{Host,Big,Little}{Const}?Int{16,32,64}

    The {Host,Big,Little}To{Host,Big,Little} functions swap conditionally; the others always swap.

    According to the Universal Binary Programming Guidelines, it is safe to use these in applications.

  4. Core Foundation

    CF’s Byte-Order Utilities provide the same facilities as OSByteOrder, with a couple of twists:

    • The implementation uses assembly language when the environment is GCC on either PowerPC or x86. This is theoretically faster than OSByteOrder’s pure-C implementation. (CF falls back on pure C in all other environments.)
    • CF adds support for byte-swapping 32-bit and 64-bit floating-point numbers.
  5. Foundation

    Foundation’s byte-order functions bear all the same capabilities as the CF Byte-Order Utilities. In fact, they are implemented with them.

  6. NeXT byte-order utilities

    These utilities are equivalent to the Foundation functions, except that they are implemented using the OSByteOrder utilities. They are declared in <architecture/byte_order.h>.

  7. Core Endian

    Core Endian logo that I made up.

    I think that the “Core Endian” name itself is new in Panther. Three functions in the API have a “CoreEndian” prefix, and are marked as new in Panther, whereas the others have simply “Endian”, and are marked as having existed since 10.0. This suggests to me that the entire API was branded “Core Endian” in 10.3, with the older functions subsumed by it.

    The new functions have to do with “flipper” callbacks, which you can install so that things like Apple Event Manager can DTRT with your custom data types. The older functions are plain byte-swapping utilities, just like all the other APIs described here, and exist mainly for the benefit of QuickTime users (they exist on Windows, too, through QuickTime).

Report-an-Apple-Bug Friday! 57

Friday, April 27th, 2007

This bug is NSFrameRectWithWidth uses the current color, not the stroke color. It was filed on 2007-04-27 at 16:17 PDT.

(more…)