TweetFollow Us on Twitter

March 96 - Using C++ Exceptions in C

Using C++ Exceptions in C

Avi Rappoport

Exception handling in C++ offers many advantages over error handling in C. Using the techniques outlined here, you can implement C++ exceptions in your C code without a lot of effort. The payback is streamlined debugging that can result in more error-free code. When your program encounters errors, it jumps to the appropriate error-handling section, rather than dealing with the error locally. This simplifies your design and helps you concentrate on the normal flow of control. Centralized error handling also makes it easier to improve your reporting and feedback mechanisms incrementally.

I wrote a few little XCMDs in C and after the fifteenth crash of the day, I decided that I'd better add some error handling. So I looked at Dartmouth XCMDs, but I wasn't impressed. Each check for an error meant another indentation in the code, and I was worried about disposing of handles correctly as I passed errors up the call chain. Since I'd been looking at a lot of C++ lately, I wondered whether I couldn't use part of the C++ exception-handling mechanism to avoid problems in my code. It worked pretty well, so I thought I'd share my results.

For part of my solution, I used some Metrowerks macros. Metrowerks has graciously allowed these helpful exception and debugging source, header, and resource files to be included on this issue's CD, so you can use them without purchasing its CodeWarrior CD. The files contain macros that provide convenient tools for implementing exceptions and debugging signals, as well as an alert resource that can provide information during debugging.

Although I've used C++ exception handling in my C code with great results, I'd like to offer you one word of caution before you use them. Realize that C++ is not strictly an extension of C; as a result, in some cases it's possible that the program may not behave as you think it should.

BASIC ERROR-HANDLING REQUIREMENTS

All programs must respond to system and subroutine failures somehow. For example, many Macintosh Toolbox routines return a variable of type OSErr, while others require that you call Toolbox routines (such as MemError and ResError) to retrieve the error. If you ignore system and subroutine failures, your program is practically guaranteed to crash.

Good error handling allows you to cope with many kinds of problems. Your checks can trigger other code that deals with the problem (for example, by freeing memory). During debugging, error checking should notify you that something has gone wrong. And since you can't, unfortunately, catch all the bugs during testing, you must also set up an error-reporting mechanism to notify your users when something has gone wrong. In the worst case, your error handling should at least ensure that your program exits gracefully, without losing or corrupting user data.

THROWING EXCEPTIONS

The American National Standards Institute (ANSI) has defined a mechanism for C++ compilers that allows code to "throw" exceptions. When the compiler encounters a throw statement, it jumps to the nearest catch statement. (The "nearest" catch statement is the one associated with the current try statement, whether it's in the current routine or farther up the call chain.) The catch statement can deal with the error, pass it up the call chain, or both. A throw statement should appear only within a try or catch statement or in code called from within a try statement. Listing 1 shows these basic components.

Listing 1. Throwing exceptions

OSErr theErr = noErr;

// Try block.
try {
   // Do something.
   ...
   // If error, throw an exception.
   if (theErr != noErr)
      throw (theErr);
}
// Catch blocks.
catch (OSErr theErr) {
   // Do something with the error.
   ...
}
catch (...) {
   // Catch anything else.
   ...
}
As shown in Listing 1, exceptions are dealt with in catch blocks, which take an appropriate action depending on the error. For serious errors, this means cleaning up and terminating the program. For less serious errors, the catch block could continue without making a fuss, or make changes based on the error and again call the routine that threw the error; sometimes you might want to throw a more generic error, which is caught and interpreted in a higher-level catch block. I also recommend using the Metrowerks signal macros (described later) within your catch blocks to help you locate errors during debugging.
    The three dots in catch (...) are actually in the code; the other such dots that appear in these listings are ellipses representing code that isn't shown.*
When carefully designed, C++ exception handling in your program can deal with problems at an appropriate level. As you may already have guessed, this feature is both powerful and dangerous. The advantage is that you don't have to mess around with returning errors for every routine or indenting deeply. However, if you allocate memory, you must be careful to dispose of it at the right time or it will cause a leak.

ADDING C++ EXCEPTIONS TO YOUR CODE

To add C++ exceptions to your code, you must do the following:
  • Force the use of the C++ compiler.
  • Create a top-level exception handler in your main routine.
  • Define try blocks and catch blocks, and call throw at appropriate times.
  • Add the C++ library (CPlusPlus.lib, CPlusPlusA4.lib, or MWCRuntime.Lib) to your project.
The Metrowerks macros that you'll see in the code that follows make implementing exception handling much easier than it would be otherwise. I'll talk about them later.

USING C++

To use C++ exceptions, you have to force the use of the C++ compiler. In Metrowerks CodeWarrior, the easiest way is to select the Activate C++ Compiler checkbox in the C/C++ Language panel. You should also make sure that the Enable C++ Exceptions checkbox is selected, because it enables throwing exceptions rather than direct destruction (one of those weird C++ things). An alternative way to invoke the compiler is to change the extension on your source code files to ".cp" or by changing the Target panel preferences; however, the checkbox method is the easiest.

C++ is stricter about automatic parameter conversion than C, so selecting the MPW Pointer Type Rules checkbox in the C/C++ Language panel avoids a bunch of errors (it forces the compiler to allow some implicit char* casts). But you'll get errors for other parameters and return values, so you have to clean them up as indicated by the compiler. For example, the following is an error message returned by a C++ compiler:

HC2RTF.c line 224  textLen = strlen(textString);
Error   : cannot convert
'unsigned char *' to
'char *'
To fix this problem, you can change the code to
textLen = strlen((char *) textString)
The CodeWarrior C++ compiler puts special C++ information into function names (this is called name mangling). C doesn't do this, so header files for C functions should be surrounded by #extern "C" statements to tell the compiler not to mangle these names (see Listing 2). The Macintosh Toolbox header files take care of this already.

Listing 2. Preventing name mangling

#ifdef __cplusplus
extern "C" {
#endif

long   FindBreak(char* buffer, short len);
// More declarations here
...

#ifdef __cplusplus
}
#endif

CREATING A TOP-LEVEL EXCEPTION HANDLER IN MAIN

In your main loop or function, you should specify the top-level exception handler. This should catch serious errors, report them, and exit gracefully. Listing 3 shows the simplest possible exception handler (which you'll understand better as you read on).

Listing 3. Simple top-level exception handler

pascal void main(XCmdPtr paramPtr)
{
   long   oldA4 = SetCurrentA4();

   try {
      CreateFile(paramPtr);
      WriteFile(paramPtr);
   }
   catch (...) {
      ReportError("\pSerious error occurred.")
      // XCMDs do not have to use ExitToShell. 
   }
   SetA4(oldA4);
}

DEFINING TRY BLOCKS

When you use a try statement, it tells the compiler that the following code might have exceptions thrown in it. All functions that throw exceptions must be within a try block, either in the current function or in a calling function. It's pretty easy to set up try blocks before catch blocks. This is good, because you do have to do it: any throws that aren't caught will automatically abort the program.

DEFINING CATCH BLOCKS

You should have catch blocks for each error type. So, for example, you might define catch (OSErr theErr), catch (errStruct errRecord), and catch (Str255 theErr). You should also have a generic catch, catch (...), which doesn't have any parameters, to catch exceptions of all other types. Although it's better to use typed catches that handle specific errors, always add at least one generic catch and have it signal an error with an alert or break to the debugger. This will help you catch exception mistakes during your debugging and testing phase. Listing 4 shows examples of these types of catch blocks.

Listing 4. Specific and generic catch blocks

catch (StringPtr errString) {
   // If HandleError throws, it will be caught above this catch. 
   HandleError(errString);
}
catch (OSErr theErr) {
   Str255 errString;
   ConvertErrToString(theErr, errString);
   ReportError(errString);
   throw (theErr);   // Rethrow to handle error.
}
// Forces the application to quit after the message.
catch (...) {
   SignalPStr_("\pUntyped error occurred in prefs.") 
   ExitToShell();
}
The compiler automatically routes the error to the appropriate catch statement, depending on the parameter passed to the throw statement. In Listing 4, both the StringPtr and OSErr types are caught specifically, after which they're reported. The OSErr catch rethrows the error as well. Any other types of errors are caught by the generic catch, which calls a signal macro to display a message and then exits the program.

You can, and often should, continue after catching an error. For example, after a disk full error, you should allow the user to choose a different volume. Note that the program will continue after the catch block, rather than in the location where the exception was thrown.

MOVING DEEPER -- HANDLING EXCEPTIONS IN THE CALL CHAIN

Many of your low-level routines may call the Macintosh Toolbox or otherwise interact with the Mac OS. They should throw an exception if there's an error, as shown in Listing 5.

Listing 5. Throwing exceptions for Macintosh Toolbox errors

void MakeMyResFile(Str32 fileName)
{
   CreateResFile(fileName); 
   // Could also use the Metrowerks ThrowIfResError_ macro.
   err = ResError();
   if (err <> noErr)
      throw (err);
   // Continue with execution.
   ...
}

// Call the function.
MakeThisFile()
{
   ...
   try {
      MakeMyResFile(thisFile);
   }
   catch (OSErr theErr) {
      if (theErr == dupFNErr) {
         // Do something; file already exists.
         ...
      }
      else 
         throw (theErr);   // Rethrow the error.
   }   // End catch statement.
   ...
}
So where do you catch these exceptions? Remember, they percolate up the call chain until they find a catch statement, so you don't have to take care of them in the immediate calling function (unless you've allocated memory or done other things that need undoing). When you catch them, you can, and sometimes should, throw the error again. You can either report errors in mid-level routines or rethrow them up to a higher-level error reporting mechanism.

In addition to these catch statements, be sure to add a catch statement in circumstances where you need to do any of the following:

  • Dispose of handles and otherwise deallocate memory.
  • Shut down something you started in the try block, such as opening a file.
  • Change the error thrown.
For your own functions, you should throw errors in situations that can cause serious problems or crash the machine. For instance, if you're providing a function that accesses a variable-length array that contains 16 members and the caller asks for the 17th member, you can throw a range error. There's no hard-and-fast rule about when to put the error checking into a function and when to require it before calling -- it depends on the situation. For example, if you're calling a function inside a tight graphics loop only and you want speed, you can probably check the parameters sufficiently in the calling function. However, if you have a utility routine that's called from several sections of your code, adding error checking will help you remember its requirements, such as parameters, memory, and other system states, to avoid problems later on.

Handling exceptions in libraries is tricky because you don't know much about the calling program. Think carefully about what you should report to the user and what you should simply return to calling functions.

As your programs become more sophisticated, you can start working around certain errors -- for example, by using temporary memory when the application's heap is full. You'll also need to design interactive error reporting, allowing your users to take action (such as unlocking a locked disk) when they can. Then your application can continue properly.

EXCEPTIONS AND DEBUGGING WITH THE METROWERKS MACROS

The Metrowerks PowerPlant UDebugging and UException files, included on this issue's CD, provide convenient tools for throwing common exceptions and alerting you during debugging. To use them, put the folder in your project folder, add the sources and the "PP DebugAlerts.rsrc" resource file to your project, and include the headers in your source files.

The UException.h file includes macros that automate common exception conditions. The UException.cp file includes an abort function. The UDebugging.h file defines some macros that make locating problems easier by allowing you to specify a signal, a debugging string displayed when the macro is invoked.

    If your project includes an ANSI library you don't need to add UException.cp. The abort function will conflict.*

SETTING GLOBAL VARIABLES FOR DEBUGGING

You need to set the global variables gDebugThrow and gDebugSignal in UDebugging.h to specify the debugging actions for throws and signals. By default, they're set to do nothing at all. Other options include displaying a dialog, dropping into the source-level debugger, or dropping into the low-level debugger. To activate the macros, be sure to define Debug_Signal in your precompiled header or UDebugging.h.

The following are the global variable options:

  • debugAction_Nothing -- Do nothing.
  • debugAction_Alert -- Display an alert box with an exception code (described later), filename, and line number where the throw or signal was made. For this to work, you must include the file "PP DebugAlerts.rsrc" in your project.
  • debugAction_SourceDebugger -- Break into the source-level debugger. For the Metrowerks source-level debugger, execution will stop with the arrow pointing to the line containing the throw statement. The exception code isn't displayed. You can check the display of variable values in the source-level debugger for that information. (I've tested this with the Metrowerks debugger only.) If you aren't running under the source-level debugger, debugAction_SourceDebugger will break into the low-level debugger on PowerPC processor-based machines, but might crash on 680x0 systems.
  • debugAction_LowLevelDebugger -- Break into MacsBug and display the exception code as a string. In MacsBug, the console will display two lines:
User Break at routine + offset
exception code 
  • Note that if you don't have a low-level debugger installed, your program will crash with an unimplemented trap error if it tries to break into the low-level debugger.

THE THROW MACROS

UException.h defines several useful macros that automatically perform tests and throw exceptions if a test failed. It also defines a type, ExceptionCode (a long), and two standard exceptions, err_AssertFailed ('asrt') and err_NilPointer ('nilP'), which are treated as type ExceptionCode. Here are the throw macros:
  • ThrowIf_(test) -- Throws an exception if test is true, where test is a Boolean or the result of a Boolean condition. The exception code will be err_AssertFailed.
  • ThrowIfNot_(test) -- Throws an exception if test is false. The exception code will be err_AssertFailed.
  • ThrowIfOSErr_(err) -- Throws an exception if err isn't equal to noErr.
  • ThrowOSErr_ (err), FailOSErr_ (err) -- Throws an exception with err as the exception code.
  • ThrowIfNULL_(ptr), ThrowIfNil_(ptr), FailNil_(ptr) -- If ptr is NULL (or nil), throws an exception with err_NilPointer as the exception code.
  • ThrowIfMemError_() -- Calls the Toolbox routine MemError and throws an exception if it returns a result that's not equal to noErr; the MemError return becomes the exception code.
  • ThrowIfMemFail_(p) -- Throws an exception if p (a pointer or a handle) is nil. The MemError routine is used to check the success or failure of the last Memory Manager call. If MemError returns a result that's not equal to noErr, the exception code is set to the return value of the MemError call. If MemError returns noErr, the exception code is set to memFullErr, a constant defined by Apple.
  • ThrowIfResError_() -- Calls the Toolbox routine ResError and throws an exception if it returns a result that's not equal to noErr; the result becomes the exception code. ResError is used to check the success or failure of the last Resource Manager call.
  • ThrowIfResFail_(h) -- Throws an exception if h (a handle to a resource) is nil. If ResError returns a result that's not equal to noErr, the exception code is set to that result. If ResError returns noErr, the exception code is set to resNotFound, a constant defined by Apple.
You can use all of the macros within if-else clauses, as they're designed to be self-contained. For example:
if (err != fnfErr)
   ThrowIfOSErr_(err); 

THE SIGNAL MACROS

UDebugging.h defines macros for raising signals, also known as asserts. These will stop the execution of the program and report errors. You can use them to check for nil pointers, out-of-range offsets, excess length, division by zero, and other problems. If you remove the definition of Debug_Signal, the entire set of macros is converted to white space and takes no runtime overhead whatsoever.

The macros are defined to check gDebugSignal for the action to take on execution, as described previously.

The following are the signal macros:

  • SignalPStr_(pstr) takes a Pascal string argument. The string can be a literal Pascal string (in double quotes beginning with \p) or a StringPtr variable (and its variants, such as Str255).
  • SignalCStr_(cstr) takes a literal C string argument. The string must be a literal (text within double quotes) and can't be a char*. Because the underlying Toolbox routines take Pascal strings, the SignalPStr_ macro is more efficient.
  • SignalIf_(test), SignalIfNot_(test) each take a Boolean condition as an argument and raise a signal depending on whether the condition is true or false.
  • Assert_(test) is a synonym for SignalIfNot_(test).

STRESS REDUCTION WITH EXCEPTION HANDLING

C++ exceptions and these Metrowerks macros make error handling reasonably easy to add to most programs. With a little thought, you can design a clean structure for dealing with Mac OS errors and internal errors -- a structure that's easily extensible to new code. You can avoid stress during testing by adding signal macro calls for common errors throughout your code. They're much easier to debug than system crashes. And yes, thank you, my XCMDs are much better now!


    RELATED READING

    • For a more in-depth examination of exceptions in C++, consult the article "Try C++ Exception Handling" by Kent Sandvik (MacTech Magazine, October 1995). For another view of C exceptions, see "Living in an Exceptional World" by Sean Parent in develop Issue 11.
    • For information on the return values of Macintosh Toolbox routines and the error codes, see the Inside Macintosh series, Macintosh Programmer's Toolbox Assistant, and THINK Reference. You can also look at the header file Errors.h.

    Because C has no objects, when you read these publications, you can ignore all discussions of object throwing, exception objects, construction, and destruction.

AVI RAPPOPORT has degrees in medieval studies and library/information studies, so she feels well qualified to work in the Macintosh software industry. In her job as user advocate and publications coordinator at Metrowerks, she spent her time documenting PowerPlant, making conference calls, and frantically trying to check CodeWarrior CDs before they were burned. Avi now works at StarNine as product manager for messaging products. She lives in Berkeley, California, with her Mac/Web scripter husband and their four-year-old son -- all BMUG members.*

Thanks to Greg Dow, Pete Gontier, Tom Lippincott, and Jon Wätte for their C++ wizardry and personal patience, and to Pete and Tom for reviewing this article.*

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Dropbox 193.4.5594 - Cloud backup and sy...
Dropbox is a file hosting service that provides cloud storage, file synchronization, personal cloud, and client software. It is a modern workspace that allows you to get to all of your files, manage... Read more
Google Chrome 122.0.6261.57 - Modern and...
Google Chrome is a Web browser by Google, created to be a modern platform for Web pages and applications. It utilizes very fast loading of Web pages and has a V8 engine, which is a custom built... Read more
Skype 8.113.0.210 - Voice-over-internet...
Skype is a telecommunications app that provides HD video calls, instant messaging, calling to any phone number or landline, and Skype for Business for productive cooperation on the projects. This... Read more
Tor Browser 13.0.10 - Anonymize Web brow...
Using Tor Browser you can protect yourself against tracking, surveillance, and censorship. Tor was originally designed, implemented, and deployed as a third-generation onion-routing project of the U.... Read more
Deeper 3.0.4 - Enable hidden features in...
Deeper is a personalization utility for macOS which allows you to enable and disable the hidden functions of the Finder, Dock, QuickTime, Safari, iTunes, login window, Spotlight, and many of Apple's... Read more
OnyX 4.5.5 - Maintenance and optimizatio...
OnyX is a multifunction utility that you can use to verify the startup disk and the structure of its system files, to run miscellaneous maintenance and cleaning tasks, to configure parameters in the... Read more
Hopper Disassembler 5.14.1 - 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
WhatsApp 24.3.78 - Desktop client for Wh...
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
War Thunder 2.33.0.135 - Multiplayer war...
In War Thunder, aircraft, attack helicopters, ground forces and naval ships collaborate in realistic competitive battles. You can choose from over 1,500 vehicles and an extensive variety of combat... Read more
Iridient Developer 4.2 - Powerful image-...
Iridient Developer (was RAW Developer) is a powerful image-conversion application designed specifically for OS X. Iridient Developer gives advanced photographers total control over every aspect of... Read more

Latest Forum Discussions

See All

A Legitimate Massage Parlor, I Swear – T...
In this week’s Episode of The TouchArcade Show we talk about some of the major new releases on mobile this week including Warframe, we go over all the major news that came out from the Nintendo Direct Partner Showcase on Wednesday, we read our one... | Read more »
TouchArcade Game of the Week: ‘Rainbow S...
I feel like I am in a fever dream right now. What is this game that I’m playing? It’s a Rainbow Six game? But it’s all cutesy, and cartoony, and also kind of psychedelic? How is this a real thing? Well, it’s no fever dream, it is indeed a real thing... | Read more »
SwitchArcade Round-Up: ‘Promenade’, ‘Cho...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for February 23rd, 2024. It’s Friday, so we have to check out the remaining releases of the week. Not so many big ones today, but a healthy crop nonetheless. After summarizing all the... | Read more »
Steam Deck Weekly: Gundam Breaker 4 and...
Welcome to this week’s slightly shorter edition of the Steam Deck Weekly. I was a bit unwell this week so no reviews in this edition, but there is a lot of news and new Steam Deck Verified and Playable games to catch up on. I have something special... | Read more »
The 10 Best Run-And-Gun Games for Ninten...
The year 2024 is a rare one, because it is a year with a brand-new Contra game. Contra: Operation Galuga might be the freshest face on the block when it comes to Nintendo Switch run-and-gun action games, but it’s hardly fighting that war alone.... | Read more »
Version 1.4 of Reverse: 1999 will be lan...
Free up your diary for February 29th, as Bluepoch has announced the impending release of the award-winning Reverse: 1999s Version 1.4 update. The Prisoner in the Cave is an Ancient Greece-themed update with new recruits, gameplay modes, and plenty... | Read more »
Premium Mobile RPG ‘Ex Astris’ From ‘Ark...
Arknights developer Hypergryph’s premium RPG Ex Astris () recently had its release date confirmed, and we finally have an extended gameplay showcase. | Read more »
Apple Arcade Weekly Round-Up: Updates fo...
Following yesterday’s big Hello Kitty Island Adventure update, a few more notable game updates and events have gone live on Apple Arcade. Cypher 007 () has gotten its first content update in a few months taking you to Egypt for five new levels... | Read more »
‘Thunder Ray’ and ‘Hime’s Quest’ Are Now...
Crunchyroll has pushed two new games into the Crunchyroll Game Vault including Purple Tree Studio’s Thunder Ray which was already on iOS before as a premium release. Shaun even reviewed it last year. Read his review here. The second game is Poppy... | Read more »
Adorable Kitty-Collector Sequel ‘Neko At...
Ya’ll. This October will mark the ten-year anniversary of Hit Point launching Neko Atsume, the adorable kitty collecting sim that has become a runaway success and essentially created a sub-genre of cozy pet-collecting life sim games. Sure, the... | Read more »

Price Scanner via MacPrices.net

16-inch M3 Max MacBook Pro on sale for $300 o...
Amazon is offering a $300 instant discount on one of Apple’s 16″ M3 Max MacBook Pros today. Shipping is free: – 16″ M3 Max MacBook Pros (36GB/1TB/Space Black): $3199, $300 off MSRP Their price is the... Read more
Apple M2 Mac minis on sale for $100 off MSRP...
B&H Photo has Apple’s M2-powered Mac minis in stock and on sale for $100 off MSRP this weekend with prices available starting at $499. Free 1-2 day shipping is available to most US addresses: –... Read more
Apple Watch SE on sale for $50 off MSRP this...
Best Buy has all Apple Watch SE models on sale this weekend for $50 off MSRP on their online store. Sale prices available for online orders only, in-store prices may vary. Order online, and choose... Read more
Deal Alert! Apple 15-inch M2 MacBook Airs on...
Looking for the lowest sale price on a new 15″ M2 MacBook Air? Best Buy has Apple 15″ MacBook Airs with M2 CPUs in stock and on sale today for $300 off MSRP on their online store. Prices valid for... Read more
Amazon discounts iPad mini 6 models up to $12...
Amazon is offering Apple’s 8.3″ WiFi iPad minis for $100-$120 off MSRP, including free shipping, for a limited time. Prices start at $399. Amazon’s prices are among the lowest currently available for... Read more
Apple AirPods Pro with USB-C discounted to $1...
Walmart has Apple’s 2023 AirPods Pro with USB-C in stock and on sale for $199.99 on their online store. Their price is $50 off MSRP, and it’s currently one the lowest prices available for new AirPods... Read more
Apple has 14-inch M3 MacBook Pro with 16GB of...
Apple has 14″ M3 MacBook Pros with 16GB of RAM, Certified Refurbished, available for $270-$300 off MSRP. Each model features a new outer case, shipping is free, and an Apple 1-year warranty is... Read more
Save $318-$432 on 16-inch M3 Max MacBook Pros...
Apple retailer Expercom has 16″ M3 Max MacBook Pros on sale for $318-$432 off MSRP when bundled with a 3-year AppleCare+ Protection Plan. Discounts are available for Silver models as well a Space... Read more
New today at Apple: 16-inch M3 Pro/M3 Max Mac...
Apple is now offering 16″ M3 Pro and M3 Max MacBook Pros, Certified Refurbished, starting at $2119 and ranging up to $530 off MSRP. Each model features a new outer case, shipping is free, and an... Read more
Apple is now offering $300-$480 discounts on...
Apple is now offering 14″ M3 Pro and M3 Max MacBook Pros, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is free, and an... Read more

Jobs Board

Part-time *Apple* and Peach Research Assist...
…and minimum qualifications: + Assist with planting, pruning, and harvesting of apple and peach trees + Conduct regular maintenance tasks to ensure optimal Read more
Housekeeper, *Apple* Valley Villa - Cassia...
Apple Valley Villa, part of a senior living community, is hiring entry-level Full-Time Housekeepers to join our team! We will train you for this position and offer a Read more
Sublease Associate Optometrist- *Apple* Val...
Sublease Associate Optometrist- Apple Valley, CA- Target Optical Date: Feb 22, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92307 **Requisition Read more
Sublease Associate Optometrist- *Apple* Val...
Sublease Associate Optometrist- Apple Valley, CA- Target Optical Date: Jan 24, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92307 **Requisition Read more
Housekeeper, *Apple* Valley Village - Cassi...
Apple Valley Village Health Care Center, a senior care campus, is hiring a Part-Time Housekeeper to join our team! We will train you for this position! In this role, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.