TweetFollow Us on Twitter

When Wild Animals Bite

Volume Number: 18 (2002)
Issue Number: 10
Column Tag: Mac OS X

When Wild Animals Bite

Or How To Workaround Cocoa bugs

by Andrew C. Stone

Of the last 50 articles I have written about Cocoa and Mac OS X, I think 49 have extolled the virtues of object oriented programming and the unparalleled advantages of using the dynamic runtime of Objective C. You might think I'd been fed some KoolAid, and you might not be too far off. But effects and mileage will vary.

The question that might arise in your mind is "Aren't there any serious drawbacks to using such a high level system?". And the answer to that would be the same as the drawbacks to any complex system: "Yes - Beware of Bit Rot", the inevitable complexification of simple architectures over time as more people work on it.

Once upon a time, the entire Cocoa development system was in the hands of a few able engineers, which kept it focused and robust between system releases. However, with Mac 10.2, aka Jaguar, we've seen a new and dangerous trend. Components that have been working almost flawlessly for over ten years have been "re-plumbed" without enough real world testing, and this can cause trouble for applications which push the envelope. Sometimes the pressure to release a new version is greater than the ability to do adequate quality control, so all of this is quite forgivable.

The same features that seem like an advantage can turn into a disadvantage! Since a Cocoa application links against the runtime AppKit and Foundation frameworks, when improvements are made to a subsystem framework, your application, even unrecompiled, will take advantage of these improvements. For example, Mac OS X 10.2's text system introduced 3 new tab types: decimal, right aligned, and centered tabs. Users of our page layout and web authoring program, Create(R), now automatically have access to these features under 10.2 - even with versions compiled under 10.1. By the same token, some changes in the subsystem framework can break existing, unrecompiled applications.

Because of the relatively small number of Cocoa engineers at Apple compared to the number of traditional Carbon engineers, there has been an increasing tendency to "pollute" our Cocoa purity with underlying Carbon implementations, and consequently, unexpected results can ensue! In Object Oriented theory, all objects are so well encapsulated and isolated that one can just swap out implementations of components and everything should just work. Reality just doesn't conform to these simplistic expectations.

When Carbon Met Cocoa

In the last several years of shipping and maintaining OS X applications, I've found that most of the problems have come up where traditional Mac OS 9 technologies have been shoehorned into the Cocoa model. The interstice between them is riddled with the same ambiguities that bedevils any organization that is a hybrid of philosophies and techniques. Cocoa's AppleScript implementation is an example of this - from the outside, it looks neat and clean, but getting it to work just right is a lot tougher than normal Cocoa programming tasks.

One object whose plumbing was changed in 10.2 is the NSPrintInfo - an object which maintains information about page size, orientation, margins and selected printer. The backend Print Manager grew out of the Carbon Tioga effort and is coded in C and C++. In 10.1, you could set the page size to any value (ideal for custom page sizes) with the simple invocation:

   [myPrintInfo setPaperSize:NSMakeSize(someWidthInPoints, someHeightInPoints)];

And the printInfo dutifully obeyed. This is useful for designing large scale posters to be printed offsite or for web sites with long content. However, in Jaguar, new behavior was introduced which attempts to see if that size is "valid" for the given printer. To be fair to the Apple engineers, I can see how this might be useful. But from my point of view, it broke the existing version of Create(R)! Luckily, since our corporate philosophy is free upgrades downloadable via the Internet, some quick coding, testing and uploading would solve the problem.

This new validation behavior occurs whenever "setPaperSize:" is called, and in the case of Create(R), that's when the document is unarchived from a file. So, when you open an old Create document with a custom size, the old size is lost forever, and all of a sudden, size "A4" is chosen! What's worse is that any attempt to even read this paper size, such as with the public NSPrintInfo API call -(NSSize)paperSize or even -objectForKey on the dictionary returned from -(NSMutableDictionary)dictionary will also trigger this new validation behavior.

Redemption Song

So, what's the workaround? Somehow, we'll find Cocoa's redemption! For any flaw that comes in a shipping OS, there are ALWAYS tricky ways in Cocoa to do post-ship fixes! Once again, Objective C Categories save us from getting egg on our digital faces.

By examining the header file NSPrintInfo.h, we see a private instance variable (IVAR), _attributes, which is the actual mutable dictionary which holds all the values of the PrintInfo:

@interface NSPrintInfo : NSObject<NSCopying, NSCoding> {
    @private
    NSMutableDictionary *_attributes;
    void *_moreVars;
}

Because the IVAR is designated private, even a subclass cannot access it directly, so subclassing NSPrintInfo won't work. Only a category of the class containing the private IVAR can directly access the variable. Here's a category that can return any set value for paperSize, without triggering the unwanted validation behavior:

@interface NSPrintInfo (peek)

- (NSSize)grabOriginalSize;
@end
@implementation NSPrintInfo(peek)
- (NSSize)grabOriginalSize {
    id obj = [_attributes objectForKey:NSPrintPaperSize];
    return obj? [obj sizeValue]: NSZeroSize;
}
@end

Documents which had no custom page size will return NSZeroSize, which indicates that no extra work will be required. So, here's how we'll use the new category method:

            NSPrintInfo *printInfo = [NSUnarchiver unarchiveObjectWithData:obj];
       // a freshly unarchived PrintInfo still has its old values in the dictionary....
            if (printInfo) {
                NSSize raw = [printInfo grabOriginalSize];
                if (!NSEqualSizes(raw, NSZeroSize)) {   
         // it could have been written
                    // by a 10.1 version of Create
                   [self setUpPrintInfo:printInfo toPaperSize:raw];
                }             
                [self setPrintInfo:printInfo];
            }

Now, all we need to do is fake NSPrintInfo out so that it thinks the custom paper size is valid. To do this, I had to dig into undocumented API using class-dump as explained in several of my last few articles, and even more dreaded by an Objective C coder, into the Carbon header files! It turns out that there is a C function to make custom paper sizes programmatically, which stands to reason because the new Page Setup... panel in Jaguar has a popup option to set custom paper sizes:

OSStatus PMPaperCreate(PMPrinter printer, CFStringRef id, CFStringRef name, double width, double height, const PMPaperMargins *margins, PMPaper *paperP);

And we'll call it like this:

        OSStatus osStatus = PMPaperCreate([[pi printer] _printer], 
(CFStringRef)[NSString stringWithFormat:@"%ld",self], 
(CFStringRef)[NSString stringWithFormat:@"%2.2f X %2.2f",userW,userH],(double) paperSize.width,
(double) paperSize.height, marginPtr, &myPaper);

If PMPaperCreate() returns an OSStatus of 0, then a new PMPaper * object (which you'll need to later release after use) has been created in myPaper. The first parameter, PMPrinter, can be returned from the NSPrintInfo via undocumented NSPrintInfo API, -(PMPrinter)_printer:

[[pi printer] _printer]

Another sweet feature of Cocoa is "toll-free bridging" between Core Foundation objects, such as a CFStringRef and the equivalent Cocoa object, in this case, the NSString. We will cast the NSString parameters to CFStringRef's just to hush the compiler. The other fancy dancing that we're doing is using the pointer to memory of the document object as our uniquing strategy for the second argument, but it could be any string as long as it is a unique identifier:

(CFStringRef)[NSString stringWithFormat:@"%ld",self]

Finally, we'll name the custom page in the user's units by converting the points to their measurement units - these functions are included below.

// these functions are declared here:
#import <CoreServices/CoreServices.h>
#import <ApplicationServices/ApplicationServices.h>
typedef struct OpaquePMPaper *PMPaper;
typedef struct {
    double top;
    double left;
    double bottom;
    double right;
} PMPaperMargins;

OSStatus PMPaperCreate(PMPrinter printer, CFStringRef id, CFStringRef name, double width, double height, const PMPaperMargins *margins, PMPaper *paperP);

- (void)setUpPrintInfo:(NSPrintInfo *)pi toPaperSize:(NSSize)paperSize {
    if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_1) {
        /* On a 10.1 - 10.1.x system */
   // nothing special to do...
    } else {
        /* 10.2 or later system */
        float userW = convertToUserUnits(paperSize.width);
        float userH = convertToUserUnits(paperSize.height);
        PMPaperMargins margins;
        PMPaperMargins *marginPtr = &margins;   
                        // does C suck or what?
        PMPaper *myPaper;
        // Create uses WYSIWYG full page model:
        margins.top = margins.left = margins.bottom = margins.right = 0.0;
        
        OSStatus osStatus = PMPaperCreate([[pi printer] _printer], (CFStringRef)[NSString 
        stringWithFormat:@"%ld",self], (CFStringRef)[NSString stringWithFormat:@"%2.2f X %2.2f",
        userW,userH],(double) paperSize.width,(double) paperSize.height, marginPtr, &myPaper);
        
     if (osStatus  != 0) {
      // You may want to do something else here:
      NSLog(@"Trouble creating a custom page size: %f by %f",paperSize.width, paperSize.height);
        }
    }
     // on 10.1 this just works:
    [pi setPaperSize:paperSize];
}
// getting user units from an NSUserDefault: - sometimes C is cool!
float
pointsFromUserUnits()
{
    switch([[NSUserDefaults standardUserDefaults] integerForKey:@"MeasurementUnits"]) {
            case 0: return 72.0;
            case 1: return 28.35;
            case 2: return 1.0;
            case 3: return 12.0;
            default: return 72.0;
    }
}
float
convertToUserUnits(float points)
{
    return points/pointsFromUserUnits();
}

Conclusion

So now, our printInfo will return the correct new size! Well, the dust hasn't settled entirely, so hopefully this will work and continue to work in the future. Ideally, NSPrintInfo would just "do the right thing" in terms of creating these custom page sizes. The proposed solution doesn't address custom page size uniquing and coalescing of similar-sized pages - but then, I haven't finished coding the solution entirely either! Even the mighty Cocoa has its compatibility weaknesses as it grows, but its native power can even pull the tractor out of the mud when it gets really stuck.


Andrew Stone, founder of Stone Design <www.stone.com>, spends too much time coding and not enough time gardening.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Viber 12.4.0 - Send messages and make fr...
Viber lets you send free messages and make free calls to other Viber users, on any device and network, in any country! Viber syncs your contacts, messages and call history with your mobile device, so... Read more
OmniFocus 3.5.1 - GTD task manager with...
OmniFocus is an organizer app. It uses projects to organize tasks naturally, and then add tags to organize across projects. Easily enter tasks when you’re on the go, and process them when you have... Read more
Network Radar 2.9 - $17.99
Network Radar is an advanced network scanning and managing tool. Featuring an easy-to-use and streamlined design, the all-new Network Radar 2 has been engineered from the ground up as a modern Mac... Read more
Tidy Up 5.3.4 - Find duplicate files and...
Tidy Up is a full-featured duplicate finder and disk-tidiness utility. Features: Supports Lightroom: it is now possible to search and collect duplicates directly in the Lightroom library. Multiple... Read more
DiskCatalogMaker 8.0 - Catalog your disk...
DiskCatalogMaker is a simple disk management tool which catalogs disks. Simple, light-weight, and fast Finder-like intuitive look and feel Super-fast search algorithm Can compress catalog data for... Read more
ExpanDrive 7.4.11 - Access cloud storage...
ExpanDrive builds cloud storage in every application, acts just like a USB drive plugged into your Mac. With ExpanDrive, you can securely access any remote file server directly from the Finder or... Read more
OmniGraffle Pro 7.13 - Create diagrams,...
OmniGraffle Pro helps you draw beautiful diagrams, family trees, flow charts, org charts, layouts, and (mathematically speaking) any other directed or non-directed graphs. We've had people use... Read more
OmniGraffle 7.13 - Create diagrams, flow...
OmniGraffle helps you draw beautiful diagrams, family trees, flow charts, org charts, layouts, and (mathematically speaking) any other directed or non-directed graphs. We've had people use Graffle to... Read more
Airmail 4.0 - Powerful, minimal email cl...
Airmail is an mail client with fast performance and intuitive interaction. Support for iCloud, MS Exchange, Gmail, Google Apps, IMAP, POP3, Yahoo!, AOL, Outlook.com, Live.com. Airmail was designed... Read more
OmniOutliner Essentials 5.5.3 - Organize...
OmniOutliner Essentials (was OmniOutliner) is a flexible program for creating, collecting, and organizing information. Give your creativity a kick start by using an application that's actually... Read more

Latest Forum Discussions

See All

Isle Escape: The House is an upcoming pu...
Isle Escape: The House is an upcoming puzzle game from Simeon Angelov that's intended to serve as an introduction to a saga they're planning on releasing in an episodic fashion. The first chapter is set to release for both iOS and Android on 29th... | Read more »
Company of Heroes, the classic RTS, is n...
Feral Interactive has finally released their highly anticipated iOS version of the strategy classic Company of Heroes. It's available now for iPad as a premium title and has had various tweaks to ensure that it's optimised for touch controls. [... | Read more »
Mario Kart Tour's Vancouver Tour ha...
With Mario Kart Tour's Valentine's Tour now at an end (suspiciously before Valentine's Day has even arrived), it's now time to move on to the all-new and exciting Vancouver Tour. This time around, the featured drivers are Hiker Wario and Aurora... | Read more »
A new PictoQuest update makes it a much...
PictoQuest is a charming little puzzle game, but it left us a little disappointed. The game just didn’t seem to use screen space effectively, to the point that using the touch controls (as opposed to the default virtual d-pad) could lead to errant... | Read more »
Alley is an atmospheric adventure game a...
Alley is an atmospheric adventure game that sees you playing as a young girl trapped in an inescapable nightmare. Surrounded by her worst fears, every step forward for her is a huge challenge that you'll help guide her through using some simple... | Read more »
Fight monsters and collect heroes in Cry...
From Final Fantasy to Chaos Rings, Japanese roleplaying games have found a large and loyal fanbase on mobile devices. If you’re seeking a more under-the-radar JRPG to escape into, Lionsfilm’s Cryptract could be the one. The game has been around... | Read more »
Circuit Dude is a top-down, tile-based p...
Circuit Dude is a tile-based puzzler that was originally released on Steam back in 2017. Now it's made it's way over to mobile devices where it's available for both iOS and Android as a premium game. [Read more] | Read more »
Liege Dragon is another upcoming RPG for...
Liege Dragon is an upcoming RPG from Kemco, who has certainly streamlined the process of making their particular brand retro-inspired turn-based games at this point. Liege Dragon will be available for both iOS and Android. [Read more] | Read more »
Hidden Survivor from Joy Brick is a hide...
Joy Brick's Hidden Survivor is an interesting title of two halves: part story-focused survival experience, part intense hide-and-seek multiplayer game. Both elements come together to form a compellingly strange and enjoyable whole. The hide-and-... | Read more »
Stupid Zombies 4 is an upcoming trick-sh...
The Stupid Zombies are preparing to make their grand return to iOS and Android in the fourth instalment of the hugely popular trick-shot shooter series. If you missed out on the earlier games, the basic idea is that you have to bounce bullets... | Read more »

Price Scanner via MacPrices.net

Sunday sale: 27″ 5K iMacs for $150 off Apple’...
B&H Photo has new 2019 27″ 5K iMacs in stock today and on sale for $150 off Apple’s MSRP. Overnight shipping is free to many locations in the US: – 27″ 3.0GHz 5K iMac: $1649.99 $150 off MSRP – 27... Read more
Sunday sale: 21″ iMacs for $100-$150 off Appl...
B&H Photo has new 21″ Apple iMacs on sale for $100 off MSRP with models available starting at $999. These are the same iMacs offered by Apple in their retail and online stores. Overnight shipping... Read more
Best Buy President’s Day Weekend 2019 sale: A...
Best Buy has Apple HomePods on sale for $249.99 as part of their President’s Day Weekend 2019 sale. Both Space Gray and White HomePods are on sale for this price. Their price is $50 off Apple’s MSRP... Read more
President’s Day Weekend Sale: 13″ 1.4GHz MacB...
Amazon has new 2019 13″ 1.4GHz MacBook Pros on sale for $200 off Apple’s MSRP, starting at $1099, as part of their President’s Day Weekend sale. These are the same MacBook Pros sold by Apple in its... Read more
President’s Day Weekend Sale: Apple AirPods f...
Amazon has new 2019 Apple AirPods on sale today ranging up to $35 off MSRP, starting at $129, as part of their President’s Day Weekend sale. Shipping is free: – AirPods Pro: $234.98 $15 off MSRP –... Read more
Save hundreds on custom 16″ MacBook Pro confi...
Save up to $920 on a custom-configured 16″ MacBook Pro with these Certified Refurbished models now available at Apple. Each MacBook Pro features a new outer case, free shipping, and includes Apple’s... Read more
Back on sale: 4 and 6-core Mac Minis for $100...
B&H Photo has 4-Core and 6-Core Mac minis on sale for $100 off Apple’s standard MSRP, with prices starting at only $699. Overnight shipping is free to many US addresses: – 3.6GHz Quad-Core mini... Read more
16″ MacBook Pros, Certified Refurbished, now...
Apple is now offering Certified Refurbished 2019 16″ MacBook Pros for up to $420 off the cost of new models, starting at $2039. Each model features a new outer case, shipping is free, and an Apple 1-... Read more
Purchase a new Apple Pro Display XDR and pay...
Apple reseller DataVision has Apple’s new Pro Display XDR models available for order including sales tax for NY, NJ, PA, and CA residents only. If you don’t reside in one of those states, you can... Read more
B&H has select 13″ 2.4GHz MacBook Pros on...
B&H Photo has select 2019 13″ 2.4GHz MacBook Pros on sale $250 off Apple’s MSRP, starting at $1549. Overnight shipping is free to many addresses in the US. These are the same MacBook Pros sold by... Read more

Jobs Board

*Apple* Computing Professional - Best Buy (U...
**761650BR** **Job Title:** Apple Computing Professional **Job Category:** Store Associates **Store NUmber or Department:** 000217-Aurora-Store **Job Description:** Read more
Medical Assistant - *Apple* Valley Clinic -...
…provide professional, quality care to patients in the ambulatory setting at the Fairview Apple Valley Clinic, located in Apple Valley, MN. Join the **Fairview Read more
Geek Squad *Apple* Consultation Professiona...
**762475BR** **Job Title:** Geek Squad Apple Consultation Professional **Job Category:** Store Associates **Store NUmber or Department:** 001423-San Jose-Store **Job Read more
*Apple* Engineering Specialist - Amentum (Un...
Job Summary Amentum has an immediate opportunity for an Apple Engineering Solutions to support a government agencys capabilities in Washington, DC (Union Station / Read more
Best Buy *Apple* Computing Master - Best Bu...
**745058BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Store Associates **Store NUmber or Department:** 001080-Lake Charles-Store **Job Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.