Archive

Programming

I have an NSWindowController that swaps between various NSViews using animations.

@interface MyWindowController : NSWindowController
{
    IBOutlet NSWindow*  window;
    IBOutlet NSView*    view;
	NSViewController*	myCurrentViewController;	// the current view controller
    FirstViewController *fvc;
    SecondlViewController *svc;    
    CATransition *transition;
}
- (IBAction)changeToFirstView:(id)sender;
- (IBAction)changeToSecondView:(id)sender;
@end


@implementation MyWindowController

- (void)awakeFromNib
{
    transition = [CATransition animation];
    [transition setType:kCATransitionPush];
    [transition setSubtype:kCATransitionFromLeft];
    NSDictionary *ani = [NSDictionary dictionaryWithObject:transition
                                                    forKey:@"subviews"];
    [view setAnimations:ani];
    [view setWantsLayer:YES];   //  turns on Core Animation

    fvc = [[FirstViewController alloc] initWithNibName:@“FirstView" bundle:nil];
    [view addSubview:[fvc view]];
    myCurrentViewController = fvc;
}

- (IBAction)changeToFirstView:(id)sender;
{
        fvc = [[FirstViewController alloc] initWithNibName:@“FirstView" bundle:nil];
        [[view animator] replaceSubview:[myCurrentViewController view] with:[fvc view]];
        myCurrentViewController = fvc;
}

- (IBAction)changeToSecondView:(id)sender;
{
        svc = [[SecondViewController alloc] initWithNibName:@“SecondView" bundle:nil];
        [[view animator] replaceSubview:[myCurrentViewController view] with:[svc view]];
        myCurrentViewController = svc;
}

However, when the secondView was swapped in for the first time, parts of the firstView remained visible within secondView. These parts were NSTextViews. After playing around for a while I discovered that switching off the drawBackground option for the NSTextViews in Interface Builder stopped the views leaking through to secondView. It also meant that the boxes were no longer distinguishable from the rest of the background in the view. I think the correct solution is to use setNeedsDisplay to force view to redraw its contents when the subviews are swapped. The issue may be related to the use of CoreAnimation in the swapping of the subviews. So I have added a line

    [view setNeedsDisplay:YES];

within the changeToFirstView and changeToSecondView methods.

Advertisements

Dynamic libraries are explained in https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/DynamicLibraries/000-Introduction/Introduction.html

There are advantages to using dynamic libraries in applications but extra effort is also required in order for the application to be able to locate and use the library when needed.

Firstly, the install names of the libraries need to be changed so that the target system knows where to find them. Secondly, the dynamic libraries need to be copied into the applications package directory. Finally, it seems that with OS X 10.9 Apple now requires third party libraries to be signed by developers separately. Steps 2 and 3 are accomplished via a script which is placed in the build phases section of Xcode. I needed to add a number of dynamic libraries to an application. One of these was libpari-gmp.dylib and I will use this here as an example.

The first step above is accomplished using the install_name_tool to change the dynamic library install name and dependency names so that the target system knows where to find them. I used otool to find the current install name of the file and dependencies:

otool -L libpari-gmp.dylib

This showed that libpari-gmp.dylib has libgmp.10.dylib as a dependency. The following command line instructions will then change the install names to the same location as the application binary (given by “@executable_path”):

install_name_tool -id @executable_path/libpari-gmp.dylib libpari-gmp.dylib

install_name_tool -change “/path/to/dependency/file/libgmp.10.dylib” @executable_path/libgmp.10.dylib libpari-gmp.dylib

The script that accomplishes steps 2 and 3 above looks like this:

Step 2.

###  Copy the library and dependencies from wherever they are located to the appropriate part of the application package (@executable_path above also known as “”$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/“).

cp -f “/path/to/library/libpari-gmp.dylib” “$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/libpari-gmp.dylib”;

cp -f “/path/to/library/libgmp.10.dylib” “$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/libgmp.10.dylib”

Step 3.

#  Sign the dynamic library as now required by Apple (see http://furbo.org/2013/10/17/code-signing-and-mavericks/ for a good explanation of the change).

LOCATION=”$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/”

IDENTITY=“Put you code signing identity here”

codesign –verbose –force –sign “$IDENTITY” “$LOCATION/libpari-gmp.dylib”

codesign –verbose –force –sign “$IDENTITY” “$LOCATION/libgmp.10.dylib

In fact step 1 can be performed after step 2 and this is what I did for the dynamic library libiconv.2.dylib. The script for ,ibiconv2.dylib is

cp -f “/opt/local/lib/libiconv.2.dylib” “$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/libiconv.2.dylib”

install_name_tool -id @executable_path/libiconv.2.dylib “$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/libiconv.2.dylib”;

View based tableviews may contain many subviews. For example, a twitter tableview needs to include outlets for an icon image, date, user name, real name, retweeting, favouriting, replying, following and the text of the tweet. In my twitter implementation I had trouble implementing the resizing of the tableview row to show tweets that extended over more than 2 lines. Instead of resizing the view I decided to truncate tweets that were more than 2 lines long and only show first 2 lines of the tweet. Apple in fact provides the necessary code to count the number of lines needed to display an attributed string in a view. This code was easy to adapt to my purpose. The work is done in the tableview delegate method tableView:viewForTableColumn:row. I created a ‘Tweet’ object to hold the relevant information about each tweet (user details, date, text etc). The ‘Tweets’ array holds the downloaded tweets. RBTwitterCellView is the top view in the tableview. It contains all the subviews mentioned above. The relevant parts of tableView:viewForTableColumn:row are given below.


- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
RBTwitterCellView *result = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
Tweet *tempTweet = [tweets objectAtIndex:row];
.
(allocate values to the various subviews of 'result' and created the attributed tweet text string)
.
[[[result tweetTextView] textStorage] setAttributedString:attributedTextString];

// The following code calculates the number of lines required to display the tweet in the given view within the tableview.
// If the number of lines required is more than 2 I truncate the tweet after the second line (line2index points to the end
// of the second line).
NSLayoutManager *layoutManager = [[result tweetTextView] layoutManager];
NSUInteger numberOfLines, index, line2index, numberOfGlyphs = [layoutManager numberOfGlyphs];
NSRange lineRange;
for (numberOfLines = 0, index = 0; index 2){
[[result tweetField] setString:[[attributedStatusString string]substringToIndex:line2index]];
}

return result;
}

Twitter recently introduced display requirements for applications which use the twitter API. Previously there were only guidelines for developers. The display requirements are available at
https://dev.twitter.com/terms/display-requirements.

The tasks required in developing a twitter app include:

1. Using the new Twitter API v1.1 to access tweets
2. Parsing the tweet dictionary to extract the data that is relevant to the app
3. Detecting has tags, user names and web addresses in the tweet as these have special display requirements.
4. Using attributed strings to create the display.
5. Displaying the results in a way that meets twitter’s display requirements.
6. Detecting mouse clicks and taking the appropriate action (retweeting, replying and favouriting need to be available).
7. Dealing with the need for variable tableview heights due the different tweet lengths.

Designing a display that meets Twitter’s new requirements required me to learn or refresh my knowledge of a number of Xcode skills. It also required me to become reasonably familiar with the twitter API. Here is a possibly incomplete list of things that I met during the development of the twitter app.

1. The responder chain for mouse events. NSViews respond to mouse events but cells don’t which makes views easier to use in this case.
2. RegexKitLite. I have never looked much at regular expressions so I am glad that this tool exists to scan strings for various components such as hashtags, twitter usernames and web addresses.
3. Creating mouse tracking areas and changing the cursor when it is within those areas (such as over hashtags etc.).
4. Using twitter web intents for retweeting, favouriting or replying to tweets.
5. Creating an attribute dictionary and adding attributes to certain parts of a string.
6. Parsing the tweet dictionary. I didn’t find a good reference for the structure for the twitter dictionary so worked it out from scratch. This isn’t very hard once you can work out how to access tweets. Retweets present a special problem as long retweets are someimes truncated in one part of the tweet dictionary. The full tweet is available elsewhere in the dictionary however so this problem can be overcome.
7. View based tableviews. I had not used these previously. The default tableview appears to be cell based.

Twitter’s new v1.1 API imposes limits on the number of requests that users can make. The limits are detailed at https://dev.twitter.com/docs/rate-limiting/1.1/limits. OSX apps need to be aware of these limits and ensure that users cannot make more requests than they are allocated. Twitter states

“We ask that you honor the rate limit. If you or your application abuses the rate limits we will blacklist it. If you are blacklisted you will be unable to get a response from the Twitter API”

Before making a twitter request I check that the rate limit has not been exceeded. I do this using a requestTwitterStatus method as follows:

-(void) requestTwitterStatus{
NSURL *statusURL = [NSURL URLWithString:@"https://api.twitter.com/1.1/application/rate_limit_status.json?resources=help,users,search,statuses"];
statusRequest = [NSMutableURLRequest requestWithURL:statusURL];
[statusRequest addValue:[NSString stringWithFormat:@"Bearer %@",bearerToken] forHTTPHeaderField:@"Authorization"];
[statusRequest setHTTPMethod:@"GET"];
statusConnection = [[NSURLConnection alloc] initWithRequest:statusRequest delegate:self startImmediately:YES];
}

Here the app’s bearer token is the Authorization token obtained from twitter (see my previous post for details of how to obtain this). The connectionDidFinishLoading method detects when the request has finished. The data obtained is then parsed within connectionDidFinishLoading with the following code (receivedData is the data that the getTweetsConnection has provided and is collected in the connection:(NSURLConnection *)connection didReceiveData:(NSData *)data method);

if (connection == statusConnection) {
NSString *twitterStatus = [[NSString alloc] initWithBytes:[statusData bytes] length:[statusData length] encoding:NSUTF8StringEncoding];
NSDictionary *statusResults = [twitterStatus JSONValue];
NSDictionary *statusDict = [statusResults valueForKey:@"resources"];
NSDictionary *searchStatusDict = [statusDict valueForKey:@"search"];
NSDictionary *searchDict = [searchStatusDict valueForKey:@"/search/tweets"];
requestsRemaining = [[searchDict valueForKey:@"remaining"] intValue];
NSLog(@"requestsRemaining = %d\n", requestsRemaining);
if (requestsRemaining > 0) {
// go ahead and make a request to twitter
} else{
NSLog(@"You have reached twitter's request limit. Please wait 15 minutes before requesting again");
}
return;
}

Twitter decommissioned v1 of its API in June 2013. The new version, v1.1, enforces Authentication and Display requirements. Currently my interest is in accessing tweets by certain users or in certain subject areas. Previously this involved the ASIHTTPRequest
methods. I would request twitter data via code such as

NSURL *url = [NSURL URLWithString:feed];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];

where the string ‘feed’ was of the form say,

@”http://search.twitter.com/search.json?q=westernbulldogs%20OR%20from%3Awesternbulldogs”

When the request finished a method then parsed the resulting data as follows:

- (void)requestFinished:(ASIHTTPRequest *)request {
NSString *responseString = [[NSString alloc] initWithBytes:[[request responseData] bytes] length:[[request responseData] length] encoding:NSUTF8StringEncoding];
NSDictionary *results = [responseString JSONValue];
NSArray *entries = [[NSArray alloc] initWithArray:[results objectForKey:@"results"]];
if (entries == nil) {
NSLog(@"Failed to parse %@", request.url);
} else{
for (NSDictionary *entry in entries) {
// Parse the data into various fields
 ...
}
}
}

The process of accessing tweets is now more involved. The instructions at https://dev.twitter.com/docs/auth/application-only-auth
were quite helpful and I will summarise the steps I took.

Each app needs to be given a consumer key and consumer secret. The developer arranges this by creating and logging into her twitter developer account
at dev.twitter.com and creating a new application at https://dev.twitter.com/apps. Once this is done the key and secret can be viewed by selecting the app.

The first step in setting up a twitter request is to use the key and secret to obtain a ‘bearer token’. The key and secret are first encoded according to RFC 1738. The result is then concatenated into one string with a colon as the separator. Finally this string is then base64 encoded. A bearer token request is then made. The following method does this work (I have of course disguised the key and secret):

-(void)tokenOAuth{
NSString *key = @"xxxxxxxxxxxxxxx";
NSString *rfc1738key = [key stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *secret = @"yyyyyyyyyyyyyyyyyyyyyyyyyy";
NSString *rfc1738secret = [secret stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *concat = [NSString stringWithFormat:@"%@:%@", rfc1738key, rfc1738secret];
NSString *enc = [[concat dataUsingEncoding:NSUTF8StringEncoding] base64Encoding_xcd]; // base64 encoded value of concat
NSURL *theURL = [NSURL URLWithString:@"https://api.twitter.com/oauth2/token"];
getToken = [NSMutableURLRequest requestWithURL:theURL];
[getToken addValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
NSString *authValue = [NSString stringWithFormat:@"Basic %@", enc];
[getToken addValue:authValue forHTTPHeaderField:@"Authorization"];
NSString *post = @"grant_type=client_credentials";
NSData *body = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
[getToken setHTTPMethod:@"POST"];
[getToken setValue:[NSString stringWithFormat:@"%u", (unsigned int)[body length]] forHTTPHeaderField:@"Content-Length"];
[getToken setHTTPBody:body];
getTokenConnection = [[NSURLConnection alloc] initWithRequest:getToken delegate:self startImmediately:YES];
}

The connectionDidFinishLoading: method detects when the connection has finished. This method then parses the data obtained by the connection and checks to see if a bearer token was sent. If so, we can proceed to request the relevant tweets using the bearer token as the authorisation. The connectionDidFinishLoading code is as follows (tokenData is the data that the connection has provided and is collected in the connection:(NSURLConnection *)connection didReceiveData:(NSData *)data method):

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
if(connection == getTokenConnection){
NSString *tokenString = [[NSMutableString alloc] initWithBytes:[tokenData bytes] length:[tokenData length] encoding:NSUTF8StringEncoding];
if (![tokenData length] == 0) {
results = [tokenString JSONValue];
} else{
results = nil;
printf("Error. No data\n");
return;
}
if([results valueForKey:@"access_token"] && [[results valueForKey:@"token_type"] isEqualToString:@"bearer"]){
bearerToken = [[NSString alloc] initWithString:[results valueForKey:@"access_token"]];
[self requestTweets]; // call a method to request relevant tweets.
}
return;
}

I use the requestTweets method to send a request to twitter for tweets. It looks like this:

- (void) requestTweets{
NSString *feed = @"https://api.twitter.com/1.1/search/tweets.json?q=westernbulldogs%20OR%20from%3Awesternbulldogs" // example request url
NSURL *url = [NSURL URLWithString:feed];
twitterrequest = [NSMutableURLRequest requestWithURL:url];
[twitterrequest addValue:[NSString stringWithFormat:@"Bearer %@",bearerToken] forHTTPHeaderField:@"Authorization"];
[twitterrequest setHTTPMethod:@"GET"];
getTweetsConnection = [[NSURLConnection alloc] initWithRequest:twitterrequest delegate:self startImmediately:YES];
return;
}

Again the connectionDidFinishLoading method detects when the request has finished. The data obtained is then parsed within connectionDidFinishLoading
with the following code (receivedData is the data that the getTweetsConnection has provided and is collected in the connection:(NSURLConnection *)connection didReceiveData:(NSData *)data method);

if(connection == getTweetsConnection){
NSString *responseString = [[NSMutableString alloc] initWithBytes:[receivedData bytes] length:[receivedData length] encoding:NSUTF8StringEncoding];
if (![receivedData length] == 0) {
results = [responseString JSONValue];
} else{
printf("Error:no data obtained\n");
return;
}
// We now need to parse the results
NSArray *entries = [[NSArray alloc] initWithArray:[results objectForKey:@"statuses"]];
if (entries == nil) {
NSLog(@"Failed to parse results");
return;
} else{
for (NSDictionary *entry in entries) {
NSDictionary *userDict = [entry valueForKey:@"user"];
NSString *imageLoc = [userDict valueForKey:@"profile_image_url"];
NSURL *url = [NSURL URLWithString:imageLoc];
NSImage *image = [[NSImage alloc] initWithContentsOfURL:url];
NSString *user = [userDict valueForKey:@"screen_name"];
NSString *name = [userDict valueForKey:@"name"];
NSString *tweetid = [userDict valueForKey:@"id"];
NSString *twitterDateString = [entry valueForKey:@"created_at"];
NSString *dateString = [NSDateFormatter dateStringFromTwitterString:twitterDateString];
NSDate *date = [NSDate dateFromInternetDateTimeString:dateString formatHint:DateFormatHintRFC3339];
NSString *text = [entry valueForKey:@"text"];
Tweet *tweet = [[Tweet alloc] initWithProfileImageLocation:imageLoc icon:image user:user name:name tweetid:tweetid date:date text:text];
[tweets addObject:tweet]; // 'tweets' is the array where the tweets are stored.
}
}

As you can see, you need to do more in v1.1 to get the same information. I have left rate limiting and display requirements out of the above to avoid
complicating matters. In short, Twitter limits the number of requests that users can make and an app needs to ensure that users are not making more requests than they should. A possible penalty for not checking this is blacklisting of the app. In addition Twitter now requires apps to display tweets in certain formats as described on the twitter developer site at https://dev.twitter.com/terms/display-requirements.

I was in the situation recently where I was using msieve to factor large integers and wanted msieve to stop factoring after a certain time even if it had not completed the factorisation. msieve itself does not provide a way of doing this but the demo.c program that comes with msieve contains a method by which it can be done. The method involves threading and signalling.

The first step is to create a thread in which a timer is started. I created the thread just before the factoring was about to start by adding this code to that part of the program. The variable deadline provides the length of time for which the timer should run before sending a signal.

int32 deadline = 1;
pthread_t thread_id;
pthread_create(&thread_id, NULL, countdown_thread, &deadline);

The thread (called countdown_thread) raises a signal (the user defined signal SIGUSR1) when the timer has finished.

void *countdown_thread(void *pminutes) {
	uint32 minutes = *(uint32 *)pminutes;
	if (minutes > 0xffffffff / 60)
		minutes = 0xffffffff / 60;   // infinite
	sleep(minutes * 60);
	raise(SIGUSR1);
	return NULL;
}

How does the program know what to do when SIGUSR1 is raised? This is set up by adding these lines prior to starting the thread.

if (signal(SIGUSR1, handle_signal) == SIG_ERR) {
	printf("could not install handler on SIGUSR1\n");
	return;
}

The instruction signal(SIGUSR1, handle_signal) tells the program to invoke the function handle_signal when it receives the signal SIGUSR1. The handle_signal function stops msieve by setting the appropriate flag.

void handle_signal(int sig) {
	msieve_obj *obj = g_curr_factorization;
	if (obj && (obj->flags & MSIEVE_FLAG_SIEVING_IN_PROGRESS))
		obj->flags |= MSIEVE_FLAG_STOP_SIEVING;
	else
		return;
}

There was an obstacle to all this working. Unfortunately, the xcode compiler stops when it receives a signal and brings up the debugger. Thus the program does not continue executing. There is a way around this which seems a little strange. The solution involves creating a breakpoint in main.m at the line

return NSApplicationMain(argc, (const char **)argv);

You then edit the breakpoint, tick the options box (“Automatically continue after evaluating”) and add the Debugger Command
process handle SIGUSR1 -n true -p true -s false
I found the solution on stackoverflow at
http://stackoverflow.com/questions/10431579/permanently-configuring-lldb-in-xcode-4-3-2-not-to-stop-on-signals
One final thing to do is to update the display to show that factorisation was interrupted before completion. This is done by passing the relevant NSMutableString and NSTextView objects to the factoring function (via myString and myView) and then adding these lines after the msieve_run line.

if (!(g_curr_factorization->flags & MSIEVE_FLAG_FACTORIZATION_DONE)) {
	[myString appendString:@"factorisation was aborted due to time constraints"];
	[myView setString:myString];
	return;
}