Showing posts with label Tips. Show all posts
Showing posts with label Tips. Show all posts

Thursday, July 14, 2011

Auto-Incrementing Build Numbers for Release Builds in Xcode

I use the wonderful Test Flight to distribute builds. One thing that Test Flight is a little picky about is build numbers. When you upload a build, it uses the build number to see if you're uploading a replacement or a new build. It will let you create a new build even if you don't remember to increment the build number, but it's an extra manual step, and then you end up with two builds with the same build number.

Because I'm forgetful, I wanted to automated this process. Basically, I wanted to increment the version short string any time we do an Archive and increment the bundle build ID any time we do a Release configuration build, but leave the version numbers alone on Debug builds.

Unfortunately, several of our projects are ones that we inherited or took over, so not every project uses the same version numbering scheme. How we increment 1.0b5 is different from how we increment 1.0.12, or a simple build number like 1058.

The way I deal with this is a Run Script Build Phase in my application's executable target that runs the following Ruby script (make sure you set the "shell" field to /usr/bin/ruby, and make sure the script is the last build phase in the application). Feel free to use this script if you wish and modify it to meet your needs. If you improve it, I'd be glad to incorporate improvements back into it. One item of note: the way that I differentiate between Archive builds and other Release configuration builds might be a bit fragile since I'm relying on an undocumented naming pattern in an environment variable.

Note: I'm aware of agvtool. I avoided it for two reasons. First, I wanted more control over the numbering schemes, and second, I tried using agvtool in a build script a few years back, but at that time, there were issues when you bumped the version numbers of a project that was currently open. Those issues may have been resolved, but I didn't want to fight that battle again.

def get_file_as_string(filename)
data = ''
f = File.open(filename, "r")
f.each_line do |line|
data += line
end
return data
end


def handle_alpha_beta(old_value, letter, infoplist, start_of_value, end_of_value)
parts = old_value.split(letter)
version_num = parts[0]
alpha_num = parts[1].to_i

alpha_num = alpha_num + 1
new_version = version_num.to_s + letter + alpha_num.to_s
print "Assigning new version: " + new_version + "\n"
new_key = "<string>#{new_version}</string>"

part_1 = infoplist[0, start_of_value - '<string>'.length];
part_2 = new_key
part_3 = infoplist[end_of_value + "</string>".length, infoplist.length - (end_of_value - start_of_value + (new_key.length - 1))]

new_info_plist = part_1 + part_2 + part_3
new_info_plist
end

def find_and_increment_version_number_with_key(key, infoplist)

start_of_key = infoplist.index(key)
start_of_value = infoplist.index("<string>", start_of_key) + "<string>".length
end_of_value = infoplist.index("</string>", start_of_value)
old_value = infoplist[start_of_value, end_of_value - start_of_value]

print "Old version for " + key + ": " + old_value + "\n"
print old_value.class.to_s + "\n"
old_value_int = old_value.to_i
print old_value_int.class.to_s + "\n"
if (old_value.index("a") != nil) # alpha
infoplist = handle_alpha_beta(old_value, "a", infoplist, start_of_value, end_of_value)
elsif (old_value.index("b") != nil) # beta
infoplist = handle_alpha_beta(old_value, "b", infoplist, start_of_value, end_of_value)
elsif (old_value.index(".") != nil) # release dot version
parts = old_value.split(".")
last_part = parts.last.to_i
last_part = last_part + 1
parts.delete(parts.last)

new_version = ""
first = true
parts.each do |one_part|
if (first)
first = false
else
new_version = new_version + "."
end
new_version = new_version + one_part
end
new_version = new_version.to_s + "." + last_part.to_s
print "New version: " + new_version.to_s + "\n"
new_key = "<string>#{new_version}</string>"
infoplist = "#{infoplist[0, start_of_value - '<string>'.length]}#{new_key}#{infoplist[end_of_value + '</string>'.length, infoplist.length - (end_of_value+1)]}"
elsif (old_value.to_i != nil) # straight integer build number
new_version = old_value.to_i + 1
print "New version: " + new_version.to_s + "\n"
new_key = "<string>#{new_version}</string>"

part_1 = infoplist[0, start_of_value - '<string>'.length]
part_2 = new_key
part_3 = infoplist[end_of_value + "</string>".length, infoplist.length - (end_of_value+1)]
infoplist = part_1 + part_2 + part_3
end
infoplist
end



config = ENV['CONFIGURATION'].upcase
config_build_dir = ENV['CONFIGURATION_BUILD_DIR']

archive_action = false
if (config_build_dir.include?("ArchiveIntermediates"))
archive_action = true
end

print "Archive: " + archive_action.to_s + "\n"


print config

if (config == "RELEASE")
print " incrementing build numbers\n"
project_dir = ENV['PROJECT_DIR']
infoplist_file = ENV['INFOPLIST_FILE']
plist_filename = "#{project_dir}/#{infoplist_file}"

infoplist = get_file_as_string(plist_filename)
infoplist = find_and_increment_version_number_with_key("CFBundleVersion", infoplist)
if (archive_action)
infoplist = find_and_increment_version_number_with_key("CFBundleShortVersionString", infoplist)
end
File.open(plist_filename, 'w') {|f| f.write(infoplist) }
else
print " not incrementing build numbers"
end

Thursday, August 5, 2010

Xcode 3, Instruments 4

This was a bit of a "duh" moment for me, but probably worth mentioning, despite being a "Captain Obvious" post.

I'm a big fan of what Apple's doing with Xcode 4, but in my opinion it's just not ready for production use yet. I'm using it for the book and occasional forays, but not for regular client work yet. However, I also run my machine using the 64-bit kernel, which means I can't use Shark without rebooting. I've been really looking forward to the Time Profiler template in the 4.0 version of Instruments as well as the two new OpenGL ES templates and the Energy Diagnostics template.

But, while grumbling about their lack in the current release version of Instruments, it struck me: you don't have to launch Instruments from the same folder you launch Xcode from. You can launch Instruments separately and attach it to a running process in the Simulator or on an attached iPhone. That means, you can launch Instruments from the Xcode4 folder instead of from the Developer folder. This allows you to develop and debug with the less-shiny-but-production-ready Xcode 3.x and also use the new Instruments templates like Time Profiler, OpenGL ES, and Energy Diagnostics.

Monday, August 2, 2010

Core Data Odds and Ends

I've been doing a fair bit of work with Core Data lately, and have picked up a few tricks to make my life easier, so I thought I'd share.

Mo' Power


Back in the EOF days, there was a great little piece of third-party software called EOGenerator that implemented something called the generation gap design pattern. Essentially, this little program would create your custom subclasses of EOGenericRecord (EOF's equivalent of NSManagedObject) for you. It wouldn't just create that, though, it would actually create two classes for each entity in your EOModel.

One of these two classes was yours to modify as you saw fit. It would never get overwritten even if you ran the EOGenerator script again, over and over. The other class contained all the generated code derived from your EOModel and was designed not to be touched by the programmer. You had your class, EOGenerator had its class, and you could both work without any fear of stepping on each other's toes. You could add attributes and entities to your data model and regenerate the machine-readable classes without any chance of losing your custom validation or methods. It was nice.

Wolf Rentzsch has re-implemented this idea for Core Data, and the result is called MOGenerator. If you spend any time with Core Data and aren't familiar with MOGenerator, go become familiar with it. It will make your life much, much, much better.

SQLite to Check Data


The default persistent store type for Core Data under iOS is a SQLite store, which means that Core Data keeps all of its data inside of a single SQLite database in your application's /Documents folder. Now, the format of the persistent store is undocumented and you should never, ever, ever, ever, never change data in the persistent store or make any assumptions about how Core Data stores the data. That's part of why we use Core Data: It's an abstraction layer that lets us not worry about the underlying storage details.

But, sometimes, it can be really helpful to know whether data is getting into the persistent store or not. I often work on my data model before my user interface, so being able to peek in the data store to make sure all is well is really helpful.

The first thing you might want to consider doing, if you plan to do that, is to create a SQLite config file. This is a file of commands that will execute whenever you launch SQLite. To do that, simply create a file called .sqliterc in your home directory. Here's what mine looks like:
.mode column
.header on
.table
In mine, the first line tells SQLite to print the column names when I issue a SELECT command. The second line tells SQLite to present the data in tabular form. The last line prints out a list of all the tables in the database before it shows me the SQL command prompt. That's handy given that the table names don't usually exactly match your entity or class names.

Once you have a config file in place, you can open up any persistent store using the sqlite3 command in Terminal.app, like so:
sqlite3 MyApplication.sqlite
You can't, of course, execute this directly against database files on your phone, but you can do it in the simulator as well as on files copied from your phone using Xcode's Organizer. If you're using the simulator, you can look for your SQLite databases at the following location:
/Users/[your account name]/Library/Application Support/iPhone Simulator/[SDK version #]/Applications/[application UUID]/Documents/[Application Name].sqlite


When I log into a database with my SQLite config file, this is what I see:
-- Loading resources from /Users/jeff/.sqliterc
ZHERO ZPOWER Z_METADATA Z_PRIMARYKEY
SQLite version 3.6.12
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite>
Notice that it has dumped the names of all my tables. It's pretty easy to figure out which table holds what data once they're right in front of you. The example above is the persistent store from the SuperDB application from More iPhone 3 Development. The Hero entity managed objects are stored in ZHERO and the Power entity managed objects are stored in ZPOWER. To look at the contents of a table, you just need to execute a SQL SELECT command. The simplest is just SELECT * FROM TABLENAME;, which prints the entire contents of the table to the console, like so:
sqlite> select * from ZPOWER;
Z_PK Z_ENT Z_OPT ZHERO ZSOURCE ZNAME
---------- ---------- ---------- ---------- ---------- ----------
1 2 2 1 Mutation Fire
sqlite>
The format of the data is pretty easy to figure out but, again, you really should look and not touch. It'd be very easy to accidentally destroy a persistent store. A seemingly inconsequential change could get the metadata out of whack and prevent your app from using the persistent store any more.

Better Descrption


I noticed a few days ago that Wil Shipley was complaining about the fact that many of Apple's delivered UIKit and Foundation objects have not overridden the -description method. This method is important because it's the one that is used in format strings, NSLog(), and when you type the po command in GDB. Instead of getting a meaningful description of many delivered objects, you instead get the class name and memory address of the instance, which is what NSObject's implementation provides, which isn't very much help while debugging.

Unfortunately, NSManagedObject is one of the many, many classes that doesn't have its own meaningful implementation of -description. That doesn't mean you can't provide one, though.

One thing you can do is to create an abstract subclass of NSManagedObject and put the -description method there. If you then subclass this abstract class, all of your managed objects will inherit the method from the abstract class. If I have other common functionality, I'll often do exactly that. But if I don't (and often you won't) I personally hate to clutter up my object hierarchy to add debugging tools. So I actually will cheat and do something you're not really supposed to do, which is override a method using a category. The reason it's bad to do this is because the Objective-C language does not specify which method should be used, which can lead to unpredictable behavior. But, when a class inherits a method from a superclass, I've found that overriding it in a category always works. I'm not suggesting you should do this in a shipping app, however. I wrap my method in pre-compiler definitions so that it doesn't get compiled into the release version of my application.

My version of -description assumes you're generating properties for your managed objects (I always do) and also assumes you're using custom subclasses (but you are, because you're using MOGenerator, right??). The method iterates over all the properties of the class and all its superclasses up to NSManagedObject, then prints them to the console. The categories I use look like this:
#if DEBUG
@implementation NSObject(MCManagedObjectDebug)
+ (NSMutableArray *)MCproperties
{
NSMutableArray *properties = nil;

if ([self superclass] != [NSManagedObject class])
properties = [[self superclass] MCproperties];
else
properties = [NSMutableArray array];


unsigned int propCount;
objc_property_t * propList = class_copyPropertyList([self class], &propCount);
int i;

for (i=0; i < propCount; i++)
{
objc_property_t oneProp = propList[i];
NSString *propName = [NSString stringWithUTF8String:property_getName(oneProp)];
if (![properties containsObject:propName])
[properties addObject:propName];
}

return properties;
}

@end


@implementation NSManagedObject(MCManagedObjectDebug)
- (NSString *)description
{
NSArray *properties = [[self class] MCproperties];
NSMutableString *ret = [NSMutableString stringWithFormat:@"%@:", [self className]];
NSDictionary *myAttributes = [[self entity] attributesByName];

for (NSString *oneProperty in properties)
{
NSAttributeDescription *oneAttribute = [myAttributes valueForKey:oneProperty];
if (oneAttribute != nil) // If not, it's a relationship or fetched property
{
id value = [self valueForKey:oneProperty];
[ret appendFormat:@"\n\t%@ = %@", oneProperty, value];
}


}

return ret;
}


@end
#endif
The +MCProperties method uses the Objective-C runtime to discover and iterate over properties in the class definition, including @dynamic properties that were created at runtime. Because the property runtime calls do not include properties inherited from parent classes, the method acts recursively, iterating up the object hierarchy until it gets to NSManagedObject. The actual -description method simply prints out each of the attributes and the value currently stored for that attribute for this object instance. Once you do this, if you use NSLog() or po, the result is much richer information about the objects getting printed to the console. This version ignores relationships and fetched properties, but it wouldn't be hard to add those in if you needed them.

Wednesday, July 15, 2009

Updating Project Hint

If you update an Xcode project from SDK 2.x to SDK 3.x, and you get some really weird compile errors in UILocalizedIndexedCollation (or other UIKit classes) when Xcode processes the precompiled headers, the problem is probably that you need to change the compiler version from GCC 4.0 to GCC 4.2. The project templates were updated, so this isn't an issue with starting new projects, but when updating an older project, you have to manually change the compiler being used.

You can do that by selecting Edit Project Settings... from the Project menu, and navigating to the Build tab.

Look for the entry called C/C++ Compiler Version, and change it from GCC 4.0 to GCC 4.2

Friday, July 10, 2009

Core Data Default Dates in Data Model

I'm leaving this blog entry, but do not use this tip - there is a flaw in it

When creating a managed object model using Xcode's Data Model Editor, I knew that there was a way to specify a default value for a date attribute that would cause it to default to the current date and time. It's one of those things I need to do about twice a year and can never, ever remember how it's done. Apple's documentation didn't yield an answer (it might be in there, but it's not easy to find). Yes, you can create a custom subclass of NSManagedObject and set the value in awakeFromInsert:, but for those situations where you don't want to subclass, or don't feel like it, I knew there was a way and it was frustrating me that I couldn't remember it or find it.

Thanks to Twitter, I got the answer, and it's a cooler answer than I remembered. The Default field for date attributes will interpret natural language strings put in quotes. It appears to be that anything that NSDate's natural language parser can handle, the date attribute's default field can handle. "Tomorrow", "Today", "Now", "Yesteday at Breakfast".

WHOAH: After a user comment alerted me to this, I tested it out and sure enough, the natural language string entered into the default field is actually determined at compile time, not at runtime, so 'today' will always result in the same date. Do not use this technique!

via Shane Crawford

 
Design by Wordpress Theme | Bloggerized by Free Blogger Templates | coupon codes