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

Audio Hijack 3.7.3 - Record and enhance...
Audio Hijack (was Audio Hijack Pro) drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio... Read more
CleanMyMac X 4.6.15 - Delete files that...
CleanMyMac makes space for the things you love. Sporting a range of ingenious new features, CleanMyMac lets you safely and intelligently scan and clean your entire system, delete large, unused files... Read more
Suitcase Fusion 21.2.1 - Font management...
Suitcase Fusion is the creative professional's font manager. Every professional font manager should deliver the basics: spectacular previews, powerful search tools, and efficient font organization.... Read more
Civilization VI 1.3.6 - Next iteration o...
Civilization® VI is the award-winning experience. Expand your empire across the map, advance your culture, and compete against history’s greatest leaders to build a civilization that will stand the... Read more
Dashlane 6.2042.0 - Password manager and...
Dashlane is an award-winning service that revolutionizes the online experience by replacing the drudgery of everyday transactional processes with convenient, automated simplicity - in other words,... Read more
Airfoil 5.9.2 - Send audio from any app...
Airfoil allows you to send any audio to AirPort Express units, Apple TVs, and even other Macs and PCs, all in sync! It's your audio - everywhere. With Airfoil you can take audio from any... Read more
VirtualBox 6.1.16 - x86 virtualization s...
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more
Xcode 12.1 - Integrated development envi...
Xcode includes everything developers need to create great applications for Mac, iPhone, iPad, and Apple Watch. Xcode provides developers a unified workflow for user interface design, coding, testing... Read more
FileZilla 3.51.0 - Fast and reliable FTP...
FileZilla (ported from Windows) is a fast and reliable FTP client and server with lots of useful features and an intuitive interface. Version 3.51.0: Bugfixes and minor changes: Fixed import of... Read more
KeyCue 9.8 - Displays all menu shortcut...
KeyCue has always been a handy tool for learning and remembering keyboard shortcuts. With a simple keystroke or click, KeyCue displays a table with all available keyboard shortcuts, system-wide... Read more

Latest Forum Discussions

See All

PUBG Mobile has provided yet another upd...
PUBG Mobile has been making a point of publicly mentioning all of their ongoing efforts to vanquish cheating from the popular battle royale. Today two teams within the company have provided updates on their progress. [Read more] | Read more »
Zombieland: AFK Survival is celebrating...
Zombieland: AFK Survival is currently celebrating its one-year anniversary. If you don't quite recognise the name that's because it initially launched as Zombieland: Double Tapper. Anyway, the game is celebrating turning one with two Halloween-... | Read more »
Distract Yourself With These Great Mobil...
There’s a lot going on right now, and I don’t really feel like trying to write some kind of pithy intro for it. All I’ll say is lots of people have been coming together and helping each other in small ways, and I’m choosing to focus on that as I... | Read more »
Genshin Impact Guide - Gacha Strategy: W...
If you're playing Genshin Impact without spending money, you'll always need to be looking for ways to optimize your play to maximize rewards without getting stuck in a position where you're tempted to spend. The most obvious trap here is the game'... | Read more »
Genshin Impact Adventurer's Guide
Hello and well met, fellow adventurers of Teyvat! Check out our all-in-one resource for all things Genshin Impact. We'll be sure to add more as we keep playing the game, so be sure to come back here to check for updates! [Read more] | Read more »
Genshin Impact Currency Guide - What...
Genshin Impact is great fun, but make no mistake: this is a gacha game. It is designed specifically to suck away time and money from you, and one of the ways the game does this is by offering a drip-feed of currencies you will feel compelled to... | Read more »
XCOM 2 Collection on iOS now available f...
The XCOM 2 Collection, which was recently announced to be coming to iOS in November, is now available to pre-order on the App Store. [Read more] | Read more »
Presidents Run has returned for the 2020...
IKIN's popular endless runner Presidents Run has returned to iOS and Android just in time for the 2020 election season. It will see players choosing their favourite candidate and guiding them on a literal run for presidency to gather as many votes... | Read more »
New update for Cookies Must Die adds new...
A new update for Rebel Twins’ platformer shooter Cookies Must Die is coming out this week. The update adds quite a bit to the game, including new levels and characters to play around with. [Read more] | Read more »
Genshin Impact Guide - How to Beat Pyro...
The end game of Genshin Impact largely revolves around spending resin to take on world bosses and clear domain challenges. These fights grant amazing rewards like rare artifacts and ascension materials for weapons and adventurers, but obviously... | Read more »

Price Scanner via MacPrices.net

Use our exclusive iPhone Price Trackers to fi...
Looking for a new Apple iPhone 12 or 12 Pro? Perhaps a deal on last year’s iPhone 11? Check out our iPhone Price Tracker here at MacPrices.net. We track new and clearance iPhone prices from Apple as... Read more
Weekend deal: $100 off 13″ MacBook Airs at Am...
Amazon has new 2020 13″ MacBook Airs on sale for $100 off Apple’s MSRP, starting at only $899. Their prices are the lowest available for new MacBooks from any Apple resellers. These are the same 13″... Read more
New 10.9″ 64GB Apple iPad Air on sale for $55...
Amazon has Apple’s new 2020 10.9″ 64GB WiFi iPad Air on sale today for $549.99 shipped. That’s $40 off MSRP. Pre-orders are available today at this discounted price, and Amazon states that the iPad... Read more
Get a clearance 2019 27″ 5K iMac for up to $5...
Apple has Certified Refurbished 2019 27″ 5K iMacs available starting at $1439 and up to $520 off their original MSRP. Apple’s one-year warranty is standard and shipping is free. The following... Read more
AT&T offers the Apple iPhone 11 for $10/m...
AT&T is offering Apple’s 64GB iPhone 11 for $10 per month, for customers opening a new line of service, no trade-in required. Discount is applied via monthly bill credits over a 30 month period.... Read more
Apple’s 2020 11″ iPad Pros on sale today for...
Apple reseller Expercom has new 2020 11″ Apple iPad Pros on sale for $50-$75 off MSRP, with prices starting at $749. These are the same iPad Pros sold by Apple in their retail and online stores: – 11... Read more
Did Apple Drop The Ball By Not Branding Its C...
EDITORIAL: 10.21.20 – In the branding game, your marketing strategy can either be a hit or a miss and the latter is the case for Apple when it missed out on an opportunity to brand its “SE” series of... Read more
27″ 6-core and 8-core iMacs on sale for up to...
Adorama has Apple’s 2020 27″ 6-core and 8-core iMacs on sale today for $50-$100 off MSRP, with prices starting at $1749. Shipping is free: – 27″ 3.1GHz 6-core iMac: $1749, save $50 – 27″ 3.3GHz 6-... Read more
Apple’s 16″ MacBook Pros are on sale for $300...
B&H Photo has 16″ MacBook Pros on sale today for $300-$350 off Apple’s MSRP, starting at $2099. Expedited shipping is free to many addresses in the US. Their prices are among the lowest available... Read more
Apple has 2020 13″ MacBook Airs available sta...
Apple has a full line of Certified Refurbished 2020 13″ MacBook Airs available starting at only $849 and up to $200 off the cost of new Airs. Each MacBook features a new outer case, comes with a... Read more

Jobs Board

*Apple* Mobility Specialist - Best Buy (Unit...
**788165BR** **Job Title:** Apple Mobility Specialist **Job Category:** Store Associates **Store Number or Department:** 001013-Virginia Commons-Store **Job Read more
Cub Foods - *Apple* Valley - Now Hiring Par...
Cub Foods - Apple Valley - Now Hiring Part Time! United States of America, Minnesota, Apple Valley Retail Post Date Oct 08, 2020 Requisition # 124800 Sign Up for Read more
*Apple* Mobility Specialist - Best Buy (Unit...
**784631BR** **Job Title:** Apple Mobility Specialist **Job Category:** Store Associates **Store Number or Department:** 000522-Baxter-Store **Job Description:** The Read more
Senior Data Engineer - *Apple* - Theorem, L...
Job Summary Apple is seeking an experienced, detail-minded data engineeringconsultant to join our worldwide business development and strategy team. If you are Read more
Corporate and Workplace Solutions - Workplace...
MORE ABOUT THIS JOB We are looking for an Apple Platform Engineer who will bring a unique engineering skill set, support, clarity, organization and above all else, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.