KRAPPS.com has a very disturbing article about Apple's action against the developer of the forChan app. The forChan application is simply an image scraper designed to work with image boards such as 4chan. While many of the pictures posted on these imageboards (which are all posted by users) are innocuous, many of these imageboards cannot be described as anything but the nasty, grimy underbelly of the Internet.
ForChan.app is simply a client to a particular type of web application that is widely used for non-porn images as well as porn. The app scrapes the imageboard HTML for image tags, then presents all of the images from one subboard as thumbnails, allowing the user to view them larger by tapping on a thumbnail. The app doesn't, itself, contain porn or do anything that clearly and undisputedly violates the SDK agreement.
After Apple realized that they let on a dedicated client tailor made for some of the nastiest places on the Internet, they quickly pulled the application. Had they stopped there, it would have been wrong, but not on any kind of epic scale. It would have been just another example of the risk of working in the Cathedral1. We all farm Apple's land, and we all know they have a certain amount of power over us. But, Apple didn't stop there, they then revoked the developer's certificate and removed all of his other applications from the App store. In one arbitrary, unappealable step, they completely removed one developer's source of income and made the product of many, many hours of work completely useless. The reason? Because he "deceived Apple about the intent of the application".
This is unfair in ways it's hard to describe.
What was the developer's deceit that was so bad it warranted completely destroying his livelihood? Well, he um… didn't default the application to show porn, which Apple has decided is the app's main purpose. Despite the fact that the very name of the application, and certainly the description submitted were very clear about what the application does (hell, the application is named after one of the seediest imageboards on the net). Because whomever reviewed the app wasn't savvy enough to pick up on the App's purpose the first time through, they've declared the developer to be dishonest.
I give Apple the benefit of the doubt whenever there is any to give. I have defended their actions a few times when they might not have deserved to be defended. But this is too far for even me. This kind of arbitrary and devastating action is disproportionate punishment and I can't see any justification or defense.
Apple should reinstate Charles Rodriguez's developer credentials and restore his other apps to the App Store immediately along with an apology. I doubt they will, but that's the right course of action for them to take.
1- This is a reference to Eric S. Raymond's classic comparison of commercial and open source software models The Cathedral and the Bazaar.
Friday, January 22, 2010
Thursday, January 21, 2010
Chapter 4 and the Tale of the NSFetchedResultsController
Okay, some people have been experiencing sporadic problems with the Chapter 4 application as described here. The solution I'd like to use would require being able to determine the number of pending, uncommitted section inserts and deletes that a table view has. Although I can get to this information, I can only do so by accessing a private instance variable of UITableView. Obviously, I don't want to give you all a solution that's going to get your application's rejected during the review process.
So, I went back to the drawing board. I don't like this solution as much since it requires us to duplicate work that the table view is already doing by keeping a shadow count of inserts and deletes, but it seems to work well and doesn't add too much complexity. I now have a pretty thorough test case for inserting and deleting rows from a table that uses an NSFetchedResultsController and this solution passes it, so fingers crossed.
The first step is to add a @private NSUInteger instance variables to the controller class that manages the table and fetched results controller. This will keep a running count of the number of sections inserted and deleted during a batch of table updates.
In context of the Chapter 4 application, that means adding the following bold line of code to HeroListViewController.h:
Now, we have to switch over to the implementation file, HeroListViewController.m and add a line of code to reset the insert count when we get notified by the fetched results controller that changes are coming. To do that, we add one line of code to the method controllerWillChangeContent:, like so:
Next, we have to increment this variable whenever we insert a section, and decrement it whenever we delete a section in controller:didChangeSection:atIndex:forChangeType:. We do that by adding the bold code below:
Finally, any time we do our consistency check in controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:, we have to take the pending inserts and deletes into account. Since we do the check more than once and insert new sections when the check fails, we also increment the variable if we do insert new rows. We do all that by adding the bold code in below to that method:
I'll push this new code into the project archive as soon as possible and get it posted to apress.com and iphonedevbook.com, but here is the updated version of the Chapter 4 Xcode project in the meantime.
Don't worry if you don't understand everything that's going on in this code. This is nasty code designed to be completely generic so you don't have to worry about it at all. Hopefully this will be the end of our troubles with NSFetchedResultsController.
So, I went back to the drawing board. I don't like this solution as much since it requires us to duplicate work that the table view is already doing by keeping a shadow count of inserts and deletes, but it seems to work well and doesn't add too much complexity. I now have a pretty thorough test case for inserting and deleting rows from a table that uses an NSFetchedResultsController and this solution passes it, so fingers crossed.
The Solution
The first step is to add a @private NSUInteger instance variables to the controller class that manages the table and fetched results controller. This will keep a running count of the number of sections inserted and deleted during a batch of table updates.
In context of the Chapter 4 application, that means adding the following bold line of code to HeroListViewController.h:
#import <UIKit/UIKit.h>
#define kSelectedTabDefaultsKey @"Selected Tab"
enum {
kByName = 0,
kBySecretIdentity,
};
@class HeroEditController;
@interface HeroListViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UITabBarDelegate, UIAlertViewDelegate, NSFetchedResultsControllerDelegate>{
UITableView *tableView;
UITabBar *tabBar;
HeroEditController *detailController;
@private
NSFetchedResultsController *_fetchedResultsController;
NSUInteger sectionInsertCount;
}
@property (nonatomic, retain) IBOutlet UITableView *tableView;
@property (nonatomic, retain) IBOutlet UITabBar *tabBar;
@property (nonatomic, retain) IBOutlet HeroEditController *detailController;
@property (nonatomic, readonly) NSFetchedResultsController *fetchedResultsController;
- (void)addHero;
- (IBAction)toggleEdit;
@end
Now, we have to switch over to the implementation file, HeroListViewController.m and add a line of code to reset the insert count when we get notified by the fetched results controller that changes are coming. To do that, we add one line of code to the method controllerWillChangeContent:, like so:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
sectionInsertCount = 0;
[self.tableView beginUpdates];
}Next, we have to increment this variable whenever we insert a section, and decrement it whenever we delete a section in controller:didChangeSection:atIndex:forChangeType:. We do that by adding the bold code below:
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1))) {
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
sectionInsertCount++;
}
break;
case NSFetchedResultsChangeDelete:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1) )) {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
sectionInsertCount--;
}
break;
case NSFetchedResultsChangeMove:
break;
case NSFetchedResultsChangeUpdate:
break;
default:
break;
}
}Finally, any time we do our consistency check in controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:, we have to take the pending inserts and deletes into account. Since we do the check more than once and insert new sections when the check fails, we also increment the variable if we do insert new rows. We do all that by adding the bold code in below to that method:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate: {
NSString *sectionKeyPath = [controller sectionNameKeyPath];
if (sectionKeyPath == nil)
break;
NSManagedObject *changedObject = [controller objectAtIndexPath:indexPath];
NSArray *keyParts = [sectionKeyPath componentsSeparatedByString:@"."];
id currentKeyValue = [changedObject valueForKeyPath:sectionKeyPath];
for (int i = 0; i < [keyParts count] - 1; i++) {
NSString *onePart = [keyParts objectAtIndex:i];
changedObject = [changedObject valueForKey:onePart];
}
sectionKeyPath = [keyParts lastObject];
NSDictionary *committedValues = [changedObject committedValuesForKeys:nil];
if ([[committedValues valueForKeyPath:sectionKeyPath] isEqual:currentKeyValue])
break;
NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (tableSectionCount + sectionInsertCount != frcSectionCount) {
// Need to insert a section
NSArray *sections = controller.sections;
NSInteger newSectionLocation = -1;
for (id oneSection in sections) {
NSString *sectionName = [oneSection name];
if ([currentKeyValue isEqual:sectionName]) {
newSectionLocation = [sections indexOfObject:oneSection];
break;
}
}
if (newSectionLocation == -1)
return; // uh oh
if (!((newSectionLocation == 0) && (tableSectionCount == 1) && ([self.tableView numberOfRowsInSection:0] == 0))) {
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:newSectionLocation] withRowAnimation:UITableViewRowAnimationFade];
sectionInsertCount++;
}
NSUInteger indices[2] = {newSectionLocation, 0};
newIndexPath = [[[NSIndexPath alloc] initWithIndexes:indices length:2] autorelease];
}
}
case NSFetchedResultsChangeMove:
if (newIndexPath != nil) {
NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (frcSectionCount != tableSectionCount + sectionInsertCount) {
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];
sectionInsertCount++;
}
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath]
withRowAnimation: UITableViewRowAnimationRight];
}
else {
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationFade];
}
break;
default:
break;
}
}
I'll push this new code into the project archive as soon as possible and get it posted to apress.com and iphonedevbook.com, but here is the updated version of the Chapter 4 Xcode project in the meantime.
Don't worry if you don't understand everything that's going on in this code. This is nasty code designed to be completely generic so you don't have to worry about it at all. Hopefully this will be the end of our troubles with NSFetchedResultsController.
Coming Soon… One Week with Android
Don't worry, I have no intention of leaving the iPhone SDK as my main programming platform or the iPhone as my primary phone, but in the interest of being an informed fanboy, I've been using a Nexus One this week, and I've been porting some small apps to Android. I'll write up my observations and thoughts about both the phone and the SDK this weekend.
Wednesday, January 20, 2010
Another TableView / NSFetchedResultsController Gotcha
If you've followed this blog for any length of time, you know that I've been locking horns with NSFetchedResultsController and periodically releasing updated versions of the Navigation-Based Core Data Xcode Template to address the various problems, inconsistencies, and gotchas that I've uncovered during my fight.
Since More iPhone 3 Development was released, I've been getting sporadic reports of a problem with the Chapter 4 version of the Core Data application that, until last night, I hadn't been able to reproduce. One reader was finally able to send me specific instructions, and lo and behold, I was able to reproduce the problem.
So, I started stepping through the code, and found that in certain situations (the parameters of which, I haven't fully figured out yet), my code is attempting to insert two sections in the table when only one new section is required by the update. It happens when a value used in the section key path is changed, but not always when that happens.
What happens is, in controller:didChangeSection:atIndex:forChangeType:, I get notified of a new section being inserted into the fetched results controller and insert a corresponding section at the appropriate spot in the table, like so:
All well and good, right? But then, in controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: which fires afterwards, I have code that checks to make sure the number of sections matches between the fetched results controller and the table view. This code is necessary because in some situations, NSFetchedResultController doesn't tell its delegate if a new section was created. It's a pretty simple check, I just find the number of sections in the fetched results controller and in the table and when they don't match, I insert a new section in the table.
And this works most of the time. But, sometimes it doesn't. The sporadic nature makes it hard to debug, but I finally managed to step through the code when it was happening. In controller:didChangeSection:atIndex:forChangeType:, before the line of code that inserts a new section, I checked the number of sections in the table view. There were five.
Then, after the line of code that inserted the section, I checked again. There were still five.
Sounds like a bug in Apple's code, right? Actually, it's not. It's documented behavior.
The documentation for insertRowsAtIndexPath:withRowAnimation: on UITableView says:
One solution, which feels kludgey, would be to have a BOOL instance variable to track when an earlier delegate method call inserted a row. I don't like that solution, though, so I'm looking for a better option to incorporate into my generic delegate methods.
I'll keep you all updated on my progress, but if you have any ideas how I can determine if there is a pending insert in a table, feel free to share them in the comments.
Update 1: There is a private mutable array called _insertItems that holds the deferred insertions. Even though it's published in the header file, I think accessing this directly would technically be considered use of a private API. Instance variables with an underscore are considered private by Apple, even if published in a header file.
Update 2: I have an illicit functioning version! Unfortunately, I can't use it because it requires accessing private instance variables of UITableView. Once Apple's Bug Reporter is back up, I'm going to put in an enhancement request to have the information I need made public, but I'm probably going to have to come up with a different interim solution, and it will probably be hacky.
For the curious, what I did was to create a category on UITableView that added this method:
Now, don't use this in your apps, as you will get rejected from the app store. UIUpdateItem is not a public class, and _insertItems is not a public instance variable (though it's contained in a public header file). Were this information to be made available, then I would be able to do a more robust consistency check that would eliminate the double insertion problem:
Since More iPhone 3 Development was released, I've been getting sporadic reports of a problem with the Chapter 4 version of the Core Data application that, until last night, I hadn't been able to reproduce. One reader was finally able to send me specific instructions, and lo and behold, I was able to reproduce the problem.
So, I started stepping through the code, and found that in certain situations (the parameters of which, I haven't fully figured out yet), my code is attempting to insert two sections in the table when only one new section is required by the update. It happens when a value used in the section key path is changed, but not always when that happens.
What happens is, in controller:didChangeSection:atIndex:forChangeType:, I get notified of a new section being inserted into the fetched results controller and insert a corresponding section at the appropriate spot in the table, like so:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];All well and good, right? But then, in controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: which fires afterwards, I have code that checks to make sure the number of sections matches between the fetched results controller and the table view. This code is necessary because in some situations, NSFetchedResultController doesn't tell its delegate if a new section was created. It's a pretty simple check, I just find the number of sections in the fetched results controller and in the table and when they don't match, I insert a new section in the table.
NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (frcSectionCount != tableSectionCount)
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];And this works most of the time. But, sometimes it doesn't. The sporadic nature makes it hard to debug, but I finally managed to step through the code when it was happening. In controller:didChangeSection:atIndex:forChangeType:, before the line of code that inserts a new section, I checked the number of sections in the table view. There were five.
Then, after the line of code that inserted the section, I checked again. There were still five.
Sounds like a bug in Apple's code, right? Actually, it's not. It's documented behavior.
The documentation for insertRowsAtIndexPath:withRowAnimation: on UITableView says:
UITableView defers any insertions of rows or sections until after it has handled the deletions of rows or sections. This happens regardless of ordering of the insertion and deletion method calls.This leaves me with quite a conundrum. Since my code is not directly managing the table, but NSFetchedResultsController is deferring certain tasks to its delegate which is my code, I don't have an easy way (that I know of yet) to determine when the row insertion from the earlier code is going to be deferred hence causing my later check to fail.
One solution, which feels kludgey, would be to have a BOOL instance variable to track when an earlier delegate method call inserted a row. I don't like that solution, though, so I'm looking for a better option to incorporate into my generic delegate methods.
I'll keep you all updated on my progress, but if you have any ideas how I can determine if there is a pending insert in a table, feel free to share them in the comments.
Update 1: There is a private mutable array called _insertItems that holds the deferred insertions. Even though it's published in the header file, I think accessing this directly would technically be considered use of a private API. Instance variables with an underscore are considered private by Apple, even if published in a header file.
Update 2: I have an illicit functioning version! Unfortunately, I can't use it because it requires accessing private instance variables of UITableView. Once Apple's Bug Reporter is back up, I'm going to put in an enhancement request to have the information I need made public, but I'm probably going to have to come up with a different interim solution, and it will probably be hacky.
For the curious, what I did was to create a category on UITableView that added this method:
- (NSUInteger)numberOfPendingSectionInserts
{
NSUInteger ret = 0;
for (id /* UIUpdateItem */ oneUpdateItem in _insertItems)
{
if ([oneUpdateItem isSectionOperation])
ret++;
}
return ret;
}Now, don't use this in your apps, as you will get rejected from the app store. UIUpdateItem is not a public class, and _insertItems is not a public instance variable (though it's contained in a public header file). Were this information to be made available, then I would be able to do a more robust consistency check that would eliminate the double insertion problem:
NSUInteger tableSectionCount = [self.tableView numberOfSections] + [self.tableView numberOfPendingSectionInserts];
NSUInteger frcSectionCount = [[controller sections] count];
if (frcSectionCount != tableSectionCount)
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];
Indie Relief
In the opinion piece I wrote yesterday, I stated that I didn't want to believe the Mac software market was dying. After thinking it through, I really don't think it is, but today I got yet another reminder of why I don't want it to be true: Indie Mac developers are just great people. That was one of the things that attracted me to Mac development back in the days when Mac development really was a dead-end as a career path.
Case in point: Indie Relief. Over 150 Mac and iPhone developers have banded together to offer all proceeds of the sales from their application to Haiti to help with the relief efforts there. Now, if you know about the economics of indie software development, you should realize that this is a pretty big deal. Few indie developers are living the high life, and many scrape by some months. Yet, all the developers listed on the Indie Relief page are donating every last cent of their income from their application for a period of time. We're not talking about donating $10 or $100 dollars, we're talking about basically handing over paychecks.
Take a look over the list, and if there's some software you've been thinking about buying, now is the perfect time to do it.
Case in point: Indie Relief. Over 150 Mac and iPhone developers have banded together to offer all proceeds of the sales from their application to Haiti to help with the relief efforts there. Now, if you know about the economics of indie software development, you should realize that this is a pretty big deal. Few indie developers are living the high life, and many scrape by some months. Yet, all the developers listed on the Indie Relief page are donating every last cent of their income from their application for a period of time. We're not talking about donating $10 or $100 dollars, we're talking about basically handing over paychecks.
Take a look over the list, and if there's some software you've been thinking about buying, now is the perfect time to do it.
Tuesday, January 19, 2010
Greatly Exaggerated
Several people today tweeted a link to this blog post from John Casasanta of Tap Tap Tap about the death of Mac software. It's an interesting, post, and I'm having trouble deciding if I agree with it or not. I don't want to agree, that's for sure, but there are many valid points made.
My gut reaction, though, from which the title of this post is derived, is to paraphrase Mark Twain by saying the rumors of the Mac Software industry's death have been greatly exaggerated.
One of the assertions in John's post is that iPhone developers don't want to go back and develop for the Mac because the iPhone SDK is "shiny" while Cocoa is "old and crufty". I can't speak for any iPhone developers but me, but I really would like to spend more time with Cocoa. In the nearly two years since I jumped on board the iPhone ship, a lot of really cool things have happened to Cocoa, many of which aren't available to us on the iPhone yet. Blocks, GCD, and OpenCL mean that there are huge opportunities for new Mac applications, and mature garbage collection and instance variable synthesis mean even shorter development times. Heck, there are huge opportunities just to compete with and replace existing consumer applications, never mind for writing new applications. Can you imagine a Photoshop competitor that fully leveraged these new technologies1? Larger companies like Adobe with huge Carbon-based codelines have quite a challenge ahead of them getting their older applications to be 64-bit clean and running on Cocoa, which is a requirement for leveraging much of the cool new stuff. The large corporate software powerhouses are floundering in terms of modernizing their mainstay apps. To say there's not an opportunity there seems wrong to me. It may not be as easy or convenient of an opportunity as represented by the App Store, but there's definitely opportunity.
The Mac's market share is also higher than it's been at any time in at least ten or fifteen years and it seems to be trending up. In terms of actual installed base size, there are more people using Macs than ever in history. Even among people who don't use or like the Mac, the realization that it's not a "toy" operating system is slowly dawning on even the most ignorant of Apple haters. Well, okay, maybe not the most ignorant, but certainly everyone else.
More people are using Mac, so it's hard to imagine how the Mac Software market can be dwindling. If it is, it's likely a failure to take advantage of the opportunities that do exist. Perhaps we're all blinded by the bright, shiny App Store. Maybe stuff's not selling because there's not enough being written or marketed. Maybe we're all still buying into the gold rush stories subconsciously.
There's no doubt that the App Store is a rousing success and that it makes it far easier to reach customers, but hardly every iPhone developer is making a great living at this. TapTapTap is one of the great success stories, and the view from that perspective is very different from the the perspective of developers I've talked to who haven't recouped even enough to have made minimum wage for their time investment in their application. More than one iPhone developer are are looking for greener pastures, though many are having trouble finding one.
I do agree with John on many of the points in his post, however. I agree that it would be great if Apple opened up the App Store to Mac applications, but also agree that it seems unlikely that Apple will do it because they wouldn't have the same level of control. I also sincerely hope that John and I are both wrong on that. I find it odd that I can go into iTunes and buy movies, music, iPhone apps, and even donate to the Red Cross, but I can't buy Mac apps there. I can't even buy Apple's own Mac apps there like iWork and iLife. Last year, I ordered the latest version of iWork, both a single license for my business and a five-license family pack for home. I was able to just buy a serial number for the individual license, but for the family pack, I had to have a box shipped across the country to me. There's something wrong with that picture. I should have been able to just go into iTMS, specify the licenses I needed, then have the software download to my machine automatically. By now, we should have just as seamless and smooth of a buying experience for Mac applications as we do for movies, music, television shows, and mobile apps.
Even without a Mac App Store, though, opportunity is there in the Mac software world. In some ways, the opportunities are better than they've ever been because the potential audience is larger than ever and many of the people who are qualified to create quality Cocoa apps are myopically focused on the iPhone right now. Yes, there's more work involved with Mac apps. You'll have to find a distribution path. You'll have to advertise. You'll have to arrange a payment mechanism. But there are so many targets begging for a good competitor right now, and so many new as-yet uncreated markets that can now exist because of the amount of processing power we can easily leverage in Cocoa. There are many big, slow, corporate-owned, Carbon-based crappy-ass apps that keep making money because they have tons of cash to advertise and because there isn't a viable alternative or, at least, people aren't aware that there is a viable alternative.
We all started on a level playing field in the iPhone nearly two years ago. In fact, it wasn't even level at the start; smaller companies and individuals had the advantage of agility. Hell, a large corporation like Adobe or EA can't decide to enter a new market in the time that some of the earliest iPhone applications were designed, developed, and shipped. Heck, a large corporation often can't even decide who should decide to enter a new market in the time that many iPhone apps were written. TapTapTap was smart enough and capable enough to take advantage of that opportunity, but people entering the iPhone market today have to compete with the big names and the established small names.
Even though the distribution situation is considerably better on the iPhone than on the Mac, the overall competitive landscape really isn't all that different when you look at the market as a whole. How many of the top-ten grossing games right now are titles from big-name companies? Usually, it seems to run between seven and ten of the top ten are big-name titles. On the other hand, what percentage of successful Mac titles are produced by independents? I have to believe it runs at least 10-30%, and I would guess it runs higher.
I don't see the markets as being nearly as different as John does for somebody starting from ground zero today. There are differences, certainly, but there's plenty of room for success — and failure — in both markets.
1- Actually, I can. A couple of years ago, I abandoned Photoshop for Acorn, which is a native Cocoa image editor that rocks. There are a few features that some design professionals might need that it doesn't have (e.g. CMYK support), but what it does, it does so much faster than Photoshop CS4 that it's not even a close race, and yet it costs a fraction of what Photoshop costs. And think about this: Acorn is mostly written by one person. Compare that with the names in Photoshop's dialog box.
My gut reaction, though, from which the title of this post is derived, is to paraphrase Mark Twain by saying the rumors of the Mac Software industry's death have been greatly exaggerated.
One of the assertions in John's post is that iPhone developers don't want to go back and develop for the Mac because the iPhone SDK is "shiny" while Cocoa is "old and crufty". I can't speak for any iPhone developers but me, but I really would like to spend more time with Cocoa. In the nearly two years since I jumped on board the iPhone ship, a lot of really cool things have happened to Cocoa, many of which aren't available to us on the iPhone yet. Blocks, GCD, and OpenCL mean that there are huge opportunities for new Mac applications, and mature garbage collection and instance variable synthesis mean even shorter development times. Heck, there are huge opportunities just to compete with and replace existing consumer applications, never mind for writing new applications. Can you imagine a Photoshop competitor that fully leveraged these new technologies1? Larger companies like Adobe with huge Carbon-based codelines have quite a challenge ahead of them getting their older applications to be 64-bit clean and running on Cocoa, which is a requirement for leveraging much of the cool new stuff. The large corporate software powerhouses are floundering in terms of modernizing their mainstay apps. To say there's not an opportunity there seems wrong to me. It may not be as easy or convenient of an opportunity as represented by the App Store, but there's definitely opportunity.
The Mac's market share is also higher than it's been at any time in at least ten or fifteen years and it seems to be trending up. In terms of actual installed base size, there are more people using Macs than ever in history. Even among people who don't use or like the Mac, the realization that it's not a "toy" operating system is slowly dawning on even the most ignorant of Apple haters. Well, okay, maybe not the most ignorant, but certainly everyone else.
More people are using Mac, so it's hard to imagine how the Mac Software market can be dwindling. If it is, it's likely a failure to take advantage of the opportunities that do exist. Perhaps we're all blinded by the bright, shiny App Store. Maybe stuff's not selling because there's not enough being written or marketed. Maybe we're all still buying into the gold rush stories subconsciously.
There's no doubt that the App Store is a rousing success and that it makes it far easier to reach customers, but hardly every iPhone developer is making a great living at this. TapTapTap is one of the great success stories, and the view from that perspective is very different from the the perspective of developers I've talked to who haven't recouped even enough to have made minimum wage for their time investment in their application. More than one iPhone developer are are looking for greener pastures, though many are having trouble finding one.
I do agree with John on many of the points in his post, however. I agree that it would be great if Apple opened up the App Store to Mac applications, but also agree that it seems unlikely that Apple will do it because they wouldn't have the same level of control. I also sincerely hope that John and I are both wrong on that. I find it odd that I can go into iTunes and buy movies, music, iPhone apps, and even donate to the Red Cross, but I can't buy Mac apps there. I can't even buy Apple's own Mac apps there like iWork and iLife. Last year, I ordered the latest version of iWork, both a single license for my business and a five-license family pack for home. I was able to just buy a serial number for the individual license, but for the family pack, I had to have a box shipped across the country to me. There's something wrong with that picture. I should have been able to just go into iTMS, specify the licenses I needed, then have the software download to my machine automatically. By now, we should have just as seamless and smooth of a buying experience for Mac applications as we do for movies, music, television shows, and mobile apps.
Even without a Mac App Store, though, opportunity is there in the Mac software world. In some ways, the opportunities are better than they've ever been because the potential audience is larger than ever and many of the people who are qualified to create quality Cocoa apps are myopically focused on the iPhone right now. Yes, there's more work involved with Mac apps. You'll have to find a distribution path. You'll have to advertise. You'll have to arrange a payment mechanism. But there are so many targets begging for a good competitor right now, and so many new as-yet uncreated markets that can now exist because of the amount of processing power we can easily leverage in Cocoa. There are many big, slow, corporate-owned, Carbon-based crappy-ass apps that keep making money because they have tons of cash to advertise and because there isn't a viable alternative or, at least, people aren't aware that there is a viable alternative.
We all started on a level playing field in the iPhone nearly two years ago. In fact, it wasn't even level at the start; smaller companies and individuals had the advantage of agility. Hell, a large corporation like Adobe or EA can't decide to enter a new market in the time that some of the earliest iPhone applications were designed, developed, and shipped. Heck, a large corporation often can't even decide who should decide to enter a new market in the time that many iPhone apps were written. TapTapTap was smart enough and capable enough to take advantage of that opportunity, but people entering the iPhone market today have to compete with the big names and the established small names.
Even though the distribution situation is considerably better on the iPhone than on the Mac, the overall competitive landscape really isn't all that different when you look at the market as a whole. How many of the top-ten grossing games right now are titles from big-name companies? Usually, it seems to run between seven and ten of the top ten are big-name titles. On the other hand, what percentage of successful Mac titles are produced by independents? I have to believe it runs at least 10-30%, and I would guess it runs higher.
I don't see the markets as being nearly as different as John does for somebody starting from ground zero today. There are differences, certainly, but there's plenty of room for success — and failure — in both markets.
1- Actually, I can. A couple of years ago, I abandoned Photoshop for Acorn, which is a native Cocoa image editor that rocks. There are a few features that some design professionals might need that it doesn't have (e.g. CMYK support), but what it does, it does so much faster than Photoshop CS4 that it's not even a close race, and yet it costs a fraction of what Photoshop costs. And think about this: Acorn is mostly written by one person. Compare that with the names in Photoshop's dialog box.
Monday, January 18, 2010
January 27th is On
Media Invites for the special January 27th event at the Yerba Buena Gardens are now out and it's official. It's still not official what's being announced, though most people are assuming it will be the fabled and long-awaited tablet.
Subscribe to:
Posts (Atom)