Why would you use an ivar?

Encapsulation

If the ivar is private, the other parts of the program can't get at it as easily. With a declared property, the clever people can access and mutate quite easily via the accessors.

Performance

Yes, this can make a difference in some cases. Some programs have constraints where they can not use any objc messaging in certain parts of the program (think realtime). In other cases, you may want to access it directly for speed. In other cases, it's because objc messaging acts as an optimization firewall. Finally, it can reduce your reference count operations and minimize peak memory usage (if done correctly).

Nontrivial Types

Example: If you have a C++ type, direct access is just the better approach sometimes. The type may not be copyable, or it may not be trivial to copy.

Multithreading

Many of your ivars are codependent. You must ensure your data integrity in multithreaded context. Thus, you may favor direct access to multiple members in critical sections. If you stick with accessors for codependent data, your locks must typically be reentrant and you will often end up making many more acquisitions (significantly more at times).

Program Correctness

Since the subclasses can override any method, you may eventually see there is a semantic difference between writing to the interface versus managing your state appropriately. Direct access for program correctness is especially common in partially constructed states -- in your initializers and in dealloc, it's best to use direct access. You may also find this common in the implementations of an accessor, a convenience constructor, copy, mutableCopy, and archiving/serialization implementations.

It's also more frequent as one moves from the everything has a public readwrite accessor mindset to one which hides its implementation details/data well. Sometimes you need to correctly step around side effects a subclass' override may introduce in order to do the right thing.

Binary Size

Declaring everything readwrite by default usually results in many accessor methods you never need, when you consider your program's execution for a moment. So it will add some fat to your program and load times as well.

Minimizes Complexity

In some cases, it's just completely unnecessary to add+type+maintain all that extra scaffolding for a simple variable such as a private bool that is written in one method and read in another.


That's not at all to say using properties or accessors is bad - each has important benefits and restrictions. Like many OO languages and approaches to design, you should also favor accessors with appropriate visibility in ObjC. There will be times you need to deviate. For that reason, I think it's often best to restrict direct accesses to the implementation which declares the ivar (e.g. declare it @private).


re Edit 1:

Most of us have memorized how to call a hidden accessor dynamically (as long as we know the name…). Meanwhile, most of us have not memorized how to properly access ivars which aren't visible (beyond KVC). The class continuation helps, but it does introduce vulnerabilities.

This workaround's obvious:

if ([obj respondsToSelector:(@selector(setName:)])
  [(id)obj setName:@"Al Paca"];

Now try it with an ivar only, and without KVC.


For me it is usually performance. Accessing an ivar of an object is as fast as accessing a struct member in C using a pointer to memory containing such a struct. In fact, Objective-C objects are basically C structs located in dynamically allocated memory. This is usually as fast as your code can get, not even hand optimized assembly code can be any faster than that.

Accessing an ivar through a getter/setting involves an Objective-C method call, which is much slower (at least 3-4 times) than a "normal" C function call and even a normal C function call would already be multiple times slower than accessing a struct member. Depending on the attributes of your property, the setter/getter implementation generated by the compiler may involve another C function call to the functions objc_getProperty/objc_setProperty, as these will have to retain/copy/autorelease the objects as needed and further perform spinlocking for atomic properties where necessary. This can easily get very expensive and I'm not talking about being 50% slower.

Let's try this:

CFAbsoluteTime cft;
unsigned const kRuns = 1000 * 1000 * 1000;

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    testIVar = i;
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"1: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    [self setTestIVar:i];
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"2: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

Output:

1: 23.0 picoseconds/run
2: 98.4 picoseconds/run

This is 4.28 times slower and this was a non-atomic primitive int, pretty much the best case; most other cases are even worse (try an atomic NSString * property!). So if you can live with the fact that each ivar access is 4-5 times slower than it could be, using properties is fine (at least when it comes to performance), however, there are plenty of situations where such a performance drop is completely unacceptable.

Update 2015-10-20

Some people argue, that this is not a real world problem, the code above is purely synthetic and you will never notice that in a real application. Okay then, let's try a real world sample.

The code following below defines Account objects. An account has properties that describe name (NSString *), gender (enum), and age (unsigned) of its owner, as well as a balance (int64_t). An account object has an init method and a compare: method. The compare: method is defined as: Female orders before male, names order alphabetically, young orders before old, balance orders low to high.

Actually there exists two account classes, AccountA and AccountB. If you look their implementation, you'll notice that they are almost entirely identical, with one exception: The compare: method. AccountA objects access their own properties by method (getter), while AccountB objects access their own properties by ivar. That's really the only difference! They both access the properties of the other object to compare to by getter (accessing it by ivar wouldn't be safe! What if the other object is a subclass and has overridden the getter?). Also note that accessing your own properties as ivars does not break encapsulation (the ivars are still not public).

The test setup is really simple: Create 1 Mio random accounts, add them to an array and sort that array. That's it. Of course, there are two arrays, one for AccountA objects and one for AccountB objects and both arrays are filled with identical accounts (same data source). We time how long it takes to sort the arrays.

Here's the output of several runs I did yesterday:

runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076

As you can see, sorting the array of AccountB objects is always significant faster than sorting the array of AccountA objects.

Whoever claims that runtime differences of up to 1.32 seconds make no difference should better never do UI programming. If I want to change the sorting order of a large table, for example, time differences like these do make a huge difference to the user (the difference between an acceptable and a sluggish UI).

Also in this case the sample code is the only real work performed here, but how often is your code just a small gear of a complicated clockwork? And if every gear slows down the whole process like this, what does that mean for the speed of the whole clockwork in the end? Especially if one work step depends on the output of another one, which means all the inefficiencies will sum up. Most inefficiencies are not a problem on their own, it's their sheer sum that becomes a problem to the whole process. And such a problem is nothing a profiler will easily show because a profiler is about finding critical hot spots, but none of these inefficiencies are hot spots on their own. The CPU time is just averagely spread among them, yet each of them only has such a tiny fraction of it, it seems a total waste of time to optimize it. And it's true, optimizing just one of them would help absolutely nothing, optimizing all of them can help dramatically.

And even if you don't think in terms of CPU time, because you believe wasting CPU time is totally acceptable, after all "it's for free", then what about server hosting costs caused by power consumption? What about battery runtime of mobile devices? If you would write the same mobile app twice (e.g. an own mobile web browser), once a version where all classes access their own properties only by getters and once where all classes access them only by ivars, using the first one constantly will definitely drain the battery much faster than using the second one, even though they are functional equivalent and to the user the second one would even probably even feel a bit swifter.

Now here's the code for your main.m file (the code relies on ARC being enabled and be sure to use optimization when compiling to see the full effect):

#import <Foundation/Foundation.h>

typedef NS_ENUM(int, Gender) {
    GenderMale,
    GenderFemale
};


@interface AccountA : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountA *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


@interface AccountB : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountB *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


static
NSMutableArray * allAcocuntsA;

static
NSMutableArray * allAccountsB;

static
int64_t getRandom ( const uint64_t min, const uint64_t max ) {
    assert(min <= max);
    uint64_t rnd = arc4random(); // arc4random() returns a 32 bit value only
    rnd = (rnd << 32) | arc4random();
    rnd = rnd % ((max + 1) - min); // Trim it to range
    return (rnd + min); // Lift it up to min value
}

static
void createAccounts ( const NSUInteger ammount ) {
    NSArray *const maleNames = @[
        @"Noah", @"Liam", @"Mason", @"Jacob", @"William",
        @"Ethan", @"Michael", @"Alexander", @"James", @"Daniel"
    ];
    NSArray *const femaleNames = @[
        @"Emma", @"Olivia", @"Sophia", @"Isabella", @"Ava",
        @"Mia", @"Emily", @"Abigail", @"Madison", @"Charlotte"
    ];
    const NSUInteger nameCount = maleNames.count;
    assert(maleNames.count == femaleNames.count); // Better be safe than sorry

    allAcocuntsA = [NSMutableArray arrayWithCapacity:ammount];
    allAccountsB = [NSMutableArray arrayWithCapacity:ammount];

    for (uint64_t i = 0; i < ammount; i++) {
        const Gender g = (getRandom(0, 1) == 0 ? GenderMale : GenderFemale);
        const unsigned age = (unsigned)getRandom(18, 120);
        const int64_t balance = (int64_t)getRandom(0, 200000000) - 100000000;

        NSArray *const nameArray = (g == GenderMale ? maleNames : femaleNames);
        const NSUInteger nameIndex = (NSUInteger)getRandom(0, nameCount - 1);
        NSString *const name = nameArray[nameIndex];

        AccountA *const accountA = [[AccountA alloc]
            initWithName:name age:age gender:g balance:balance
        ];
        AccountB *const accountB = [[AccountB alloc]
            initWithName:name age:age gender:g balance:balance
        ];

        [allAcocuntsA addObject:accountA];
        [allAccountsB addObject:accountB];
    }
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        @autoreleasepool {
            NSUInteger ammount = 1000000; // 1 Million;
            if (argc > 1) {
                unsigned long long temp = 0;
                if (1 == sscanf(argv[1], "%llu", &temp)) {
                    // NSUIntegerMax may just be UINT32_MAX!
                    ammount = (NSUInteger)MIN(temp, NSUIntegerMax);
                }
            }
            createAccounts(ammount);
        }

        // Sort A and take time
        const CFAbsoluteTime startTime1 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAcocuntsA sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime1 = CFAbsoluteTimeGetCurrent() - startTime1;

        // Sort B and take time
        const CFAbsoluteTime startTime2 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAccountsB sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime2 = CFAbsoluteTimeGetCurrent() - startTime2;

        NSLog(@"runTime 1: %f", runTime1);
        NSLog(@"runTime 2: %f", runTime2);
    }
    return 0;
}



@implementation AccountA
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (self.gender != account.gender) {
            if (self.gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![self.name isEqualToString:account.name]) {
            return [self.name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (self.age != account.age) {
            if (self.age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (self.balance != account.balance) {
            if (self.balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end


@implementation AccountB
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (_gender != account.gender) {
            if (_gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![_name isEqualToString:account.name]) {
            return [_name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (_age != account.age) {
            if (_age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (_balance != account.balance) {
            if (_balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end