Codesign error: “Warning: –resource-rules has been deprecated in Mac OS X >= 10.10”

So you are humming along and everything is going great. Then all of a sudden you start getting the error message “Warning: –resource-rules has been deprecated in Mac OS X >= 10.10”, especially if you are using some kind of tool to re-sign code.

Apparently, in your build settings for your target in Xcode, there is a Code Signing Resource Rules Path entry that is no longer supported. Once this is removed and the app is rebuilt, the message should no longer appear. Of course, if you are re-signing an app that you do not have the source code for, this could be a challenge.

Here is the technical note from Apple that describes the issue:

TN2206

BTW, Happy 60th Birthday to the best rock guitar player of all time, Joe Satriani. I am looking forward to seeing Joe in a few weeks at the G4 Experience on Long Island.

Calling a .NET web service endpoint with NSURLSessionUploadTask

I have been going through the process of removing open source components from my apps when Cocoa Touch and the associated frameworks catch up with what I need to do.

Let’s say for the sake of argument you have a .NET web service that looks like this:

using System;
using System.Web.Script.Services;
using System.Web.Services;
 
namespace My_WebRole
{
    /// <summary>
    /// Summary description for CreateCustomer
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    [ScriptService]
    // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
    // [System.Web.Script.Services.ScriptService]
    public class CreateCustomer : WebService
    {
        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public string Create()
        {
            try
            {
                var myName = Context.Request.Form["MyName"];
                var companyName = Context.Request.Form["CompanyName"];
                var db = new MyServicesDataContext();
                var row = new CustomerInformation { MyName = myName, CompanyName = companyName, CreationDate = DateTime.Now };
                db.CustomerInformations.InsertOnSubmit(row);
                db.SubmitChanges();
            }
            catch (Exception ex)
            {
                return ex.ToString();
            }
            return "OK";
        }
    }
}

The NSURLSessionUploadTask class was added in iOS 7, which is sufficiently long enough ago to be stable to implement. (As long as your app is going to require iOS 7 or newer, of course.)

Here is what the code looks like that calls the above web service:

+ (BOOL)pushTheData:(NSString *)myName companyName:(NSString *)companyName
{
    NSMutableString *urlString = [NSMutableString stringWithString:PUSH_DATA_URL];
    NSString *boundary = [NSString boundaryString];
    NSMutableData *body = [NSMutableData data];
 
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", @"MyName"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"%@\r\n", myName] dataUsingEncoding:NSUTF8StringEncoding]];
 
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", @"CompanyName"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"%@\r\n", companyName] dataUsingEncoding:NSUTF8StringEncoding]];
 
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
 
    NSURL *url = [NSURL URLWithString:urlString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setHTTPMethod:@"POST"];
    [request addValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
 
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    configuration.HTTPAdditionalHeaders = @ { @"Content-Type"  : [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] };
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
 
    NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromData:body completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error)
        {
            // handle the error
            return;
        }
 
        NSLog(@"Response: %@", [data utf8String]);
        // do something here
    }];
 
    [task resume];
 
    return YES;
}

The above method uses a category that I have set up off of NSString that creates a new boundary string each time it is called. Here is that method:

+ (NSString *)boundaryString
{
    NSString *uuidStr = [[NSUUID UUID] UUIDString];
    return [NSString stringWithFormat:@"Boundary-%@", uuidStr];
}

BTW, Happy Birthday to Jeff Beck. He has a new album coming out soon, and I can’t wait to hear it.

Xcode project line counts

If you are looking to find out how many lines of code are in each of the files of your Xcode project, you can use the following command in Terminal after you change into the root folder of your project:

find . \( -iname \*.m -o -iname \*.mm -o -iname \*.h \) -exec wc -l '{}' \+

SpriteKit game background music stopped working

In my SpriteKit game, all of a sudden the background music stopped playing, all I heard was silence no matter what the volume levels were in either the app settings or via the hardware buttons.

I am not sure why this happened, but I was able to figure out how to get it working again. The key was adding an AVAudioSession call before the existing AVAudioPlayer code. Here is some sample code of what I did:

AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
 
NSURL *url = [NSURL fileURLWithPath:@"your_sound_file.caf"];
_player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[_player setDelegate:self];
[_player prepareToPlay];
[_player play];

BTW, Happy Birthday to Brutus Beefcake. (I couldn’t really find any other good birthdays, deaths, or events in the Wikipedia page for today.)

Xcode test coverage falsely indicates methods covered by testing

If you have a normal Xcode project that you added unit testing to, you may find that the testing code coverage inside of Xcode shows that there is a lot more code being tested than actually is. Or if you have an app that does complex and/or lengthy things in its initialization, you may get tired of waiting for all that stuff to run just because you want to run the tests to see if they pass.

Luckily, I figured out a way to short circuit the app from its normal initialization if you are invoking it for the purposes of testing. Here is the code you would need to put in your app delegate’s didFinishLaunchingWithOptions method, preferably right at the top:

#if defined(DEBUG_VERSION)
    if ([[[NSProcessInfo processInfo] arguments] containsObject:@"-FNTesting"])
    {
        DebugLog(@"The -FNTesting argument is in use, so the app will just create a blank view controller");
        UIViewController *vc = [[UIViewController alloc] init];
        navigationController = [[MyNavigationController alloc] initWithRootViewController:vc];
        [window setRootViewController:navigationController];
        return YES;
    }
#endif

(The #if above uses a compiler variable that is only used in the debug scheme of the application. It’s a good plan all around to do this in your own code.)

Basically, this code only is going to run if your are doing a unit test run, and it just creates a blank view controller and returns.

BTW, Happy Pi Day. I meant to do this posting at 1:59 today to continue the pi theme, but got busy with work and forgot.

Reinforced concrete would be a pretty cool security feature

I was going to upgrade to Windows 10, but I don’t think I can trust any operating system that does not have reinforced concrete…

reinforced_concrete

BTW, a posthumous Happy Birthday to Steve Jobs.

Apple Tech Talk recap

I was in attendance of the Apple Tech Talk last week in New York City. The focus of the event was all Apple TV, and rightfully so. The Apple TV platform is a new and emerging platform, and has tons of opportunities.

Unfortunately, there do not appear to be too many business app opportunities for the Apple TV, it seems to skew mostly toward games and content providing apps. However, that does not mean that opportunities do not exist, it will just be a challenge to find them.

BTW, Happy Birthday to Mark Messier, one of the all time great hockey players.

Advent of Code

I have been going through the 2015 version of the Advent of Code, which is a web site that has a bunch of interesting programming puzzles. In an attempt to try to learn something new, I decided I would solve the puzzles using Swift. I am about half way through so far, and the results have been eye opening.

Here is the link to the Advent of Code web site:

http://adventofcode.com

And here is a link to my Github repository with the solutions:

https://github.com/Wave39/AdventOfCode

BTW, Happy Birthday to Mike Keneally, the excellent guitarist and musician who has worked with so many great artists, including Joe Satriani and Steve Vai.

How to make random entries into the iPhone address book (updated)

A few years have passed since I posted a blog entry entitled How to make random entries into the iPhone address book, and while you might be able to figure out how to make this code work if you try to use it with iOS 9, I figured it was time to revisit.

Here is the iOS 9 compliant method for creating the random address book entries:

- (IBAction)add25ButtonPressed:(id)sender
{
#define NUMBER_OF_RANDOM_ENTRIES    25
#define URL_FORMAT  @"http://old.wave39.com/roster/generate.php?f=csv&g=%@&num=%d&limit=99"
 
    CFErrorRef error1 = nil;
    ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error1);
    ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error2) {
        // callback can occur in background, address book must be accessed on thread it was created on
        dispatch_async(dispatch_get_main_queue(), ^{
            if (error2)
            {
                NSLog(@"Address book error: %@", ((__bridge NSError *)error2).localizedDescription);
            }
            else if (!granted)
            {
                NSLog(@"Access to the address book has been denied.");
            }
            else
            {
                // access granted
                NSString *genderOfNames = ((arc4random() % 2) == 1 ? @"m" : @"f");
                NSString *urlString = [NSString stringWithFormat:URL_FORMAT, genderOfNames, NUMBER_OF_RANDOM_ENTRIES];
                NSURL *url = [NSURL URLWithString:urlString];
                NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
                NSURLResponse *response = nil;
                NSError *error = nil;
                NSData *data = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:&error];
                NSString *stringData = [NSString stringWithUTF8String:[data bytes]];
                NSString *areaCode = [NSString stringWithFormat:@"%03d", (arc4random() % 799 + 200)];
 
                NSArray *lineArray = [stringData componentsSeparatedByString:@"\n"];
                for (NSString *line in lineArray)
                {
                    if ([line length] > 0)
                    {
                        NSArray *fieldArray = [line componentsSeparatedByString:@","];
                        if ([fieldArray count] == 3)
                        {
                            ABRecordRef person = ABPersonCreate();
                            NSString *phone = [NSString stringWithFormat:@"%@-%03d-%04d", areaCode,
                                               (arc4random() % 799 + 200), (arc4random() % 9999)];
                            ABMutableMultiValueRef phoneNumberMultiValue = ABMultiValueCreateMutable(kABMultiStringPropertyType);
                            CFStringRef phoneType = (arc4random() % 2 == 0 ? kABPersonPhoneMainLabel : kABPersonPhoneMobileLabel);
                            ABMultiValueAddValueAndLabel(phoneNumberMultiValue, (__bridge CFTypeRef)(phone), phoneType, NULL);
                            NSString *email = [NSString stringWithFormat:@"%@_%@@tapbandit.com", fieldArray[1], fieldArray[2]];
                            ABMutableMultiValueRef emailMultiValue = ABMultiValueCreateMutable(kABMultiStringPropertyType);
                            CFStringRef emailType = kABHomeLabel;
                            ABMultiValueAddValueAndLabel(emailMultiValue, (__bridge CFTypeRef)(email), emailType, NULL);
                            ABRecordSetValue(person, kABPersonFirstNameProperty, (__bridge CFTypeRef)(fieldArray[1]), nil);
                            ABRecordSetValue(person, kABPersonLastNameProperty, (__bridge CFTypeRef)(fieldArray[2]), nil);
                            ABRecordSetValue(person, kABPersonPhoneProperty, phoneNumberMultiValue, nil);
                            ABRecordSetValue(person, kABPersonEmailProperty, emailMultiValue, nil);
                            ABAddressBookAddRecord(addressBook, person, nil);
                            NSLog(@"Created record for %@ %@ (%@: %@; %@)", fieldArray[1], fieldArray[2], phoneType, phone, email);
                            CFRelease(phoneNumberMultiValue);
                            CFRelease(emailMultiValue);
                            CFRelease(person);
                        }
                    }
                }
 
                ABAddressBookSave(addressBook, nil);
                NSLog(@"Done creating address book data.");
 
                CFRelease(addressBook);
            }
        });
    });
}

BTW, Happy Birthday to Windows, first available 30 years ago today. Coincidentally, on the same exact day those 30 years ago, the Pittsburgh Pirates hired Jim Leyland to be their manager. And in both instances, the rest is history.

Big Android BBQ Day 2

Well, the Big Android BBQ 2015 conference is in the books. I found the content on day 2 to be a bit more helpful to me at my current level of Android experience, with the highlights being sessions on lean layouts and fragments. A bit basic for the seasoned Android developer, but just right for me.

BTW, Happy Birthday to Omar Moreno, the great center fielder on the Pirates teams of the late 70s and early 80s, and leadoff man for the 1979 World Series champs.