Thursday, July 23, 2009

Interesting Contrast

Definitely a sign of the times: Two days ago, Apple announced another best non-holiday quarter ever, handily beating analysts' predictions. Today, the once-unstoppable Microsoft reported a 17% drop in revenue, marking the first full-year decline in revenue in Microsoft's history and missing analysts predictions by a wide margin.

It's obviously a good time to be a Mac user. It's been a long time since I've heard anyone predict the death of Apple (a favorite pasttime of analysts and pundits for years) and ignorant comments about Macs being "toys" or "not for serious work" are almost non-existent except for a few bitter and lonely commenters on Reddit and Digg.

The problems Microsoft is having, no they are not Microsoft's fault. It's the global slowdown and other things that they have no control over and couldn't have possibly predicted.

Let's face it, Microsoft has "Victim Syndrome". They're not fixing their problems because they haven't truly accepted that they're doing anything wrong. They go so far as to pat themselves on the back for cutting costs and laying people off, saying
In light of that environment, it was an excellent achievement to deliver over $750 million of operational savings compared to the prior year quarter.
Seriously? Operational savings? That's a pussy way of putting it, but I guess "layoffs" sounds too negative. And how does the word "excellent" even get worked into reporting a substantial decline in revenue? And how do they title their release?
The company delivered operational efficiency and innovation in a difficult environment
Now, compare that with Apple's post-Jobs earnings statement from December 2000 where they had some similar problems. Right in the title, they called their results "disappointing". Although Apple pointed out the global PC slump as a contributing factor, the reasons they gave for the loss were that they didn't plan well enough. Apple's approach was, when you cut through the anaylst-speak, to say "we fucked up, we know it, and we're going to fix it". They acknowledged the problem.

Microsoft, on the other hand, is trying to convince everyone (themselves included, perhaps) that they are still headed in the right direction and are doing just great, despite overwhelming evidence to the contrary. What they should be telling us is that they've got problems and they know it, and they've got a plan for fixing those problems.

Microsoft needs an intervention, badly. They need somebody to be a real friend and tell them the harsh truth: that they've got a problem and they need help.

I've said it before, but I think Microsoft is desperately overdue for a change of leadership. It takes a long time to turn a big ship around, so the sooner Microsoft can get someone with vision at the helm, the better it will be for Microsoft.



Waving the Red Flag

Craig Hockenberry has just posted a very important piece of information for anyone with applications on the App Store.. Go read it.



Microsoft's Ads Redux

A while back, I took Microsoft's new Laptop Hunter series of television advertisements to task for being ill-advised. This line pretty much summed up my point:
Price is the competitive advantage of generic brands and sweatshop-backed superstores.
and this was the crux of my argument:
Now, this [approach] will resonate with some people. There are always people who take a certain pride in buying things cheaply under the assumption that anytime you pay less you get a better value and it's a waste of time comparing the actual products. I doubt that people who think like that are a particularly large percentage of the population. Maybe they are, and maybe this is a brilliant ad, and I'm simply overestimating people. Only time will tell, but I suspect that these ads will help Apple as much as they help Microsoft and, if they have any effect at all, it will simply be to polarize the consumer market even further, giving Apple more of the higher-end, higher-profit sales, and cementing Windows as the operating system of the "cheap" computer. Most people will interpret "just as good" as meaning "not as good".
To the extent that I predicted that Microsoft's ads would not have an impact, I was completely 100% wrong. These ads have almost definitely impacted computer purchasing decisions over the last several months. However, unfortunately for Microsoft, the effect the ads seem to have had is exactly what I predicted. The PC market is getting polarized, with Apple becoming the OS of choice for high-end, high-margin computers.

Microsoft still has overall market share probably in the 80-90% range, which is more than substantial. In fact, it can't be described in any other way than "dominant". There are very few industries where any player has that kind of market share.

But, the bulk of the new computers being sold with Windows on them are sub-$1,000 systems. If you look at the systems being sold in that price range, they are mostly computers with outdated components like slower (and less) RAM and older integrated graphics chips. Now, there are plenty of consumers in this price range, and Microsoft is still making a lot of money from their OEM sales.

But, Apple is now dominating the more lucrative, higher-margin computers, and that dominance has been trending up fairly rapidly.

This is bad for Microsoft in a number of ways. First, the higher the percentage of low-end, basically obsolete computers that there are running Windows, the smaller the potential market for cool new OS-level features in future versions of Windows is. That means that the opportunities for selling higher-margin retail copies of Windows 7 to existing customers (as opposed to those being sold with a new computer) will be considerably less than the installed base. It's also bad for Microsoft's dominance in the PC game industry. One of the most commonly leveled (and perfectly true) accusations against the Mac is that there aren't many games for it. Cutting edge games, however, usually want to leverage the power of the latest and greatest hardware. If 91% of the systems costing $1,000 or more being sold are Macs, don't think game companies aren't going to take notice.

But, the biggest and worst problem for Microsoft is just what I stated above. They're setting themselves up as a generic brand. By competing only on price, and touting only price in their advertisements, rather than the actual technological advantages they do have (and they definitely do have some), their current marketing campaigns are instilling and reinforcing the idea that Microsoft creates bland, mediocre, but affordable and serviceable products. They are basically saying the same thing in their ads as Apple is.

You can argue all day long that your product is better, but if that runs contrary to what you're telling consumers with your marketing, then you're just pissing in the wind. In terms of future sales, it doesn't matter whether Windows is better, it matters whether consumers believe it is better, and Microsoft has stopped telling consumers that.



Wednesday, July 22, 2009

August 14 Workshop Update


The August 14 workshop, for which I am one of the two instructors is filling up fast. I'll be teaching the second part of the workshop on iPhone development; Steve Kochan, author of Programming in Objective-C 2.0, will be teaching the first part on Objective-C. You can sign up for either the 3-day iPhone session, the 3-day Objective-C session, or do the combined 6-day workshop.



Tuesday, July 21, 2009

To VBO or Not to VBO...

Daniel Pasco of Black Pixel Luminance has an interesting blog post today on the comparative performance between using VBOs and not using VBOs.

Surprising results, to say the least, and it really makes you wonder why Apple is recommending VBOs for the iPhone.



Improved Blender Export

Thanks to some help from some readers, I've got a new and improved version of the Blender Export Script for Objective-C. This new version works correctly regardless of whether there's a texture. If you've used vertex paint, it will export the vertex colors. If you have neither vertex colors or a mapped texture, then it will just export the vertices and normals. It will also update the drawing code as appropriate to the data that's been exported.

Also, new with this version, you don't need to triangulate the faces, nor do you need to apply modifiers. Those will happen automatically in the export without affecting your original model. That should make it easier to update and maintain the models you use in your iPhone apps.

You can download the new script from the link above. I'm including the script here for the curious:

Note of warning: I have not tested this script very thoroughly, so caveat emptor! Fixes and improvement suggestions welcome, as always.

#!BPY

"""
Name: 'Objective-C Header (.h)'
Blender: 244
Group: 'Export'
Tooltip: 'Exports header file for use with the OpenGL ES template for iPhone available from http://iphonedevelopment.blogspot.com/'
"""

import Blender
from Blender import *
import bpy
import bpy
import os


def write_obj(filepath):
out = file(filepath, 'w')
sce = bpy.data.scenes.active
ob = sce.objects.active
mesh = Mesh.New()
mesh.getFromObject(ob.name)

editmode = Window.EditMode()
if editmode: Window.EditMode(0)
has_quads = False
for f in mesh.faces:
if len(f) == 4:
has_quads = True
break

if has_quads:
oldmode = Mesh.Mode()
Mesh.Mode(Mesh.SelectModes['FACE'])

mesh.sel = True
tempob = sce.objects.new(mesh)
mesh.quadToTriangle(0) # more=0 shortest length
oldmode = Mesh.Mode(oldmode)
sce.objects.unlink(tempob)

Mesh.Mode(oldmode)

objectname = ob.getData(True)
basename = objectname.capitalize()

out.write('#import "OpenGLCommon.h"\n\n\n')

if (mesh.faceUV):
out.write('static const TexturedVertexData3D %sVertexData[] = {\n' % basename)
for face in mesh.faces:
for (vert, uvert) in zip(face.verts, face.uv):
out.write('\t{/*v:*/{%f, %f, %f}, ' % (vert.co.x, vert.co.y, vert.co.z) )
out.write('/*n:*/{%f, %f, %f}, ' % (vert.no.x, vert.no.y, vert.no.z))
out.write('/*t:*/{%f, %f}' % ( uvert.x, uvert.y ) )
out.write('},\n')
out.write('};\n\n')
elif (mesh.vertexColors):
out.write('static const ColoredVertexData3D %sVertexData[] = {\n' % basename)
for face in mesh.faces:
for (vert, color) in zip(face.verts, face.col):
out.write('\t{/*v:*/{%f, %f, %f}, ' % (vert.co.x, vert.co.y, vert.co.z) )
out.write('/*n:*/{%f, %f, %f}, ' % (vert.no.x, vert.no.y, vert.no.z))
out.write('/*c:*/{%f, %f, %f, %f}' % ( color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0) )
out.write('},\n')
out.write('};\n\n')
else:
out.write
out.write('static const VertexData3D %sVertexData[] = {\n' % basename)
for face in mesh.faces:
for vert in face.verts:
out.write('\t{/*v:*/{%f, %f, %f}, ' % (vert.co.x, vert.co.y, vert.co.z) )
out.write('/*n:*/{%f, %f, %f} ' % (vert.no.x, vert.no.y, vert.no.z))
out.write('},\n')
out.write('};\n\n')

if editmode: Window.EditMode(1)
out.write('#define k%sNumberOfVertices\t%i\n' % (basename, len(mesh.faces) * 3) )

out.write('// Drawing Code:\n')
out.write('// glEnableClientState(GL_VERTEX_ARRAY);\n')
if (mesh.faceUV):
out.write('// glEnableClientState(GL_TEXTURE_COORD_ARRAY);\n')
elif (mesh.vertexColors):
out.write('// glEnableClientState(GL_COLOR_ARRAY);\n')
out.write('// glEnable(GL_COLOR_MATERIAL)\n')
out.write('// glEnableClientState(GL_NORMAL_ARRAY);\n')
out.write('// glVertexPointer(3, GL_FLOAT, sizeof(TexturedVertexData3D), &%sVertexData[0].vertex);\n' % basename)
out.write('// glNormalPointer(GL_FLOAT, sizeof(TexturedVertexData3D), &%sVertexData[0].normal);\n' % basename)
if (mesh.faceUV):
out.write('// glTexCoordPointer(2, GL_FLOAT, sizeof(TexturedVertexData3D), &%sVertexData[0].texCoord);\n' % basename)
elif (mesh.vertexColors):
out.write('// glColorPointer(4, GL_FLOAT, sizeof(ColoredVertexData3D), &%sVertexData[0].color);\n' % basename)
out.write('// glDrawArrays(GL_TRIANGLES, 0, k%sNumberOfVertices);\n' % basename)
out.write('// glDisableClientState(GL_VERTEX_ARRAY);\n')
if (mesh.faceUV):
out.write('// glDisableClientState(GL_TEXTURE_COORD_ARRAY);\n')
elif (mesh.vertexColors):
out.write('// glDisableClientState(GL_NORMAL_ARRAY);\n')
out.write('// glDisable(GL_COLOR_MATERIAL);\n')
out.write('// glDisableClientState(GL_NORMAL_ARRAY);\n\n\n')

out.close()


filename = os.path.splitext(Blender.Get('filename'))[0]
Blender.Window.FileSelector(write_obj, "Export", '%s.h' % filename)


Thanks to Scott Lyons and Dennis Ippel for their Python and Blender API smarts. I couldn't have gotten this far without their help.



Monday, July 20, 2009

Refactoring Nav from Chapter 9

I received an interesting question (in the form of a tweet) today about Chapter 9. An observant reader asked if there was a way to "DRY" (Don't Repeat Yourself) the code where we add all the controllers to the array that drives the root view controller's table, this code here:

- (void)viewDidLoad {
self.title = @"First Level";
NSMutableArray *array = [[NSMutableArray alloc] init];

// Disclosure Button
DisclosureButtonController *disclosureButtonController =
[[DisclosureButtonController alloc]
initWithStyle:UITableViewStylePlain]
;
disclosureButtonController.title = @"Disclosure Buttons";
disclosureButtonController.rowImage = [UIImage
imageNamed:@"disclosureButtonControllerIcon.png"]
;
[array addObject:disclosureButtonController];
[disclosureButtonController release];

// Check List
CheckListController *checkListController = [[CheckListController alloc]
initWithStyle:UITableViewStylePlain]
;
checkListController.title = @"Check One";
checkListController.rowImage = [UIImage imageNamed:
@"checkmarkControllerIcon.png"
]
;
[array addObject:checkListController];
[checkListController release];

// Table Row Controls
RowControlsController *rowControlsController =
[[RowControlsController alloc]
initWithStyle:UITableViewStylePlain]
;
rowControlsController.title = @"Row Controls";
rowControlsController.rowImage = [UIImage imageNamed:
@"rowControlsIcon.png"
]
;
[array addObject:rowControlsController];
[rowControlsController release];


// Move Me
MoveMeController *moveMeController = [[MoveMeController alloc]
initWithStyle:UITableViewStylePlain]
;
moveMeController.title = @"Move Me";
moveMeController.rowImage = [UIImage imageNamed:@"moveMeIcon.png"];
[array addObject:moveMeController];
[moveMeController release];

// Delete Me
DeleteMeController *deleteMeController = [[DeleteMeController alloc]
initWithStyle:UITableViewStylePlain]
;
deleteMeController.title = @"Delete Me";
deleteMeController.rowImage = [UIImage imageNamed:@"deleteMeIcon.png"];
[array addObject:deleteMeController];
[deleteMeController release];

// President View/Edit
PresidentsViewController *presidentsViewController =
[[PresidentsViewController alloc]
initWithStyle:UITableViewStylePlain]
;
presidentsViewController.title = @"Detail Edit";
presidentsViewController.rowImage = [UIImage imageNamed:
@"detailEditIcon.png"
]
;
[array addObject:presidentsViewController];
[presidentsViewController release];

self.controllers = array;
[array release];
[super viewDidLoad];
}

It's a good spot. This is, in fact, a prime candidate for refactoring. Notice how similar all the chunks of code are. With the exception of the controller class, title, and image name, each chunk of code is basically identical.

The answer to whether this can be DRY'ed, yes. This can be refactored in Objective-C and probably should. We didn't do it in the book basically because Chapter 9 was already long enough without having to use Class objects or the Objective-C runtime, and we were concerned this would add something confusing to an already long and difficult chapter.

But, my blog doesn't have to be only beginner friendly, so let's look at how we could refactor this chunk of code. First and foremost, let's start by changing the controllers property from an NSArray to an NSMutableArray so its contents can be modified by an instance method.

#import <Foundation/Foundation.h>


@interface FirstLevelViewController : UITableViewController {
NSMutableArray *controllers;
}

@property (nonatomic, retain) NSMutableArray *controllers;
@end


Next, we can create a method that will add a controller to that array. Since the items that are not the same between the various chunks of code are the controller class, the title, and the image name, we need the method to take arguments for each of those.

If we know and have access at compile time to all the classes that we will be using, we can do this pretty easily by creating a method that takes a Class object. This is the object that represents the singleton meta-object that exists for every Objective-C class. When you call a class method, you are actually calling a method on this object and you can call class methods on Class objects. So, in this scenario where we know all the classes we'll be using, we can write this method:

- (void)addControllerOfClass:(Class)controllerClass usingTitle:(NSString *)title withImageNamed:(NSString *)imageName {
SecondLevelViewController *controller = [[controllerClass alloc] initWithStyle:UITableViewStylePlain];
controller.title = title;
controller.rowImage = [UIImage imageNamed:imageName];
[self.controllers addObject:controller];
[controller release];
}

We create an instance of the correct class by calling alloc on the Class object, which returns an instance, which we can then initialize ordinarily. We declare this an instance to be the abstract superclass of all the second level controllers, SecondLevelViewController, which allows us to use both the title and rowImage properties without having to typecast or set the values by key.

Then, our viewDidLoad method becomes much, much shorter and without all the repeated code:

- (void)viewDidLoad {
self.title = @"First Level";
NSMutableArray *array = [[NSMutableArray alloc] init];
self.controllers = array;
[array release];

[self addControllerOfClass:[DisclosureButtonController class] usingTitle:@"Disclosure Buttons" withImageNamed:@"disclosureButtonControllerIcon.png"];
[self addControllerOfClass:[CheckListController class] usingTitle:@"Check One" withImageNamed:@"checkmarkControllerIcon.png"];
[self addControllerOfClass:[RowControlsController class] usingTitle:@"Row Controls" withImageNamed:@"rowControlsIcon.png"];
[self addControllerOfClass:[MoveMeController class] usingTitle:@"Move Me" withImageNamed:@"moveMeIcon.png"];
[self addControllerOfClass:[DeleteMeController class] usingTitle:@"Delete Me" withImageNamed:@"deleteMeIcon.png"];
[self addControllerOfClass:[PresidentsViewController class] usingTitle:@"Detail Edit" withImageNamed:@"detailEditIcon.png"];

[super viewDidLoad];
}

But, what if you don't know all the classes at compile time? Say, if you want to create a generic class to go into a static library? You can still do it, but you lose the compile-time check for the class and have to use an Objective-C runtime method to derive a Class object from the name of the class. Easy enough, though. Under that scenario, here's our new method:

- (void)addControllerOfName:(NSString *)controllerClassName usingTitle:(NSString *)title withImageNamed:(NSString *)imageName {

Class controllerClass = objc_getClass([controllerClassName UTF8String]);
SecondLevelViewController *controller = [[controllerClass alloc] initWithStyle:UITableViewStylePlain];
controller.title = title;
controller.rowImage = [UIImage imageNamed:imageName];
[self.controllers addObject:controller];
[controller release];
}

Notice that the only difference is that we take an NSString * parameter rather than a Class parameter, and then we get the correct Class object using the Objective-C runtime function called objc_getClass(). This function actually takes a C-string, not an NSString, so we get a C-string using the UTF8String instance method on our NSString.

In this case, we have to change our viewDidLoad method slightly to pass string constants, rather than Class objects:

- (void)viewDidLoad {
self.title = @"First Level";
NSMutableArray *array = [[NSMutableArray alloc] init];
self.controllers = array;
[array release];

[self addControllerOfName:@"DisclosureButtonController" usingTitle:@"Disclosure Buttons" withImageNamed:@"disclosureButtonControllerIcon.png"];
[self addControllerOfName:@"CheckListController" usingTitle:@"Check One" withImageNamed:@"checkmarkControllerIcon.png"];
[self addControllerOfName:@"RowControlsController" usingTitle:@"Row Controls" withImageNamed:@"rowControlsIcon.png"];
[self addControllerOfName:@"MoveMeController" usingTitle:@"Move Me" withImageNamed:@"moveMeIcon.png"];
[self addControllerOfName:@"DeleteMeController" usingTitle:@"Delete Me" withImageNamed:@"deleteMeIcon.png"];
[self addControllerOfName:@"PresidentsViewController" usingTitle:@"Detail Edit" withImageNamed:@"detailEditIcon.png"];

[super viewDidLoad];
}


Either of these options will be much easier to maintain and extend than the version in the book. You should be on the lookout for refactoring opportunities in your own code, as well. Sometimes an ounce of refactoring can save a pound of headache down the line.