TweetFollow Us on Twitter

May 92 - Exception Handling in MacApp 3

Exception Handling in MacApp 3

Lonnie Millett

One of the reasons MacApp is successful with developers is that applications built with MacApp can recover and continue operating under conditions that cause other applications to crash. What's the key to creating applications that never crash? It's called exception handling, and understanding how it works is the key to making your own applications fail-safe.

An exception can be defined as any condition which your code cannot be expected to cope with. One common type of exception occurs when trying to allocate memory when there is no memory available. Exception handling "…provides a way of transferring control and information to an unspecified caller that has expressed willingness to handle exceptions of a given type."[1] An exception handler that has expressed willingness to handle a memory exception may be responsible for cleaning up allocations that were made previous to the allocation that failed.

This first article will focus on the mechanics of exception handling in MacApp. By mechanics I am referring to the different usage styles, differences between C++ and Pascal, how it works internally, why and when variables need to be declared VOLATILE, etc. With the mechanics established, a second article will focus on the "how to" of exception handling: when it is needed, when it is not needed, where it goes, and how to introduce it into existing code.

Styles of Exception Handling in MacApp

In MacApp there are two styles of exception handling. The original style of exception handling available since MacApp 1.0, I will refer to as Pascal Style exception handling. It was designed to be used with Object-Pascal and relies on local or global procedures to handle an exception. With MacApp 3.0 a new style of exception handling has been introduced which I will refer to as C++ Style exception handling. Rather than relying on local procedures, it handles an exception within the else clause of an if statement. Neither style of exception handling is exclusive to the language it was designed for. Instances of Pascal style exception handling can be found in the C++ version of MacApp, and C++ style exception handling can be used from Pascal.

Pascal Style Exception Handling

Pascal style exception handling relies on the ability of Pascal to define local procedures as handlers as a way to have access to local variables, procedure parameters, or instance variables. A typical exception handler in Pascal would look like:
PROCEDURE TPicture.IPicture(…);

    VAR fi:     FailInfo;

    PROCEDURE HandleFailure(error: OSErr; message:LONGINT);
    BEGIN SELF.Free; END;

BEGIN
    …
    CatchFailures(fi, HandleFailure);
    fDataHandle := GetPicture(fRsrcID);
    FailResError;
    Success(fi);
    …
END;

We start by declaring an instance of a FailInfo record to contain the information needed by the exception handling services. Among the information stored in the FailInfo record are the current state of all the registers and the address of the procedure that will handle the exception. Later on we will look at the definition of FailInfo in detail. Next we define the local procedure that will be called should an exception occur. Because this is an IMethod, we will want to free ourselves if we can not completely initialize ourselves. In this case the body of the local procedure simply calls SELF.Free. With these two pieces defined we are ready to execute the code that could fail.

In Pascal you bracket any code that could fail with a calls to CatchFailures() and Success(). CatchFailures() requires two parameters: the FailInfo record to store the information necessary to handle the exception and the name of the local procedure to call should an exception occur. CatchFailures() is responsible for storing in the FailInfo record the information needed to pass control to an exception handler. It also installs or links the exception handling information to a list of exception handlers. In the above example, if the call to GetPicture should fail with a resource error, our local procedure HandleFailure() will be called, allowing us to clean up after ourselves. If we successfully retrieved the picture, then we finish our bracketing of the code that could fail with a call to Success(), passing it the same FailInfo record that we passed to CatchFailures(). Success() is responsible for removing the exception handling information from the list of installed exception handlers.

The Exception Handling Chain

MacApp maintains a chain of exception handlers in a linked list, with the current, or last installed, exception handler pointed to by the MacApp global variable gTopHandler. This means that there may be more than one active exception handler installed at any point in time, each scoped to perform certain tasks should an exception occur. This is important. It allows you to define exception handlers that can handle an exception at the local level and not worry about how the exception should be handled at an outer level.

So how does it work? After control is passed to the current exception handler, we usually want the exception to be passed to the other exception handlers in the chain so we can continue to handle the exception in the appropriate manner. With Pascal style exception handling, i.e. when the exception handler is installed with CatchFailures(), this happens automatically. When the global routine Failure() is called, gTopHandler is made to point to the next exception handler in the chain:

pascal void Failure(short error, long message)
{
    FailInfoPtr pf = gTopHandler;

    if (pf)
    {
        // pop the stack first, because calling the handler
        // is likely to result in a call to another call to
        // Failure
        gTopHandler = pf->nextInfo;
        …
        pf->error = error;
        pf->message = message;
        DoFailure(pf);  // Go execute the failure handler
        …

The call to DoFailure() is responsible for passing control to the currently installed exception handler. For Pascal style exception handling, as in our example, DoFailure() would call the local routine HandleFailure(). Following the call to HandleFailure(), DoFailure() calls Failure() once again with the same error and message:

   …
    MoveA.L (A0)+,A0        ; get address of failure handler
    
    Jsr         (A0)        ; call failure handler
                            ; (HandleFailure in our example)

    MoveM.L (SP)+,D0/D1     ; get error & message back
    Move.W  D0,-(SP)
    Move.L  D1,-(SP)        ; parameters to Failure
    Jsr     FAILURE         ; Pass the exception to next
                            ; handler
                            ; should Not return

gTopHandler already points to the next exception handler, so another call to Failure() will pass the exception to the next exception handler in the chain.

If you build debug versions of your MacApp applications you have probably seen something similar to this at one time or another:

Failure caught by: TAPPLICATION.OPENOLD
Failure caught by: CArrayIterator::IArrayIterator(…)
Failure caught by: TODOCCOMMAND.DOIT
Failure caught by: TCOMMANDHANDLER.PERFORMCOMMAND
Failure caught by: TAPPLICATION.POLLEVENT

When an exception occurs, part of the debugging information sent to your favorite source code debugger is a description of the exception stack or chain. Each entry in the list represents a currently installed exception handler. In the example above, TApplication::OpenOld was the last exception handler installed.

The last exception handler in the chain for a running application is usually installed in TApplication::PollEvent. When this exception handler is reached, MacApp assumes that all efforts to handle the exception have already occurred. The only task left is to display the appropriate error alert. After the alert has been displayed, the exception is fully handled, so MacApp continues by processing the event.

What if we don't want to pass the exception to any other installed exception handlers? This might be the case if we can completely handle an exception locally. Since Pascal style exception handling automatically propagates the exception, we have to break the propagation with a Pascal GOTO:

PROCEDURE TPicture.IPicture(…);
    LABEL 1;
    VAR fi:     FailInfo;

        PROCEDURE HandleFailure(
            error: OSErr; message:LONGINT);
        BEGIN
        SELF.Free;
        GOTO 1;
        END;

    BEGIN
    …
    CatchFailures(fi, HandleFailure);
    fDataHandle := GetPicture(fRsrcID);
    FailResError;
    Success(fi);
1:
    …
    END;

As we saw earlier, DoFailure() will call our exception handler, HandleFailure() in this example, if some resource error should occur. Rather than returning to DoFailure() and calling Failure() to propagate the exception, we instead jump to a label we have defined and continue executing from that point without ever returning.

C++ Style Exception Handling

With MacApp 3.0 a new style of exception handling was introduced. Unlike Pascal style exception handling, C++ exception handling is encapsulated in an if statement rather than a local procedure:
pascal void TPicture::IPicture(…)
{
    …
    FailInfo fi;

    if (fi.Try())
    {
        this->SetPictureRsrcID(itsRsrcID, kDontRedraw);
        fi.Success();
    }
    else    // Recover
    {
        this->Free();
        fi.ReSignal();
    }
    …
}

We start as we do in Pascal by declaring an instance of FailInfo. In C++ the FailInfo record of Pascal has been redefined as a C++ class. This class is not derived from TObject and is allocated locally on the stack. We are now ready to bracket the code that could potentially fail. The Try() method of FailInfo has the same responsibilities as CatchFailures() in Pascal. Try() stores information in the fields of the FailInfo instance to pass control back to the If statement should an exception occur. It also links itself into the chain of exception handlers. Finally, Try() returns a boolean TRUE, indicating that it is ready to catch an exception and the If clause of the statement is then executed.

In the above example if the call to SetPictureRsrcID() fails we will automatically jump to the portion of the If statement that tests the return value of Try(). However, this time the value returned is FALSE, so we jump to the ELSE clause of the statement and begin executing the exception handling code. If we were successful in retrieving the picture, we call the Success() method of the FailInfo instance.

Unlike Pascal, C++ style exception handling does not automatically propagate the exception to the next exception handler in the chain. When an exception is bracketed with a Try() instead of CatchFailures(), a JMP rather than a JSR is used to arrive at the exception handler, so we never return to MacApp's exception handling code where it can continue by calling Failure() automatically. If we wish to propagate the exception, we must do so explicitly. This is done at the end of the exception handling code by calling the ReSignal() method of the FailInfo instance. What if we don't want to propagate the exception? C++ style exception handling makes this easy. If we don't include the call to ReSignal(), we continue execution with the first statement following the IF statement.

The Need for VOLATILE

We learned before that when CatchFailures() or Try() is called the current state of all registers is saved. If an exception occurs, the registers are restored to their saved state. But what happens if we modified a variable in the bracketed code that was cached by the compiler in a register? Unfortunately we lose the modified value. This becomes a serious problem if we must then reference the variable in the exception handling part of the code. If, for example, the variable was a newly allocated handle that should be freed on exception, we would lose our reference and would not be able to dispose of it. What is needed is a way to ensure that a variable or parameter is not cached in a register.

The concept of a value being volatile was introduced to do just that. Volatile is a hint to the compiler that the variable in question could change and should not be cached in a register. Volatile is a keyword in both ANSI C and C++. However, Apple's C compiler does not have an implementation of volatile that is sufficient for C++. Therefore, CFront ignores the volatile keyword and does not pass it on to the C compiler. To solve the problem, MacApp 3.0 defines a macro for C++ users called VOLATILE (all upper case). The macro is quite simple:

#define VOLATILE(a) ((void) &a)

By taking the address of a variable or parameter we prevent the compiler from caching it in a register. While this approach currently works for CFront and C, a smarter compiler might optimize the statement away and then go ahead and cache the variable in a register. A different compiler could require a different definition of VOLATILE.

The macro is used like this:

pascal void TWindow::IWindow(TDocument* itsDocument,
                        WindowPtr itsWMgrWindow,
                        …
                        Boolean disposeOnFree)
{
    VOLATILE(itsWMgrWindow);
    VOLATILE(disposeOnFree);

    FailInfo fi;
    if (fi.Try())
    {
        …

Do all variables and parameters need to be declared VOLATILE if we are using exception handling? Thankfully no, since we really do want our compilers to optimize our code as much as possible. The rule for declaring a value as volatile is:

  • Any four-byte or smaller local variable or parameter that is modified in the IF clause of an exception handler and used in the ELSE clause must be declared VOLATILE.

Typical examples are Pointers, Handles, Object references, long or short integer counters, etc.

One more comment about the use of volatile. Those of you who have been using Pascal style exception handling in the current and previous versions of MacApp may be wondering why the use of volatile has never been required. The answer is in the types of optimizations the Pascal compiler can make. When a local or nested procedure is defined, as is always the case for Pascal style exception handling, the Pascal compiler is incapable of optimizing any variable or parameter into a register. Therefore no hints from the developer are required!

C++ Style Exception Handling For Pascal

Neither style of exception handling is exclusive to the language it was initially designed for. With a little effort C++ style exception handling is possible with Pascal in MacApp 3.0. What does it look like?:
PROCEDURE TPicture.IPicture(…);
    VAR fi:     FailInfo;

    BEGIN
    …
    IF Try(fi) THEN
        BEGIN
        SELF.SetPictureRsrcID(itsRsrcID, kDontRedraw);
        Success(fi);
        END
    ELSE
        BEGIN
        SELF.Free;
        Failure(fi.error, fi.message);
        END;
    …

Because FailInfo is not a class in Pascal, we must use the global procedures Try(), Success(), and Failure(), passing our local instance of FailInfo as the parameter. Unfortunately Try() is not externalized in UFailure.p. Part of the extra effort required to use C++ style exception handling in Pascal with MacApp 3.0 is adding a declaration for Try():

FUNCTION Try(VAR fi: FailInfo): BOOLEAN; C; EXTERNAL;

The declaration can be added to your UFailure.p if you are willing to modify your MacApp sources. If not, you can add it to your own source where needed.

The other missing component to support C++ style exception handling in Pascal is a solution for volatile values. Because we are no longer using nested procedures as exception handlers, the Pascal compiler may have optimized local variables and parameters into registers for us. However, unlike C++, Pascal has there is no volatile keyword and no macro capability for extending the language as we did with MacApp 3.0. Luckily we can define a small inline procedure that will serve our purpose nicely:

PROCEDURE VOLATILE(p: UNIV Ptr);
inline 588F;    { ADDQ.L #$4,A7 }

As with the above declaration for Try() this declaration can also be added to your UFailure.p interface or to one of your own headers if you prefer not to modify MacApp. The declarations for both Try() and VOLATILE() have been added to UFailure.p in MacApp 3.0.1, so C++ style exception handling will be fully supported in Pascal.

The same rules apply for determining if a value should be declared VOLATILE in Pascal and C++. However the usage model is slightly different:

PROCEDURE TWindow.IWindow(itsDocument: TDocument,
                        itsWMgrWindow: WindowPtr,
                        …
                        disposeOnFree: BOOLEAN);
    VAR fi: FailInfo;

    BEGIN
    VOLATILE(@itsWMgrWindow);
    VOLATILE(@disposeOnFree);

    IF Try(fi) THEN
        BEGIN
        …

This implementation of VOLATILE requires you to take the address of the variable or parameter that you do not wish the compiler to optimize.

Pascal Style Exception Handling in C++

Pascal style exception handling is still useful in some interesting ways with C++. In particular, MacApp 3.0 uses Pascal style exception handling for certain kinds of value-based or stack-based C++ objects.

Iterators are a special class of value-based objects that can iterate over MacApp's dynamic arrays. One nice feature of dynamic arrays is that iterations can be nested. That is, more than one iterator at a time can be installed for a single dynamic array with each iterator at a different stage of iteration. To make this possible, each dynamic array stores a pointer to a circular list of installed iterators. If during iteration an object becomes inserted into the dynamic array, each of the iterators in the list has its index modified accordingly so that it can continue to iterate properly. When an iterator is finished iterating it removes itself from the list of iterators maintained by its dynamic array.

But what happens if a failure occurs during the iteration? Because an iterator is allocated on the stack it ceases to exist when we exit the iterator's enclosing block and clean up the stack. We need to remove the iterator from the list of iterators maintained by the dynamic array; otherwise it is left with a pointer to an iterator that no longer exists. It's clear that we need an exception handler installed during the life of the iterator so that if an exception occurs the iterator can be removed from the dynamic array's list of iterators.

One of the fields of an iterator is an instance of a FailInfo class. Iterators also have a special method called HandleFailure() which encapsulates the behavior required to unlink the iterator from the list of iterators. During the iterator's IMethod, HandleFailure() is installed with CatchFailures() as the exception handler to be called should an exception occur :

void CArrayIterator::IArrayIterator(…)
{
    …
    // Setup the FailInfo to catch any failures
    // while this object is in scope
    // giving control to the HandleFailure method
    CatchFailures( this->fFailInfo,
        (CatchFailuresType) &CArrayIterator::HandleFailure, this);
} // CArrayIterator::IArrayIterator

As described before, CatchFailures() requires two parameters: an instance of a FailInfo class or record and the procedure to call when an exception occurs. Iterators pass the fFailInfo field as the FailInfo instance and the address of the HandleFailure() method as the method to call. Unlike Pascal we are able to pass the address of methods in C++. But why and how are we able to pass this as a normally non existent third parameter?

CatchFailures() is defined with Pascal calling conventions. Because procedures can be nested in Pascal, a hidden parameter, referred to as the static-link in MacApp, is always passed as the last parameter. If the procedure is nested, the hidden parameter is a pointer to the local scope of that procedure so that it has access to local variables and parameters. If the procedure is not nested, the hidden parameter has a value of NULL. Similar to Pascal, C++ always passes a hidden parameter this to methods for access to the class scope. Because the hidden parameter of Pascal and C++ are both passed as the last parameter to the procedure or method, we can replace the pointer to the static link with a pointer to the class scope of the iterator instance. Now when HandleFailure() is called it will be able to access all of the fields and methods of the instance. If the iterator was successful in iterating over the list, it needs to remove itself from the list of iterators maintained by the dynamic array:

CArrayIterator::~CArrayIterator()
{
    if (fDynamicArray)
    {
        // Remove our entry in the failure handling
        // system as we are going out of scope
        // Use this style of success for efficiency.
        fFailInfo.Success();
        …
    }
} // CArrayIterator::~CArrayIterator

This is done by calling success with the iterator's fFailInfo fields in the class destructor. Why do we use a C++ style call to success rather than a Pascal style when the failure handler was installed with CatchFailures? Actually either one can can be used here but the C++ style call to success is turned into an inline for non-debug builds making it slightly faster to execute.

If we fail during iteration, then the iterator's HandleFailure() will be called:

pascal void CArrayIterator::HandleFailure(OSErr,long)
{
    fDynamicArray->fIteratorPtr = this->RemoveFromList();

    // Check if there is a pending free request that
    // couldn't be honored because we were iterating and if
    // so… be free!
    if (fDynamicArray->fFreeRequested && 
            !fDynamicArray->fIteratorPtr)
        fDynamicArray->Free();

    // the error will be resignalled automatically
    // with the Pascal style of failure handler 
} // CArrayIterator::HandleFailure  

It will take care of removing the iterator from the dynamic arrays list of iterators, honor any requests to free the list which could not be serviced during iteration, and, since we wish to continue propagating the exception to the next exception handler, do nothing, remembering that exception handlers installed with CatchFailures() always resignal an exception automatically.

FailInfo Fields

Part of understanding how exception handling works in MacApp is understanding the fields of the FailInfo record. In C++ FailInfo is defined as a class but is bit-wise compatible with the definition in Pascal:
class FailInfo {
public:
    long        regs[12];   // The saved registers
    short       flags;      // 0 for CatchFailures, 1 for Try
    short       error;      // Error condition passed to Failure
    long        message;    // Failure message
    long        failA6;     // Static-Link passed to Pascal handler
    long        failPC;     // Address of failure handler routine
    FailInfo*   nextInfo;   // Next handler in the chain 
#if qDebug
    long        whoPC;      // Address of the caller
    short       installed;  // Linked into the chain?
#endif
}

When an exception handler is installed with CatchFailures() or Try(), most of the registers, specifically A2-A7 and D2-D7, are saved in regs. If an exception occurs, DoFailures() uses regs to restore the registers to their previous state. The flags field is used to determine how the exception handler was installed. If flags has a value of 0, then the exception handler was installed with CatchFailures() and will need to be called as a procedure. The address of the procedure to be called is stored in failPC, and the link to its local scope is stored in failA6. If the value is 1, then the exception handler was installed with a Try() and DoFailure() will JMP rather than JSR to the address stored in failPC. The failA6 field is unused when using Try(). The error and message fields are set to the value of the parameters passed to Failure() when an exception occurs. These will be passed on to the next exception handler in the chain, pointed to by nextInfo, if the exception handler was installed with CatchFailures() or if the exception is explicitly resignalled.

The last two fields are only available for debug builds of MacApp. The whoPC field points to the instruction following the call to CatchFailures() or Try(). It is used to determine the name of the method that caught the exception. For exception handlers installed with Try(), failPC and whoPC have the same value.

One of the common mistakes made when wrapping code that could cause an exception is forgetting to call Success(). Forgetting to call success can lead to some serious problems. Because the state of all registers including the PC are saved in a FailInfo class, an exception handler that is not removed from the chain by calling Success() could jump to a code segment that has since been unloaded. Debug builds of MacApp warn you the next time you call Success() that you have either too few or too many calls to Success. However, it is sometimes difficult to determine where.

The last field, installed, is available only to C++ users and is used to determine as early as possible that a call to success was forgotten. It is set to TRUE when the exception handler is linked into the chain of exception handlers with CatchFailures() or Try(). If an exception occurs or Success() is called, installed is set to FALSE again. The C++ version of FailInfo defines a destructor for debug builds of MacApp that checks the value of installed. Because the FailInfo destructor is only called if no exception occurred, a value of TRUE indicates that we forgot to call Success(), and we are informed immediately.

FailInfo Methods

In addition to the fields that are common between the Pascal and C++ definitions of FailInfo, C++ adds some useful methods:
class FailInfo {
public:
    inline FailInfo::FailInfo()
    inline Boolean FailInfo::Try(> {return ::Try(*this);}
    inline void FailInfo::ReSignal()
                            {::Failure(error, message);}
#if qDebug
    inline FailInfo::~FailInfo();
    inline void FailInfo::Success() {::Success(*this);}
#else
    inline void FailInfo::Success() {gTopHandler = nextInfo;}
#endif
}

For non-debug builds of MacApp all the methods are defined as inline, so the resulting code looks very much like what you get when doing C++ style exception handling in Pascal. The only difference is the Success() method. Debug builds of MacApp continue to call the global procedure Success() because it will perform some useful debug checks. Non-debug builds optimize Success() so that it only does the required work of unlinking the exception handler from the chain of exception handlers.

Conclusion

Exception handling is an important part of MacApp and is the key to developing applications that respond in a consistent manner under varying conditions. With an understanding of the mechanics of exception handling we are better able to discuss how exception handling can be used in your applications. In the next article I will define the ground rules that are essential to understanding when and how to integrate exception handling. I will also work through some typical examples in MacApp, look at ways of reducing the number of exception handlers that might be required, and try to answer some of the more esoteric questions developers have about exception handling.
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.