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

GraphicConverter 10.5.1 - $39.95
GraphicConverter is an all-purpose image-editing program that can import 200 different graphic-based formats, edit the image, and export it to any of 80 available file formats. The high-end editing... Read more
Delicious Library 3.7 - Import, browse a...
Delicious Library allows you to import, browse, and share all your books, movies, music, and video games with Delicious Library. Run your very own library from your home or office using our... Read more
Adobe Animate CC 2017 18.0.0.107 - Anima...
Animate CC 2018 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous Flash Professional customer). Animate CC 2018 (was Flash CC) lets you... Read more
Adobe After Effects CC 2018 15.0 - Creat...
After Effects CC 2018 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous After Effects customer). The new, more connected After Effects CC... Read more
Adobe Premiere Pro CC 2018 12.0.0 - Digi...
Premiere Pro CC 2018 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous Premiere Pro customer). Adobe Premiere Pro CC 2018 lets you edit... Read more
Alarm Clock Pro 10.3 - $19.95
Alarm Clock Pro isn't just an ordinary alarm clock. Use it to wake you up in the morning, send and compose e-mails, remind you of appointments, randomize the iTunes selection, control an internet... Read more
Adobe Lightroom 20170919-1412-ccb76bd] -...
Adobe Lightroom is available as part of Adobe Creative Cloud for as little as $9.99/month bundled with Photoshop CC as part of the photography package. Lightroom 6 is also available for purchase as a... Read more
Adobe Illustrator CC 2018 22.0.0 - Profe...
Illustrator CC 2018 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous Illustrator customer). Adobe Illustrator CC 2018 is the industry... Read more
Hopper Disassembler 4.3.0- - Binary disa...
Hopper Disassembler is a binary disassembler, decompiler, and debugger for 32- and 64-bit executables. It will let you disassemble any binary you want, and provide you all the information about its... Read more
Adobe InDesign CC 2018 13.0.0.125 - Prof...
InDesign CC 2018 is available as part of Adobe Creative Cloud for as little as $19.99/month (or $9.99/month if you're a previous InDesign customer). Adobe InDesign CC 2018 is part of Creative Cloud.... Read more

ICEY (Games)
ICEY 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: ICEY is a 2D side-scrolling action game. As you follow the narrator's omnipresent voice, you will see through ICEY's eyes and learn the... | Read more »
The best new games we played this week -...
We've made it, folks. Another weekend is upon us. It's time to sit back and relax with the best new releases of the week. Puzzles, strategy RPGs, and arcade games abound this week. There's a lot of quality stuff to unpack this week, so let's hop... | Read more »
Wheels of Aurelia (Games)
Wheels of Aurelia 1.0.1 Device: iOS Universal Category: Games Price: $3.99, Version: 1.0.1 (iTunes) Description: | Read more »
Halcyon 6: Starbase Commander guide - ti...
Halcyon 6 is a well-loved indie RPG with stellar tactical combat and some pretty good writing, too. It's now landed on the App Store, so mobile fans, if you're itching for a good intergalactic adventure, here's your game. Being a strategy RPG, the... | Read more »
Game of Thrones: Conquest guide - how to...
Fans of base building games might be excited to know that yet another entry in the genre has materialized - Game of Thrones: Conquest. Yes, you can now join the many kingdoms of the famed book series, or create your own, as you try to conquer... | Read more »
Halcyon 6: Starbase Commander (Games)
Halcyon 6: Starbase Commander 1.4.2.0 Device: iOS Universal Category: Games Price: $6.99, Version: 1.4.2.0 (iTunes) Description: An epic space strategy RPG with base building, deep tactical combat, crew management, alien diplomacy,... | Read more »
Legacy of Discord celebrates its 1 year...
It’s been a thrilling first year for fans of Legacy of Discord, the stunning PvP dungeon-crawling ARPG from YOOZOO Games, and now it’s time to celebrate the game’s first anniversary. The developers are amping up the festivities with some exciting... | Read more »
3 reasons to play Thunder Armada - the n...
The bygone days of the Battleship board game might have past, but naval combat simulators still find an audience on mobile. Thunder Armada is Chinese developer Chyogames latest entry into the genre, drawing inspiration from the explosive exchanges... | Read more »
Experience a full 3D fantasy MMORPG, as...
Those hoping to sink their teeth into a meaty hack and slash RPG that encourages you to fight with others might want to check out EZFun’s new Eternity Guardians. Available to download for iOS and Android, Eternity Guardians is an MMORPG that lets... | Read more »
Warhammer Quest 2 (Games)
Warhammer Quest 2 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: Dungeon adventures in the Warhammer World are back! | Read more »

Price Scanner via MacPrices.net

Save $100 on 13″ MacBook Airs, prices start a...
Adorama has 2017 13″ MacBook Airs on sale today for $100 off MSRP including free shipping. Adorama charges NY & NJ sales tax only: – 13″ 1.8GHz/128GB MacBook Air (MQD32LL/A): $899, $100 off MSRP... Read more
1.4GHz Mac mini available for $399, $100 off...
TigerDirect has the 1.4GHz Mac mini on sale today for $399 including free shipping. Their price is $100 off MSRP, and it’s the lowest price available for this model. Although currently out of stock,... Read more
21″ 2.3GHz iMac on sale for $999, save $100
MacMall has the 21″ 2.3GHz iMac (MMQA2LL/A) on sale today for $999 including free shipping. Their price is $100 off MSRP, and it’s the lowest price available for this model. Read more
12″ iPad Pros on sale for $50 off MSRP, no ta...
Adorama has 12″ iPad Pros on sale today for $50 off MSRP. Shipping is free, and Adorama charges sales tax in NY & NJ only: – 12″ 64GB iPad Pro: $749, save $50 – 12″ 256GB iPad Pro: $899, save $50... Read more
9″ iPads on sale for $30 off, starting at $29...
MacMall has 9″ iPads on sale for $30 off including free shipping: – 9″ 32GB iPad: $299 – 9″ 128GB iPad: $399 Read more
Apple restocks full line of refurbished 13″ M...
Apple has restocked a full line of Apple Certified Refurbished 2017 13″ MacBook Pros for $200-$300 off MSRP. A standard Apple one-year warranty is included with each MacBook, and shipping is free.... Read more
13″ 3.1GHz/256GB MacBook Pro on sale for $167...
Amazon has the 2017 13″ 3.1GHz/256GB Space Gray MacBook Pro on sale today for $121 off MSRP including free shipping: – 13″ 3.1GHz/256GB Space Gray MacBook Pro (MPXV2LL/A): $1678 $121 off MSRP Keep an... Read more
13″ MacBook Pros on sale for up to $120 off M...
B&H Photo has 2017 13″ MacBook Pros in stock today and on sale for up to $120 off MSRP, each including free shipping plus NY & NJ sales tax only: – 13-inch 2.3GHz/128GB Space Gray MacBook... Read more
15″ MacBook Pros on sale for up to $200 off M...
B&H Photo has 15″ MacBook Pros on sale for up to $200 off MSRP. Shipping is free, and B&H charges sales tax in NY & NJ only: – 15″ 2.8GHz MacBook Pro Space Gray (MPTR2LL/A): $2249, $150... Read more
Roundup of Apple Certified Refurbished iMacs,...
Apple has a full line of Certified Refurbished 2017 21″ and 27″ iMacs available starting at $1019 and ranging up to $350 off original MSRP. Apple’s one-year warranty is standard, and shipping is free... Read more

Jobs Board

Engineering Manager, *Apple* Retail Enginee...
# Engineering Manager, Apple Retail Engineering Job Number: 58139948 Santa Clara Valley, California, United States Posted: 20-Oct-2017 Weekly Hours: 40.00 **Job Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Commerce Engineer, *Apple* Media Products -...
Commerce Engineer, Apple Media Products (New York City) Job Number: 113028813New York City, New York, United StatesPosted: Sep. 20, 2017Weekly Hours: 40.00 Job Read more
US- *Apple* Store Leader Program - Apple (Un...
US- Apple Store Leader Program Job Number: VariousUnited StatesPosted: Oct. 19, 2017Retail Store Job Summary Learn and grow as you explore the art of leadership at Read more
Product Manager - *Apple* Pay on the *Appl...
Job Summary Apple is looking for a talented product manager to drive the expansion of Apple Pay on the Apple Online Store. This position includes a unique Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.