Skip to content

Instantly share code, notes, and snippets.

@sukima
Forked from quique123/ImageLoadingOp.h
Created November 4, 2009 20:05
Show Gist options
  • Save sukima/226332 to your computer and use it in GitHub Desktop.
Save sukima/226332 to your computer and use it in GitHub Desktop.
Thread programming review
//
// 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
//
// 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
//
// 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
//
// 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
//
// 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
//
// 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