TweetFollow Us on Twitter

The Road to Code: Quit Bugging Me

Volume Number: 25 (2009)
Issue Number: 02
Column Tag: The Road to Code

The Road to Code: Quit Bugging Me

Debugging on Mac OS X

by Dave Dribin

The Perfect Story

You've been a faithful reader of The Road to Code. You've typed in every code example to gain complete understanding. If you had problems getting something working, you could always download the source code from the MacTech web site. But now, you're off on your own. You've started writing your own applications, and inevitably, something goes wrong. Your application doesn't work as you expect it. Or it crashes, losing all your hard-entered data. How do you figure out what's going wrong?

The process of fixing an application that does not work correctly is called debugging. The term debugging in the context of computer software is often credited to an early computer scientist named Grace Hopper. As the legend goes, she was working on the Mark II system at Harvard, investigating a glitch in the system. The problem ended up being a moth stuck in one of the relays, and she dubbed this the "first actual case of bug being found". From then on, whenever a computer system has not worked, it is blamed on bugs. Thus, removing bugs in a computer program is called debugging.

That's a fine and dandy story, but how do you actually debug a program? It's not as if you will usually find real moths stuck inside your computer to eradicate. Let's go over some techniques that you can use to help you debug your own code.

Simple Logging

One of the simplest debugging techniques is to use logging. Say some variable is not being set correctly, and you do not know why. You can begin tracking down the problem by inserting NSLog statements to print its value out to the console. You can even use NSLog in a GUI application and then view the output in the Xcode console. This kind of debugging, while primitive, is surprisingly effective. It's also one of the easiest to implement, since printing to the console is one of the first things you learn how to do.

Sometimes this kind of debugging is called printf debugging, because in straight C you use the printf function instead of NSLog. Using NSLog in Objective-C has some nice advantages, but the main one is the %@ format pattern to print objects. You can use the %@ format string to print any Objective-C object. For example, to print an NSString variable:

    NSString * name = @"Joe";
    NSLog(@"name: %@", name);

You've seen code like this before. The %@ in the format string gets substituted with the string value:

2008-12-07 22:49:09.356 Rectangles[60874:10b] name: Joe

Along with the message you pass as its arguments, NSLog prints the application name and a timestamp. We've seen how you can use other format patterns, such as %d to print integers and %f to print floating point numbers. The %@ formatter works on any class, not just NSString though. Here's how you could print an NSNumber instance:

    NSNumber * number = [NSNumber numberWithInt:42];
    NSLog(@"number: %@", number);

The output would look like this:

number: 42

I'm omitting the timestamp and application name from now on, to keep the output succinct, but there's nothing too surprising going on here. You get useful output on a surprising number of classes included in Foundation and AppKit. For example, logging an NSArray instance displays the contents of the array:

    NSArray * array =
       [NSArray arrayWithObjects:@"Jack", @"Jane", nil];
    NSLog(@"array: %@", array);

The output would look like this:

array: (
    Jack,
    Jane
)

You can even use %@ on your own objects; however, it doesn't always provide useful information. Let's use the Rectangles application from the October 2008 issue, as an example. You can get it from the MacTech FTP site, if you need to. Here's how you could log one of our Rectangle objects:

    Rectangle * rectangle =
        [[Rectangle alloc]   initWithLeftX:0
                                 bottomY:0
                                  rightX:15
                                    topY:10];
    NSLog(@"rectangle: %@", rectangle);

And here's what it looks like by default:

rectangle: <Rectangle: 0x104fba0>

Hrm... not very useful. It's printing out the pointer value of the object. Luckily, we can customize this output. In order to turn an object into a string, NSLog uses the description method defined on NSObject. The default implementation prints the class name and the pointer value of self, and it would look something like this:

- (NSString *)description
{
    NSString * description =
        [NSString stringWithFormat:@"<%@: %p>", [self className], self];
    return description;
}

It's using the className method, also defined on NSObject, to get the name of the class as a string. The %p format specifier prints out pointer values. The output is the hexadecimal number of the memory address of the pointer.

By overriding description in our Rectangle class, we can give it more meaningful output. As a refresher, Listing 1 shows the interface for the Rectangle class.

Listing 1: Rectangle.h header file

#import <Foundation/Foundation.h>
@interface Rectangle : NSObject <NSCoding>
{
    float _leftX;
    float _bottomY;
    float _width;
    float _height;
}
@property float leftX;
@property float bottomY;
@property float width;
@property float height;
@property (readonly) float area;
@property (readonly) float perimeter;
- (id) initWithLeftX:(float)leftX
            bottomY:(float)bottomY
             rightX:(float)rightX
               topY:(float)topY;
@end

A useful implementation of description would output the leftX, bottomY, width, and height:

- (NSString *)description
{
    NSString * description =
        [NSString stringWithFormat:
         @"<%@: (%.1f, %.1f) %.1f x %.1f>",
         [self className],
         _leftX, _bottomY, _width, _height];
    return description;
}

Now, when we log our rectangle variable above, it would look like this:

rectangle: <Rectangle: (0.0, 0.0) 15.0 x 10.0>

Ah! Much better. We can now use logging to dump our rectangles out to the console.

There are, of course, downsides to using logging for debugging. If you need to view a large number of variables or watch a variable change from line to line, you'll need to insert lots of logging statements. This is tedious and takes time. You also may accidentally leave your debug logging statements inside a shipping application. This can clutter up the user's console and even affect the performance of your application, so it is not a good idea to have NSLog statements in a released application. Don't fret. There's something better called a debugger.

Xcode Debugger

A debugger is an application whose purpose is to help debug another application. The main feature of a debugger is the ability to set breakpoints. Breakpoints allow you to stop the execution of a running application and investigate the state of the application. You can typically examine any local variables, instance variables, and global variables from within a debugger.

Xcode comes with an integrated debugger, meaning that you can use Xcode to set breakpoints. The simplest way to explain how it works is to provide an example. Take this code that is the constructor for the Rectangle class:

- (id) initWithLeftX:(float)leftX
            bottomY:(float)bottomY
             rightX:(float)rightX
               topY:(float)topY
{
    self = [super init];
    if (self == nil)
        return nil;
    
    _leftX = leftX;
    _bottomY = bottomY;
    _width = rightX - leftX;
    _height = topY - bottomY;
    
    return self;
}

Say we want to see if the rectangle is created properly. You simply click on the left margin with the line numbers, and a blue arrow is added to indicate that you added a breakpoint, as in Figure 1.


Figure 1: Setting a breakpoint in Xcode

To trigger the breakpoint, we first need to run our application with the debugger instead of running it normally. You do this by choosing the Build > Build and Debug menu, or Command-Y, instead of the usual Build > Build and Run menu, or Command-R. This will launch your application, but whenever code that has a breakpoint set is executed, the program stops execution. In order to trigger our example breakpoint, you need to hit the Add button in the user interface, which ends up creating a new rectangle.

When you do this, the debugger will stop your program, and Xcode will change the main editor interface to show you that you have stopped at a breakpoint, as shown in Figure 2. You can see that it adds a red arrow in the left margin and it highlights the line that the red arrow is on in blue. This is done to clearly indicate where the debugger has halted your application.Debugger integration within the standard editing window is new to Xcode 3. There is also a separate debugging window, which contains more debugging information. You can view this window by choosing the Run > Debugger menu, which looks like Figure 3.


Figure 2: Xcode stopped at a breakpoint


Figure 3: Xcode debugger window

While your program is halted, you can poke around and investigate its state. The lower pane of the debugger window is similar to the main editor window in that it shows you your code, along with the red arrow and blue highlight to indicate where you application stopped. The upper left pane of the debugger window shows the method call stack, or stack trace. This shows all method calls that lead you to this point, all the way back to main function that started the application.

The upper right pane of the debugger window allows you to examine the value of any variable. Variables that contain other variables, such as structures or objects, have a little disclosure triangle. Clicking on the triangle displays the containing variables. For example, clicking on the triangle of the self variable allows us to examine the instance variables, as shown in Figure 4. As you can see, all instance variables are set to zero initially.


Figure 4: Examining variables

Examining variables is extremely helpful when trying to figure out how your application is misbehaving, but there is a lot more you can do with a debugger. Most likely, you are going to want to start your application up again. There are two ways to resume execution of your program: continuing and stepping. Continuing starts the application back up again, and it will resume as if nothing happened.

Stepping allows you to continue execution temporarily and execute the statements of your program one at a time. By stepping through your program, you can watch how each statement modifies variables. Say we step over the four statements that assign the instance variables. The debugger window would now look like Figure 5.


Figure 5: Stepping over statements

You will notice that the red arrow and blue highlight move as each statement is executed. Also the variable examination pane highlights variables that have changed in red. Thus, the _height instance variable is red because we just assigned to it. You can keep stepping as long as you would like and watch your application execute in slow motion.

There are actually two kinds of stepping: step into and step over. Both continue execution temporarily, but the difference is how the debugger handles method and function calls. Using "step into" the debugger will follow execution into method and function calls and stop at the first statement inside the called method. "Step over" will not follow execution into method and functions calls, and instead will stop on the next line of the current method. If the statement is not a method or function call, then "step into" and "step over" are equivalent.

The toolbar of the debugger window has buttons labeled Continue, Step Over, and Step Into so you can easily restart your application using one of these commands. It's worth noting that you can only step into your own code. Any code that is part of the system frameworks cannot be stepped into.

The upper left pane that shows the method call stack allows you to "back up" the method call chain and view the state of each method, too. By default, the top of the stack is selected. For example, if I click on the second method in the stack, -[MyDocument addRectangle:], the debug window would change to look like Figure 6. Again, you can only select method calls that are in your code. You cannot select methods that are part of the system frameworks, and they are grayed out.


Figure 6: Selected methods in the call stack

The source pane and the variable pane update to the new method when you click on one. Thus, the variable arguments and locals shown in Figure 6 are for the addRectangle: method.

Debugger Console

There's another important part of Xcode's debugger called the debugger console. This is the same console where logging output usually goes, but with the debugger active, it's turned into a command line interface for the debugger.

Xcode's debugger is built around a debugger called gdb, the GNU debugger. gdb is traditionally run from the command line to debug C and C++ programs. Xcode wraps most of gdb's functionality with an easy to use GUI, but the console allows you to enter commands directly to gdb. The print command, for example, prints the value of a variable. You can use this instead of the variable pane in the GUI to examine variables. You can examine the rightX local variable from the console like this:

(gdb) print rightX
$4 = 15

The print command only works on primitive types, though. If you want to examine Objective-C objects, you need to use the print-object command. It uses the same description method that NSLog uses to get the string value of an object, so if we used this on self at the end of the Rectangle constructor, the output would look like this:

(gdb) print-object self
<Rectangle: (0.0, 0.0) 15.0 x 10.0>

You can also use po as a shortcut for print-object:

(gdb) po self
<Rectangle: (0.0, 0.0) 15.0 x 10.0>

The arguments to the print and po commands are not limited to printing variables. You can traverse structures and even call Objective-C methods. To call methods, you use the standard square bracket syntax you normally use. Here's how you would print the class name of self:

(gdb) po [self className]
Rectangle

Note that the debugger does not understand property dot notation. You have to use the square bracket syntax:

(gdb) print self.area
There is no member named area.
(gdb) print [self area]
$6 = 150

Generally print is smart enough to figure out the type to print out, but sometimes you need to give it a hint. If you need to do this, you can use standard C cast syntax to force the value to be a certain type:

(gdb) print (int)[self area]
$7 = 150

The command line is powerful, yet complex. Everything available in the Xcode debugger GUI is available from the gdb console. For example, the commands that correspond to the Continue, Step Over, and Step Into of the GUI are continue, next, and step, respectively.

Breakpoint Actions

One use for gdb commands is to assign actions to breakpoints. Instead of breakpoints stopping the program and not continuing right away, you can add actions to breakpoints. These actions are executed automatically when the breakpoint is triggered. Actions can be gdb commands, log statements, or even AppleScripts.

Xcode has some pre-configured breakpoint actions that you can use when setting a breakpoint. The easiest way to access these is to right click (or Control click) in the left margin of the source editor, and choose Built-in Breakpoints from the contextual menu, as shown in Figure 7.


Figure 7: Built-in Breakpoints

Let's choose Print self and auto-continue to see how it works. Now, when we run our program and cause the breakpoint to trigger, it will log the description of the Rectangle and continue. This is just like adding an NSLog statement, except you do not have to modify your code!

Let's see how this works behind the scenes. Right click on the blue breakpoint arrow in the left margin and choose Edit Breakpoint from the contextual menu. This will bring up the Breakpoints window as shown in Figure 8. Alternatively, you can access this window from the Breakpoints toolbar item or the Run > Show > Breakpoints menu.


Figure 8: Breakpoint with an action

This window shows that the po self debugger command is automatically executed when the breakpoint is hit. Also, the rightmost column is checked, meaning the application is automatically continued. This combination is how you can add logging statements without modifying the code. Again, you can use any gdb command you can type at the console as an action, so breakpoint actions are very powerful.

Debugging Crashes

While breakpoints are a great way to track down bugs, sometimes your application will unexpectedly crash, and you don't know where to start looking. The debugger can help in these cases, too. Crashes can happen for many reasons, but one of the most common is dereferencing a NULL pointer. In order to demonstrate this point, I'm going to make the program crash by inserting a bug.

- (IBAction)addRectange:(id)sender
{
    // Dereference a NULL pointer
    int * pointer = NULL;
    *pointer = 0;
    Rectangle * rectangle =
        [[Rectangle alloc]   initWithLeftX:0
                                 bottomY:0
                                  rightX:15
                                    topY:10];
    
    [_rectangles addObject:rectangle];
    
    // Update the UI
    [_tableView reloadData];
    [self updateTotalAreaAndPerimeter];
}

The second line in the method will cause the application to crash when executed. To trigger this bug, all you have to do is click on the Add button in the GUI. If you try this, you'll notice that Xcode will catch the crash and automatically attach the debugger. The debugger will then highlight the exact line where the crash occurred, with the red arrow and blue highlight, as shown in Figure 9. You can now examine the state of your application to figure out why the crash occurred. In this case, it's obvious: pointer is 0x0, or NULL. However, it may not be obvious in a real crash. The debugger gives you a chance to investigate what caused the crash.


Figure 9: Xcode debugger catching a crash

The debugger will only get attached to a crashing program when running within Xcode. If a program crashes outside of Xcode, you will get a standard dialog, as shown in Figure 10.


Figure 10: Application crash dialog

There's not much information here to help you debug. However, ever time any application crashes, Mac OS X saves what's called a crash log. Crash logs contain information that may help determine why an application crashed, and they are stored in this directory:

~/Library/Logs/CrashReporter/

There's one text file per crash, and if you open up one of these files, you'll see the full crash log. The most useful part of the crash log is what's known as a stack trace. This is similar to the upper left pane of the debugger, in that it shows you the methods called in the current stack before the crash occured. The first line of the crash log (which I've edited for brevity) will look something like this:

Thread 0 Crashed:
0  [...] -[MyDocument addRectange:] + 29 (MyDocument.m:64)
1  [...] -[NSApplication sendAction:to:from:] + 112
[...]
11 [...] 0x00002679 main + 30 (main.m:14)
12 [...] start + 54

It tells us exactly which line our program crashed on. While your program has already exited, and you can't examine it for more information, the crash log is often very helpful in determining why your application crashed for a user.

You can actually configure the crash window to give you crash logs, as well, using the CrashReporterPrefs application, found in this directory:

/Developer/Applications/Utilities/

CrashReporterPrefs allows you to configure Crash Reporter for Developer mode. When in Developer mode, you can view crash logs right in the GUI when the application crashes, as shown in Figure 11. However, you get this crash dialog whenever any application crashes, not just ones you've written, so you may find this behavior too annoying to leave Developer mode on by default.


Figure 11: Crash dialog in Developer mode

Xcode Configurations

Take a look in the Xcode toolbar, and you'll notice the Overview item in the toolbar on the left hand side. It should say something like 10.5 | Debug | i386. To know what this means, click on the toolbar item, and you'll see a menu similar to Figure 12. You'll see that 10.5 corresponds to the Active SDK, Debug corresponds to the Active Configuration, and i386 corresponds to the Active Architecture. A full description of all these items is beyond the scope of this article, but we're going to briefly talk about the Active Configuration and Active Architecture settings.


Figure 12: Xcode overview menu

By default, Xcode uses the Debug configuration. You'll notice there is also a Release configuration. This is because your application needs to be compiled in a special manner to work properly with the debugger. When you compile with the Release configuration, your application will be faster and use less disk space, so you will want to use it before distributing your application to your users. However, it may not work well with the debugger. You may have trouble setting breakpoints, and the stack traces included when your application crashes may not be as accurate. For example, it may not be able to tell you exactly which line your application crashed on.

Also, in the Debug configuration, your application is only compiled for the Active Architecture, which speeds up compile times. In contrast, the Release configuration will compile your application as a Universal binary and will run natively on both PowerPC and Intel Macs. However, this means your code is compiled twice and can slow down development time.

The Active Architecture is the architecture of your current hardware: if you are running on an Intel Mac, the architecture will be set to i386. If you're on a PowerPC Mac, the architecture will be set to ppc. This means that your application will only run natively on the same type of Mac that you have.

Because of the differences between Debug and Release configurations, you will typically use Debug while developing your application and Release to build the final version you ship to your users. Just be aware that you will not be able to accurately debug that final version or get accurate stack traces if your application crashes.

Conclusion

This article has given you a brief overview of how to use logging and Xcode's integrated debugger to help you diagnose and fix errors in your programs. Debugging is an art in and of itself, and just like programming, the more you do it, the better you get at it. There are lots of resources on the Internet about debugging in general as well as specifics on how to use gdb and the Xcode debugger. For instance, Apple has a good article called "Technical Note TN2124: Mac OS X Debugging Magic" on their website. This article is chock full of tips and tricks of debugging techniques for Mac OS X and a worthy read.

Bibliography

"Technical Note TN2124" http://developer.apple.com/technotes/tn2004/tn2124.html


Dave Dribin has been writing professional software for over eleven years. After five years programming embedded C in the telecom industry and a brief stint riding the Internet bubble, he decided to venture out on his own. Since 2001, he has been providing independent consulting services, and in 2006, he founded Bit Maki, Inc. Find out more at http://www.bitmaki.com/ and http://www.dribin.org/dave/.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Tidy Up 5.3.7 - 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
Pinegrow 5.97 - Mockup and design web pa...
Pinegrow (was Pinegrow Web Designer) is desktop app that lets you mockup and design webpages faster with multi-page editing, CSS and LESS styling, and smart components for Bootstrap, Foundation,... Read more
BlueStacks 4.210.0 - Run Android applica...
BlueStacks App Player lets you run your Android apps fast and fullscreen on your Mac. Feature comparison chart How to install Bluestacks on your Mac Go to MacUpdate and click the green "Download"... Read more
WhatsApp 2.2027.10 - Desktop client for...
WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more
Art Text 4.0.1 - $29.99
Art Text is graphic design software specifically tuned for lettering, typography, text mockups and various artistic text effects. Supplied with a great variety of ready to use styles and materials,... Read more
Adobe Dreamweaver CC 2020 20.2 - Build w...
Dreamweaver CC 2020 is available as part of Adobe Creative Cloud for as little as $20.99/month (or $9.99/month if you're a previous Dreamweaver customer). Adobe Dreamweaver CC 2020 allows you to... Read more
Adobe Acrobat DC 20.009.20074 - Powerful...
Acrobat DC is available only as a part of Adobe Creative Cloud, and can only be installed and/or updated through Adobe's Creative Cloud app. Adobe Acrobat DC with Adobe Document Cloud services is... Read more
beaTunes 5.2.10 - Organize your music co...
beaTunes is a full-featured music player and organizational tool for music collections. How well organized is your music library? Are your artists always spelled the same way? Any R.E.M. vs REM?... Read more
DiskCatalogMaker 8.1.5 - Catalog your di...
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
Meteorologist 3.4.1 - Popular weather ap...
Meteorologist is a simple interface to weather provided by weather.com. It provides the ability to show the weather in the main menu bar, displaying more detail in a pop-up menu, whose contents are... Read more

Latest Forum Discussions

See All

Steam Link Spotlight - Disco Elysium
Steam Link Spotlight is a feature where we look at PC games that play exceptionally well using the Steam Link app. Our last entry was Signs of the Sojourner Read about how it plays using Steam Link over here. | 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 »
Pokemon Go's July Community Day wil...
Pokemon Go developers have announced the details concerning the upcoming Gastly Community Day. This particular event was selected by the players of the game after the Gas Pokemon came in second place after a poll that decided which Pokemon would... | Read more »
Clash Royale: The Road to Legendary Aren...
Supercell recently celebrated its 10th anniversary and their best title, Clash Royale, is as good as it's ever been. Even for lapsed players, returning to the game is as easy as can be. If you want to join us in picking the game back up, we've put... | Read more »
Detective Di is a point-and-click murder...
Detective Di is a point-and-click murder mystery set in Tang Dynasty-era China. You'll take on the role of China's best-known investigator, Di Renjie, as he solves a series of grisly murders that will ultimately lead him on a collision course with... | Read more »
Dissidia Final Fantasy Opera Omnia is se...
Dissidia Final Fantasy Opera Omnia, one of Square Enix's many popular mobile RPGs, has announced a plethora of in-game events that are set to take place over the summer. This will include several rewards, Free Multi Draws and more. [Read more] | Read more »
Sphaze is a neat-looking puzzler where y...
Sphaze is a neat-looking puzzler where you'll work to guide robots through increasingly elaborate mazes. It's set in a visually distinct world that's equal parts fantasy and sci-fi, and it's finally launched today for iOS and Android devices. [... | Read more »
Apple Arcade is in trouble
Yesterday, Bloomberg reported that Apple is disappointed in the performance of Apple Arcade and will be shifting their approach to the service by focusing on games that can retain subscribers and canceling other upcoming releases that don't fit... | Read more »
Pixel Petz, an inventive platform for de...
Pixel Petz has built up a sizeable player base thanks to its layered, easy-to-understand creative tools and friendly social experience. It revolves around designing, trading, and playing with a unique collection of pixel art pets, and it's out now... | Read more »
The King of Fighters Allstar's late...
The King of Fighters ALLSTAR, Netmarble's popular action RPG, has once again been updated with a plethora of new content. This includes battle cards, events and 21 new fighters, which increases the already sizeable roster even more. [Read more] | Read more »

Price Scanner via MacPrices.net

$219 Apple AirPods Pro are back at Verizon, s...
Verizon has Apple AirPods Pro on sale again for a limited time for $219.99 on their online store. Their price is $30 off Apple’s MSRP, and it’s the lowest price we’ve seen for AirPods Pro. Available... Read more
Apple’s $779 13″ MacBook Air deal returns to...
Apple has clearance, Certified Refurbished, 2019 13″ MacBook Airs available again starting at $779. Each MacBook features a new outer case, comes with a standard Apple one-year warranty, and is... Read more
$200 13″ MacBook Pro discounts are back at Am...
Amazon has 2020 13″ 2.0GHz MacBook Pros on sale again today for $150-$200 off Apple’s MSRP. Shipping is free. Be sure to purchase the MacBook Pro from Amazon, rather than a third-party seller, and... Read more
Deal Alert! Apple AirPods with Wireless Charg...
Sams Club has Apple AirPods with Wireless Charging Case on sale on their online store for only $149.98 from July 6, 2020 to July 9, 2020. Their price is $50 off Apple’s MSRP, and it’s the lowest... Read more
Xfinity Mobile promo: Apple iPhone XS models...
Take $300 off the purchase of any Apple iPhone XS model at Xfinity Mobile while supplies last. Service plan required: – 64GB iPhone XS: $599.99 save $300 – 256GB iPhone XS: $749.99 save $300 – 512GB... Read more
New July 2020 promo at US Cellular: Switch an...
US Cellular has introduced a new July 2020 deal offering free 64GB Apple iPhone 11 smartphones to customers opening a new line of service. No trade-in required, and discounts are applied via monthly... Read more
Apple offers up to $400 Education discount on...
Apple has launched their Back to School promotion for 2020. They will include one free pair Apple AirPods (with charging case) with the purchase of a MacBook Air, MacBook Pro, iMac, or iMac Pro (Mac... Read more
July 4th Sale: Woot offers wide range of Macs...
Amazon-owned Woot is blowing out a wide range of Apple Macs and iPads for July 4th staring at $279 and ranging up to just over $1000. Models vary from older iPads and 11″ MacBook Airs to some newer... Read more
Apple Pro Display XDR with Nano-Texture Glass...
Abt Electronics has Apple’s new 32″ Pro Display XDR model with the nano-texture glass in stock and on sale today for up to $144 off MSRP. Shipping is free: – Pro Display XDR (nano-texture glass): $... Read more
New 2020 Mac mini on sale for up to $100 off...
Amazon has Apple’s new 2020 Mac minis on sale today for $40-$100 off MSRP with prices starting at $759. Shipping is free: – 2020 4-Core Mac mini: $759 $40 off MSRP – 2020 6-Core Mac mini: $998.99 $... Read more

Jobs Board

Physical Therapist Assistant - *Apple* Hill...
Physical Therapist Assistant - Apple Hill Rehab - Full Time Tracking Code 62519 Job Description General Summary: Under the direct supervision of a licensed Physical Read more
Operating Room Assistant, *Apple* Hill Surg...
Operating Room Assistant, Apple Hill Surgical Center - Full Time, Day Shift, Monday - Saturday availability required Tracking Code 62363 Job Description Operating Read more
Perioperative RN - ( *Apple* Hill Surgical C...
Perioperative RN - ( Apple Hill Surgical Center) Tracking Code 60593 Job Description Monday - Friday - Full Time Days Possible Saturdays General Summary: Under the Read more
Product Manager, *Apple* Commercial Sales -...
Product Manager, Apple Commercial Sales Austin, TX, US Requisition Number:77652 As an Apple Product Manager for the Commercial Sales team at Insight, you Read more
*Apple* Mac Product Engineer - Barclays (Uni...
Apple Mac EngineerWhippany, NJ Support the development and delivery of solutions, products, and capabilities into the Barclays environment working across technical Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.