-
-
Save sukima/226332 to your computer and use it in GitHub Desktop.
Thread programming review
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ImageLoadingOp.h | |
// PersonList | |
// | |
// Created by Marcio Valenzuela on 10/20/09. | |
// Copyright 2009 Personal. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
#import <UIKit/UIKit.h> | |
extern NSString *const ImageResultKey; | |
extern NSString *const URLResultKey; | |
@interface ImageLoadingOp : NSOperation { | |
NSURL *imageURL; | |
id target; | |
SEL action; | |
} | |
- (id)initWithImageURL:(NSURL *)imageURL target:(id)target action:(SEL)action; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ImageLoadingOp.m | |
// PersonList | |
// | |
// Created by Marcio Valenzuela on 10/20/09. | |
// Copyright 2009 Personal. All rights reserved. | |
// | |
#import "ImageLoadingOp.h" | |
NSString *const ImageResultKey = @"image"; | |
NSString *const URLResultKey = @"url"; | |
@implementation ImageLoadingOp | |
- (id)initWithImageURL:(NSURL *)theImageURL target:(id)theTarget action:(SEL)theAction | |
{ | |
self = [super init]; | |
if (self) { | |
imageURL = [theImageURL copy]; | |
target = [theTarget retain]; | |
action = theAction; | |
} | |
NSLog(@"EXITING INITIMAGELOADINGOP"); | |
return self; | |
} | |
- (void)dealloc | |
{ | |
[super dealloc]; | |
[imageURL release]; | |
[target release]; | |
} | |
- (void)main | |
{ | |
// Synchronously oad the data from the specified URL. | |
NSLog(@"ENTER IMAGELOADINGOP MAIN"); | |
NSData *data = [[NSData alloc] initWithContentsOfURL:imageURL]; | |
UIImage *image = [[UIImage alloc] initWithData:data]; | |
NSLog(@"IN ILOP main data %@", data); | |
// Package it up to send back to our target. | |
NSDictionary *result = [NSDictionary dictionaryWithObjectsAndKeys:image, ImageResultKey, imageURL, URLResultKey, nil]; | |
[target performSelectorOnMainThread:action withObject:result waitUntilDone:NO]; | |
[data release]; | |
[image release]; | |
NSLog(@"EXITING IMAGELOADINGOP MAIN dict"); | |
} | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Person.h | |
// P3Scratch | |
// | |
// Created by Marcio Valenzuela on 10/30/09. | |
// Copyright 2009 Personal. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
// In oreder to use [Person copy] you need to implement the NSCopying protocal. | |
@interface Person : NSObject <NSCopying> { | |
NSString *screenName; | |
NSString *realName; | |
NSURL *imageURL; | |
//for later on when i get tweets rollin | |
NSMutableArray *statusUpdates; | |
NSMutableArray *timeStamps; | |
NSDictionary *twitterDictionary; | |
} | |
// Rule of thumb is strings and objects of literal types should be copied. Not | |
// required just good practise. | |
@property(nonatomic,copy)NSString *screenName; | |
@property(nonatomic,copy)NSString *realName; | |
@property(nonatomic,copy)NSURL *imageURL; | |
//for later on when i get tweets rollin | |
@property(nonatomic,retain)NSMutableArray *statusUpdates; | |
@property(nonatomic,retain)NSMutableArray *timeStamps; | |
@property(nonatomic,retain)NSDictionary *twitterDictionary; | |
//-(id)initWithScreenName:(NSString*)name; | |
//-(id)initWithScreenName:(NSString*)name RealName:(NSString*)rname; | |
-(id)initWithScreenName:(NSString*)name RealName:(NSString*)rname URL:(NSURL*)url; | |
//-(id)initWithObject:(Person*)Object; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Person.m | |
// P3Scratch | |
// | |
// Created by Marcio Valenzuela on 10/30/09. | |
// Copyright 2009 Personal. All rights reserved. | |
// | |
#import "Person.h" | |
@implementation Person | |
@synthesize screenName; | |
@synthesize realName; | |
@synthesize imageURL; | |
//for later on when i get tweets rollin | |
@synthesize statusUpdates; | |
@synthesize timeStamps; | |
@synthesize twitterDictionary; | |
/* | |
-(id)initWithScreenName:(NSString*)name{ | |
screenName = name; | |
return self; | |
} | |
-(id)initWithScreenName:(NSString*)name RealName:(NSString*)rname{ | |
screenName = name; | |
realName = rname; | |
return self; | |
} | |
*/ | |
-(id)initWithScreenName:(NSString*)name RealName:(NSString*)rname URL:(NSURL*)url{ | |
if ( (self = [super init]) == nil ) | |
return nil; | |
// The objects need to be retained (or copied depending) otherwise they | |
// will be dealloc right out from underneath you. Bad for bugs. The setter | |
// methods provided by @synthesize do this for you. Using dot notation to | |
// access the setters. | |
self.screenName = name; | |
self.realName = rname; | |
self.imageURL = url; | |
return self; | |
} | |
// This seems redundant and problematic. KISS (Keep It Short and Simple) method | |
// works best here. Use [Object copy] instead. It is more explicit and you know | |
// what is going on. Other wise with an initWithObject: your basically creating | |
// a Person object for the purpose of making a Person object. Seems silly. | |
/* | |
-(id)initWithObject:(Person*)Object{ | |
screenName = [Object.screenName copyWithZone:nil]; | |
realName = [Object.realName copyWithZone:nil]; | |
imageURL = [Object.imageURL copyWithZone:nil]; | |
return self; | |
} | |
*/ | |
// Need one of these!! | |
- (void)dealloc; | |
{ | |
[super dealloc]; | |
[screenName release]; | |
[realName release]; | |
[imageURL release]; | |
} | |
// Implement this to conform the NSCopying protocal. | |
- (id)copyWithZone:(NSZone *)zone; | |
{ | |
// Create a new Person object. The properties for screenName, realName, and | |
// imageURL are all set to copy so using the init method will copy the data | |
// into the new object. (See initWithScreenName: above) | |
Person *p = [[[Person allocWithZone:zone] | |
initWithScreenName:self.screenName | |
RealName:self.realName URL:self.imageURL] autorelease]; | |
// We use autorelease above because we are passing the ownership to the | |
// calling method. We don't need to save a reference to it here. | |
return p; | |
} | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// PersonListViewController.h | |
// P3Scratch | |
// | |
// Created by Marcio Valenzuela on 10/30/09. | |
// Copyright 2009 Personal. All rights reserved. | |
// | |
#import <UIKit/UIKit.h> | |
#import "Person.h" | |
#import "TwitterHelper.h" | |
@interface PersonListViewController : UITableViewController { | |
// Since this is assigned from a seperate thread having it mutable is a bad | |
// idea and only asking for it to be clobbered. | |
NSArray *persons; | |
NSString *filePath; | |
NSMutableDictionary *cachedImages; | |
NSOperationQueue *operationQueue; | |
UIActivityIndicatorView *spinner; | |
UILabel *loadingLabel; | |
// This is never used except as a local veriable in a method. | |
//Person *twit; | |
// Person *personToDisplay; | |
} | |
@property(nonatomic,retain) NSArray *persons; | |
@property(nonatomic,retain) NSString *filePath; | |
@property(nonatomic,retain) NSMutableDictionary *cachedImages; | |
//@property(nonatomic,retain) Person *twit; | |
//@property(nonatomic,retain) Person *personToDisplay; | |
-(id)initWithStyle:(UITableViewStyle)style; | |
-(UIImage*)cachedImageForURL:(NSURL*)url;//to cache images | |
-(void)showLoadingIndicators;//show spinner | |
-(void)hideLoadingIndicators;//hide spinner | |
-(void)beginLoadingTwitterData;//create operation queue and load twitterdata //call spinners | |
-(void)synchronousLoadTwitterData;//could be the same as beginLoadingTwitterData | |
-(void)didFinishLoadingTwitterDataWithResults:(id)result;//to return fetched data | |
-(void)makeNewTweet;//to make new tweets :) | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// PersonListViewController.m | |
// P3Scratch | |
// | |
// Created by Marcio Valenzuela on 10/30/09. | |
// Copyright 2009 Personal. All rights reserved. | |
// | |
#import "PersonListViewController.h" | |
#import "ImageLoadingOp.h" | |
NSString *const LoadingPlacedholder = @"Loading"; | |
@implementation PersonListViewController | |
@synthesize persons; | |
@synthesize cachedImages; | |
//@synthesize twit; | |
@synthesize filePath; | |
//@synthesize personToDisplay; | |
#pragma mark ---------------------------------------------------------------------- | |
#pragma mark initAllocMethods | |
-(id)initWithStyle:(UITableViewStyle)style{ | |
// This view controller is being created pragmatically and so initializing | |
// in initWithStyle: is appropriate. If it were created from a Nib you | |
// would need to move this code to the awakeFromNib method instead. | |
self = [super initWithStyle:style]; | |
// Always check before continuing. | |
if (self == nil) | |
return nil; | |
self.title = @"People List"; | |
operationQueue = [[NSOperationQueue alloc]init]; | |
[operationQueue setMaxConcurrentOperationCount:1]; | |
// Create an empty cachedImages dictionary ready. | |
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; | |
self.cachedImages = dict; | |
[dict release]; | |
// Do I have to write it this way? No the following are the same: | |
// cachedImages = [[NSMutableDictionary alloc] init]; | |
// self.cachedImages = [[[NSMutableDictionary alloc] init] autorelease]; | |
// However the way I did it is more verbose and explicit. Meaning 5 years | |
// from now I can look at the code and know right off the bat were the | |
// retain count is without having to think who owns what. Easier to debug | |
// too. Style choice. | |
// There is also the fact the self.cachedImages and cachedImages are | |
// different things. It is suggested that ising the sett methids | |
// (self.cachedImages) is better. However when you do this and assign | |
// to an object you have to ballance out the reference count. With | |
// responsability traded to the setter it is kludgy to balance it by | |
// using [cachedImages release]. That is why we create a local | |
// reference to handle locally and still balance it out properly. | |
persons = [[NSMutableArray alloc] init]; | |
// We don't have any setter methods so we access it directly and | |
// therefore the balancing is done in the dealloc method. Not as | |
// explicit as the above idea. To explicitly do this for consistancy | |
// sake we would do this: | |
// NSMutableArray *tempArray = [[NSMutableArray alloc] init]; | |
// persons = [tempArray retain]; | |
// [tempArray release]; | |
return self; | |
} | |
-(void)viewWillAppear:(BOOL)animated{ | |
[super viewWillAppear:animated]; | |
[self showLoadingIndicators]; | |
[self beginLoadingTwitterData]; | |
} | |
-(void)viewDidLoad { | |
[super viewDidLoad]; | |
UIBarButtonItem *tweetButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCompose target:self action:@selector(makeNewTweet)]; | |
self.navigationItem.rightBarButtonItem = tweetButton; | |
[tweetButton release]; | |
} | |
- (void)didReceiveMemoryWarning { | |
// Releases the view if it doesn't have a superview. | |
[super didReceiveMemoryWarning]; | |
[cachedImages removeAllObjects]; | |
// Release any cached data, images, etc that aren't in use. | |
} | |
- (void)viewDidUnload { | |
// Release any retained subviews of the main view. | |
// e.g. self.myOutlet = nil; | |
} | |
- (void)dealloc { | |
[persons release]; | |
[cachedImages release]; | |
[operationQueue release]; | |
[super dealloc]; | |
} | |
#pragma mark ----------------------------------------------------------------------- | |
#pragma mark Loading Progress UI | |
- (void)showLoadingIndicators{ | |
if (!spinner) { | |
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; | |
[spinner startAnimating]; | |
loadingLabel = [[UILabel alloc] initWithFrame:CGRectZero]; | |
loadingLabel.font = [UIFont systemFontOfSize:20]; | |
loadingLabel.textColor = [UIColor grayColor]; | |
loadingLabel.text = @"Loading..."; | |
[loadingLabel sizeToFit]; | |
static CGFloat bufferWidth = 8.0; | |
CGFloat totalWidth = spinner.frame.size.width + bufferWidth + loadingLabel.frame.size.width; | |
CGRect spinnerFrame = spinner.frame; | |
spinnerFrame.origin.x = (self.tableView.bounds.size.width - totalWidth) / 2.0; | |
spinnerFrame.origin.y = (self.tableView.bounds.size.height - spinnerFrame.size.height) / 2.0; | |
spinner.frame = spinnerFrame; | |
[self.tableView addSubview:spinner]; | |
CGRect labelFrame = loadingLabel.frame; | |
labelFrame.origin.x = (self.tableView.bounds.size.width - totalWidth) / 2.0 + spinnerFrame.size.width + bufferWidth; | |
labelFrame.origin.y = (self.tableView.bounds.size.height - labelFrame.size.height) / 2.0; | |
loadingLabel.frame = labelFrame; | |
[self.tableView addSubview:loadingLabel]; | |
} | |
} | |
- (void)hideLoadingIndicators{ | |
if (spinner) { | |
[spinner stopAnimating]; | |
[spinner removeFromSuperview]; | |
[spinner release]; | |
spinner = nil; | |
[loadingLabel removeFromSuperview]; | |
[loadingLabel release]; | |
loadingLabel = nil; | |
} | |
} | |
#pragma mark ----------------------------------------------------------------------- | |
#pragma mark Twitter Data Loading OPERATIONQUEUE #1 : LOAD TWITTER DATA | |
-(void)beginLoadingTwitterData{ | |
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(synchronousLoadTwitterData) object:nil]; | |
[operationQueue addOperation:operation]; | |
[operation release]; | |
} | |
-(void)synchronousLoadTwitterData{ | |
// Beause this is ran inside a seprate thread we have to be real careful | |
// that the data we play with here doesn't clobber another thread that is | |
// running at the exact same time. We will build a temprary array that we | |
// will assign the instance variable to only on the main thread. | |
NSMutableArray *tempArray = [[NSMutableArray alloc] init]; | |
// GET PLIST FILE INFO | |
filePath = [[NSBundle mainBundle] pathForResource:@"TwitterUsers" ofType:@"plist"]; | |
if (filePath) | |
{ | |
// CREATE TWITTERIDS ARRAY FROM PLIST FILE PATH | |
NSArray *twitterIds = [NSArray arrayWithContentsOfFile:filePath]; | |
if (twitterIds != nil) | |
{ | |
// This is now in local scope. Otherwise when used as an instance | |
// variable it would have eaten up more memory then needed. | |
Person *twit = nil; | |
for (int count=0; count<[twitterIds count]; count++) | |
{ | |
// now get information from twitter | |
NSDictionary *dict = [[NSDictionary alloc] initWithDictionary:[TwitterHelper fetchInfoForUsername:[twitterIds objectAtIndex:count]]]; | |
//CREATE INSTANCES BY INITTING THEM WITH THESE VALUES VIA THE PERSON CLASS CALL | |
twit =[[Person alloc] initWithScreenName:[dict objectForKey:@"name"] RealName:[dict objectForKey:@"screen_name"] URL:[NSURL URLWithString:[dict objectForKey:@"profile_image_url"]]]; | |
//NSLog(@"%@", twit.imageURL); | |
//ADD INSTANCES TO AN ARRAY & RELEASE THE CLASS OBJECT & THEN RETURN TO MAINTHREAD... | |
[tempArray addObject:twit]; | |
[twit release]; | |
} | |
} | |
} | |
[self performSelectorOnMainThread:@selector(didFinishLoadingTwitterDataWithResults:) withObject:tempArray waitUntilDone:NO]; | |
} | |
-(void)didFinishLoadingTwitterDataWithResults:(id)result{ | |
self.persons = (NSArray *) result; | |
[self hideLoadingIndicators]; | |
[self.tableView reloadData]; | |
[self.tableView flashScrollIndicators]; // I guess this is only if you have more users that the screenful which isnt my case | |
} | |
#pragma mark ----------------------------------------------------------------------- | |
#pragma mark Cached Image Loading | |
-(UIImage*)cachedImageForURL:(NSURL*)url{ | |
id cachedObject = [cachedImages objectForKey:url]; | |
if (cachedObject == nil) | |
{ | |
//set loading placeholder in our cache dict | |
[cachedImages setObject:LoadingPlacedholder forKey:url]; | |
//Create and queue a new image loading op | |
ImageLoadingOp *operation = [[ImageLoadingOp alloc] initWithImageURL:url target:self action:@selector(didFinishLoadingImageWithResult:)]; | |
[operationQueue addOperation:operation]; | |
[operation release]; | |
} else if(![cachedObject isKindOfClass:[UIImage class]]) | |
{ | |
//were already loading the image, dont kick off another request | |
cachedObject = nil; | |
} | |
NSLog(@"EXIT CIFU"); | |
return cachedObject; | |
} | |
-(void)didFinishLoadingImageWithResult:(NSDictionary*)result{ | |
NSURL *url = [result objectForKey:@"url"]; | |
UIImage *image = [result objectForKey:@"image"]; | |
[cachedImages setObject:image forKey:url]; | |
[self.tableView reloadData]; | |
NSLog(@"EXIT DFLIWR"); | |
} | |
#pragma mark ----------------------------------------------------------------------- | |
#pragma mark Table view methods | |
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { | |
return 1; | |
} | |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { | |
// This could be 0 because the loading thread hasn't finished yet. | |
if (persons == nil) | |
return 0; | |
return [persons count]; | |
} | |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { | |
static NSString *CellIdentifier = @"Cell"; | |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; | |
NSLog(@"here at CFRAIP Identifier"); | |
if (cell == nil) { | |
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease]; | |
} | |
// Set up data for cells | |
// There isn't any need to create a new (copy of) Person because we have a | |
// reference in the persons array. Instead lets just send the reference. | |
// cell.textLabel.text will hold on to a copy of the string anyway. | |
Person *personToDisplay = [persons objectAtIndex:indexPath.row]; | |
// Set up the cell... | |
NSLog(@"aft adding array to person object"); | |
cell.textLabel.text = personToDisplay.screenName; | |
//[self cachedImageForURL:personToDisplay.imageURL]; | |
[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton]; | |
// No longer needed. | |
//[personToDisplay release]; | |
return cell; | |
} | |
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { | |
// Navigation logic may go here. Create and push another view controller. | |
/* | |
PersonDetailViewController *detailView = [[PersonDetailViewController alloc] initWithStyle:(UITableViewStyle)UITableViewStyleGrouped]; | |
detailView.someTwit = [persons objectAtIndex:indexPath.row]; | |
[self.navigationController pushViewController:detailView animated:YES]; | |
[detailView release]; | |
*/ | |
} | |
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { | |
return 55; | |
} | |
#pragma mark ----------------------------------------------------------------------- | |
#pragma mark makeNewTweet | |
-(void)makeNewTweet;{ | |
/* | |
StatusComposeViewController *myModalViewController = [[StatusComposeViewController alloc] initWithNibName:@"StatusComposeViewController" bundle:nil]; | |
//self.myModalViewController = [[StatusComposeViewController alloc] initWithNibName:NSStringFromClass([StatusComposeViewController class]) bundle:nil]; | |
//[self.navigationController presentModalViewController:self.myModalViewController animated:YES]; | |
NavigationController = [[UINavigationController alloc]init]; | |
[NavigationController pushViewController:myModalViewController animated:NO]; | |
[self presentModalViewController:NavigationController animated:YES]; | |
[myModalViewController release]; | |
Must add //[NavigationController release]; to dealloc for this new UINavCont... | |
*/ | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment