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

calibre 5.0.1 - Complete e-book library...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
Compressor 4.4.8 - Adds power and flexib...
Compressor adds power and flexibility to Final Cut Pro X export. Customize output settings, work faster with distributed encoding, and tap into a comprehensive set of delivery features. Features:... Read more
Adobe Acrobat Reader 20.012.20048 - View...
Adobe Acrobat Reader allows users to view PDF documents. You may not know what a PDF file is, but you've probably come across one at some point. PDF files are used by companies and even the IRS to... Read more
Adobe Acrobat DC 20.012.20048 - 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
Box Sync 4.0.8009 - Online synchronizati...
Box Sync gives you a hard-drive in the Cloud for online storage. Note: You must first sign up to use Box. What if the files you need are on your laptop -- but you're on the road with your iPhone? No... Read more
Daylite 2020.36.1 - Dynamic business org...
Daylite helps businesses organize themselves with tools such as shared calendars, contacts, tasks, projects, notes, and more. Enable easy collaboration with features such as task and project... Read more
Catalina Cache Cleaner 15.0.6 - Clear ca...
Catalina Cache Cleaner is an award-winning general-purpose tool for macOS X. CCC makes system maintenance simple with an easy point-and-click interface to many macOS X functions. Novice and expert... Read more
Final Cut Pro X 10.4.10 - Professional v...
Final Cut Pro X is a professional video editing solution. Completely redesigned from the ground up, Final Cut Pro adds extraordinary speed, quality, and flexibility to every part of the post-... Read more
Civilization VI 1.3.4 - 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
iTubeDownloader 6.5.23 - Easily download...
iTubeDownloader is a powerful-yet-simple YouTube downloader for the masses. Because it contains a proprietary browser, you can browse YouTube like you normally would. When you see something you want... Read more

Latest Forum Discussions

See All

Undercrawl is a procedurally generated r...
Undercrawl is a roguelike dungeon crawler from indie developer Monster Shop Games. It's a genre that's popular in gaming in general but features even more frequently on mobile devices since the shorter, 'run' style of playthrough suits playing in... | 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 »
BTS Universe Story, the social game that...
Netmarble's highly anticipated social game, BTS Universe Story, is available now for iOS and Android. It's the second collaboration between the hugely successful mobile developer and the K-pop superstars following BTS World. [Read more] | Read more »
The 5 Best Mobile Games Like Hades
Supergiant Games finally released Hades upon the world this week, and we’re loving it. The game plays to all of the studio’s strengths while still retaining a strong sense of identity. It also just so happens to play rather well using the Steam... | Read more »
A Year of Apple Arcade: The Good, The Ba...
Apple Arcade has persisted for just over a year at this point, and although that means I've been busy ranking and re-ranking every game on the service for just about as long, I haven't done much reflection on the service as a whole. [Read more] | Read more »
Animal Restaurant anniversary event team...
Animal idle simulator Animal Restaurant is celebrating its first-year anniversary with a crossover event with popular YouTube series Aaron’s Animals. [Read more] | Read more »
Raziel: Dungeon Arena is a hack 'n...
Raziel: Dungeon Arena is available now on mobile and will appeal to fans of both comic books and old school dungeon crawlers. Not only will you hack 'n' slash your way through mobs of enemies but there's also fully-narrated animated comic to enjoy... | Read more »
Steam Link Spotlight - Hades
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 on Disco Elysium. Read about how it plays using Steam Link over here. | Read more »
Microsoft has acquired ZeniMax Media and...
In the latest of a series of blockbuster moves, Microsoft has now acquired Zenimax Media and its subsidiary, Bethesda Softworks, for $7.5 billion. [Read more] | Read more »
Infinity Mechs is an upcoming idle game...
Indie developer SkullStar studio has announced an upcoming idle mech game called Infinity Mechs. It draws inspiration from the mobile game Iron Saga and has been officially licensed by Game Duchy. It's set to launch for both iOS and Android on... | Read more »

Price Scanner via MacPrices.net

Clearance 8-core iMac Pro available for $3819...
Apple has Certified Refurbished, clearance, 27″ 3.2GHz 8-Core iMac Pros available $3819 including free shipping. Their price is $1180 off the original MSRP of new models. A standard Apple one-year... Read more
How The Upcoming Mac Transition To Apple Sili...
FEATURE: 09.25.20 – Apple’s plan to transition all of its desktop and notebook computers away from Intel processors to Apple silicon, chips designed by the company itself, has been eclipsed by the... Read more
New low price! Apple Watch SE for only $269
B&H Photo is reporting limited stock of Apple’s new Apple Watch SE GPS models for $10 off MSRP and including free shipping. Their $269 price for the 40mm model is the lowest price we’ve seen so... Read more
Lowest price anywhere: New 13″ 2.0GHz MacBook...
Amazon has new 2020 13″ 2.0GHz/512GB MacBook Pros with 10th generation Intel processors back in stock on sale today for $200 off Apple’s MSRP. Shipping is free. Be sure to purchase the MacBook Pro... Read more
Apple Pro Display XDR with Nano-Texture Glass...
Amazon Apple Premier Partner GatorTec has the Apple Pro Display XDR with Nano-Texture Glass on sale for $5599 shipped, on Amazon. Their price is $400 off Apple’s MSRP, and it’s the cheapest price... Read more
Get a 2019 13″ MacBook Air for only $779 toda...
Apple has clearance, Certified Refurbished, 2019 13″ 1.6GHz/128GB MacBook Airs available again for $779. Each MacBook features a new outer case, comes with a standard Apple one-year warranty, and is... Read more
2020 11″ iPad Pros on sale today for $50-$75...
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
Apple has restocked 2020 13″ MacBook Airs sta...
Apple has restocked Certified Refurbished 2020 13″ MacBook Airs starting at only $849 and up to $200 off the cost of new Airs. Each MacBook features a new outer case, comes with a standard Apple one-... Read more
Apple’s new 8th generation 10.2″ iPads are on...
Amazon is discounting new 2020 8th generation 10.2″ Apple iPads by up to $35 off MSRP with prices starting at only $299. Shipping is free. These are the same iPads sold by Apple in their retail and... Read more
Today on Woot: Apple refurbished 16″ MacBook...
Amazon-owned Woot has Apple refurbished 16″ MacBook Pros available today for up to $605 off the cost of new models. Shipping is free for Prime members: – 16″ 6-Core MacBook Pros: $1874.99 $525 off... Read more

Jobs Board

Freelance *Apple* Technology Journalist - V...
…freelance basis. Valnet Inc. is looking for journalists with strong knowledge of Apple technology for our website MakeUseOf.com MakeUseOf is one of the largest Read more
*Apple* Certified Macintosh Technician - Exc...
Apple Certified Macintosh Technician Summary Title: Apple Certified Macintosh Technician ID:350 Department:All Location:Bethesda, MD Description Apple Read more
Security Officer ($23.00/Hourly) - *Apple*...
**Security Officer \($23\.00/Hourly\) \- Apple Store** **Description** About NMS Built on a culture of safety and integrity, NMSdelivers award\-winning, integrated Read more
Security Officer ($23.00/Hourly) - *Apple*...
**Security Officer \($23\.00/Hourly\) \- Apple Store** **Description** About NMS Built on a culture of safety and integrity, NMSdelivers award\-winning, integrated Read more
*Apple* Certified Macintosh Technician - Exc...
Apple Certified Macintosh Technician Summary Title: Apple Certified Macintosh Technician ID:350 Department:All Location:Falls Church, VA Description Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.