Welcome to the MacNN Forums.

If this is your first visit, be sure to check out the FAQ by clicking the link above. You may have to register before you can post: click the register link above to proceed. To start viewing messages, select the forum that you want to visit from the selection below.

You are here: MacNN Forums > Software - Troubleshooting and Discussion > Developer Center > Advanced (?) Cocoa book?

Advanced (?) Cocoa book?
Thread Tools
Professional Poster
Join Date: Sep 2000
Location: San Francisco
Status: Offline
Reply With Quote
Apr 15, 2003, 04:08 PM
 
I am looking for a new cocoa book to help me get to the next level. I'd like something that includes a good discussion of threads. Recommendations?

I already have the Hillegrass book and the Apple book. I downloaded the Vermont Recipes source code and there are no instances of 'thread' found in the source, so I doubt that book would help.

kman
     
Addicted to MacNN
Join Date: May 2001
Location: Cupertino, CA
Status: Offline
Reply With Quote
Apr 15, 2003, 04:21 PM
 
You specifically want threads in Cocoa, or threads in general?
     
kman42  (op)
Professional Poster
Join Date: Sep 2000
Location: San Francisco
Status: Offline
Reply With Quote
Apr 15, 2003, 06:14 PM
 
Originally posted by itai195:
You specifically want threads in Cocoa, or threads in general?
I guess I could use a little of both. Whatever combination will make them usable for me in cocoa programming. I've tried to find info on the net, but I haven't been albe to find basic info about strategies for using NSLocks, NSConditionLock performSelectorOnMainThread, NSConnection, etc. I've found bits and pieces, but not quite enough to let me understand what the best approaches are for various situations.

kman
     
Mac Elite
Join Date: Sep 2000
Location: Tempe, AZ
Status: Offline
Reply With Quote
Apr 15, 2003, 08:48 PM
 
I think threading strategies are pretty general, so there probably isn't a lot of Cocoa-specific stuff (caveat - I haven't done a lot of threading in Cocoa). So, you can probably just get the basics down about threads, mutexes, locks & semaphores and all that horrible stuff, and then apply the knowledge to Cocoa.

This is probably a bit formal for what you're looking for (it's platform-generic C++ stuff), but it's the only link I've got. The concepts are generally applicable, and if you stare at the sample code for a little while, you'll start to see how things like threads and locks work. But, be warned, that link is not for the faint-of-heart.

By the way, you have read Apple's docs, right?
Geekspiff - generating spiffdiddlee software since before you began paying attention.
     
Forum Regular
Join Date: May 2001
Location: Oviedo, Floriduh USA
Status: Offline
Reply With Quote
Apr 15, 2003, 09:28 PM
 
O'Reilly has a good book on Posix threads that you should read, since Posix directly relates to UNIX. It's Pthreads Programming, ISBN 1-56592-115-1.

There are many articles on Java threads which aren't so different either.
folding@home is good for you.
     
Addicted to MacNN
Join Date: May 2001
Location: Cupertino, CA
Status: Offline
Reply With Quote
Apr 16, 2003, 12:05 PM
 
Well I don't know any books specifically about threading, but this is generally considered an operating systems topic. You might want to just pick up a good operating systems textbook, read the sections on threads, deadlock, synchronization, etc and keep the rest of the book for reference. This book is what we used in my OS class in school, though I know it's expensive and I've personally only skimmed it. Might get you started at looking in the right direction though... and your local library may have it. The advantage of looking at threads from an OS standpoint is that you will get a very generalized understanding of the concept, which you'll be able to apply in the future wherever you need it. On the other hand, if someone learned about threads by simply studying the Java implementation, they would never learn anything about semaphores, for example.

Most of the language-specific books I've seen that mention threads don't go into very much depth, but you may be able to find one that suits your needs.

BTW, you've probably already read this, but just to make sure, here's Apple's threading documentation.
     
kman42  (op)
Professional Poster
Join Date: Sep 2000
Location: San Francisco
Status: Offline
Reply With Quote
Apr 16, 2003, 01:27 PM
 
Thanks for the input. I've read Apple's documentation several times plus some other info from around the web. I'm still a bit confused so maybe I should just ask some general questions here to get me started.

Here's a simple hypothetical example that I think I can extrapolate from with a few answers.

An app with a MainController that contains the code for the UI and most of the code running in the main thread. A single Data object with methods (Data::collectData) to collect data from an offsite location that might be slow (thus the use of threads).

As the Main runloop progresses an NSTimer will fire and launch the method in the Data object to update its data. The UI should be updated after the data is collected by calling the accessor methods in the Data instance.

1) When should I launch the new thread for data collection? Should the NSTimer fire the method from the Data object and have that method start a new thread or should the timer fire a method in the MainController that then detaches a thread using the method from Data?

2) I think the lock should be in the scope of MainController. Should it go right before the call to Data::collectData?

3) Should I use NSConnections to change the NSConditionLock in MainController from within the thread running with Data::collectData or should I just use performOnMainThread: from Data::collectData to tell MainController that the data collection method is complete?

I think my questions demonstrate my confusion. I could just use some basic strategy suggestions for getting started. Where to use locks? When to use perfromMethodOnMainThread vs. NSConnections?

Any help would be great.

thanks,
kman
     
Addicted to MacNN
Join Date: May 2001
Location: Cupertino, CA
Status: Offline
Reply With Quote
Apr 16, 2003, 03:48 PM
 
I'm not sure exactly how to answer your question because I don't know Cocoa.

I do know Java and Swing, and I know that in Swing you would not need a lock at all... You would have your view objects listening for changes to your data object. So when you have finished retrieving new data in your thread, you would generate a change event, and the forked thread would terminate. The Swing event handling thread would then take over and run the event listener in the view object, in which the view can update itself based on changes to the data object.

I'm not sure how Cocoa would handle all of this. My impression is that you do not need a lock at all. You just need an NSConnection (or some similar construct) to notify your main thread when the data check has completed, so that your main thread can update your UI if necessary. You probably do not want to directly modify your UI from the data retrieval thread, because I'm guessing that Cocoa's UI methods are probably not thread-safe. But again, I don't know Cocoa well enough to give you a totally educated answer.
     
Forum Regular
Join Date: Jun 2001
Location: Savoy, IL USA
Status: Offline
Reply With Quote
Apr 17, 2003, 03:08 PM
 
Originally posted by kman42:
I think my questions demonstrate my confusion. I could just use some basic strategy suggestions for getting started. Where to use locks? When to use perfromMethodOnMainThread vs. NSConnections?

Any help would be great.

thanks,
kman
Here're my suggestions. Everything that I say is stuff that I've used, and you should probably have the NSThread & NSLock documentation open in either Cocoa Browser or a web browser.

Ok, first question: when do I spawn the new thread?

This is really a hard question to answer generally; it is really dependant on the situation. Having said that, the rule of thumb that I like to use is that of implementation hiding: basically, you ask objects to perform a service, and you don't care how that object does its job. If it uses a thread, that's great. In your case, you want Controller to tell Data to refresh its contents. Then, I assume that Controller will continue doing whatever, and that Data will tell Controller when it is done updating. I'll show you below how I would do this.

Second question: where does the lock go?

Locks are used so that if you have multiple objects accessing shared data, (or in event driven systems [like yours with the timer is] may cause a single object to try to access/change the same data multiple times), the data doesn't get corrupted (or, you don't get segmentation faults as memory that your app uses is free()'d by some other object...). Because you want access to data that would be protected by a lock, you want the lock to be in the 'lowest' level that they can be; that is, put the lock where the objects that are accessing or changing data can get to them. If you have only 1 object accessing/changing shared data, put the lock in that object. If you have multiple objects all accessing/changing data in a controller, then I would put the lock in the controller. Basically, the lock & the data should go together (logically, not necessarily physically. If, for example, your controller was a clearinghouse for accessing some lower level data storage object, and all objects should go through the controller to access data, then the data logically belongs to the controller, even though the data may physically belong to another object).

Now, since I recommend keeping the lock in with the data, the third question becomes irrelevant; you don't need to worry about this, since you have the lock all set up in the data object. In fact, your third question (synchronization of the lock itself) is a good indication that the lock should go in with the data object; that is, since you need to atomically test & lock in one function call, having the lock in the controller would not work. Basically, without atomic locking (that is, test & lock in one function call; its the only way to prevent race conditions in this sort of set up. I guess I should explain a little further: if you had to do something like:

Code:
if (! locked) { locked = YES; // do work locked = NO; }
then you have situations where two threads could enter the same if statement doing work at the same time (well, on MP machines), and you're not actually locking anything. So, to get around this, we use NSLock's -tryLock: method, which is atomic.)

Whew. So, basically, what I would recommend for your situation is to have the lock be in the data class, since that is the class that is going to be updating itself with information. So, to show you some code, here's what I'd do:

In Data.h:

Code:
#import <Cocoa/Cocoa.h> @interface Data : NSObject { NSLock *theLock; NSArray *data; // Or, whatever. It doesn't matter for the purposes of this example. } - (IBAction) refresh: (id) sender; // Other functions... @end
In Controller.h:
Code:
#import <Cocoa/Cocoa.h> @class Data; @interface Controller : NSObject { NSTimer *timer; Data *myData; // Whatever else } - (void) timerHandler: (NSTimer *) theTimer; - (void) refreshDoneNotificationHandler: (NSNotification *) note; // You'll see... @end
In Data.m:
Code:
#import "Data.h" @interface Data(Private) // This is private, because you don't want to expose this to anyone else! - (void) doRefresh: (id) anArg; @end @implementation Data - (id) init { if ((self = [super init]) != nil) { theLock = [[NSLock alloc] init]; [theLock unlock]; // Shouldn't be necessary, but just want you to know that it's unlocked when init'd data = [[NSArray alloc] init]; } return self; } - (void) dealloc { [theLock release]; [data release]; } - (IBAction) refresh { if (! [theLock tryLock]) { // Couldn't grab the lock, so someone is already refreshing: we just exit return; } // Note! Remove the spaces in the @selector call; I just put them there so UBB wouldn't interpret this as doRefresh*smile* [NSThread detachNewThreadSelector:@selector( doRefresh: ) toTarget:self withObject:nil]; } - (void) doRefresh: (id) anArg { // Very important!! You'll leak memory if you don't create a new autorelease pool for this thread! NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Do all your work here with data, hitting the network, whatever. // Now we're done with all the work, unlock the lock & let Controller know. [theLock unlock]; // You can do this with a direct method call, or with a Notification. I'll just use a Notification. [[NSNotificationCenter defaultCenter] postNotificationName: @"DataRefreshDone" object: data]; // data could be anything here. You don't even need to send data; it could just be nil [pool release]; // Now, exit this thread, since we're done. [NSThread exit]; } @end
In Controller.m:
Code:
#import "Controller.h" #import "Data.h" @implementation Controller - (id) init { if ((self = [super init]) != nil) { myData = [[Data alloc] init]; [[NSNotificationCenter defaultCenter] addObserver: self selector:@selector(refreshDoneNotificationHandler:) name: @"DataRefreshDone" object: nil]; // Timer for 60 seconds in the future. Just replace 60 with whatever number of seconds. This is repeating, so set the repeat as well! timer = [[NSTimer alloc] initWithFireDate: [NSDate dateWithTimeIntervalSinceNow: 60.0] interval:60.0 target: self selector: @selector( timerHandler: ) userInfo: nil repeats: YES]; } return self; } - (void) dealloc { [timer invalidate]; // We're done with this, so shut it off [timer release]; [myData release]; } - (void) timerHandler: (NSTimer *) theTimer { // Tell myData to refresh itself and then carry on listening for events from user, timer, etc. [myData refresh: self]; // Remember, if myData is already refreshing itself, the tryLock call will return NO, and it'll just return. // NOTE: If you reduce the length of the timer, make sure that the handling done in here will be shorter than the repeat time of the timer! Otherwise, make the timer non-repeating and do a manual repeat... } - (void) refreshDoneNotificationHandler: (NSNotification *) note { NSArray *newData = [note object]; // Redisplay everything with the newly refreshed data } @end
There, clear as mud, right? Anyway, while I didn't try to enter this and compile it, it should be alright (yeah, right! Famous last words!! And let me tell you, Safari isn't the best code input application...). Please write back with comments, questions, etc. If I think of anything else, I'll edit this post. If people find mistakes in all this, I'll edit the post. Anyway, this should be an OK boilerplate for your purposes, and hopefully you'll get an idea about why the things work the way they do.

[Edit: Added autorelease pool and [NSThread exit] which I somehow forgot!! ]
(Last edited by Mskr; Apr 17, 2003 at 03:44 PM. )
Software Architect, CodeTek Studios, Inc.

12" AlBook 867 (Combo drive) 640 MB/40 GB (work development machine) -- TiBook 400MHz/384MB/10GB (home machine)
CodeTek VirtualDesktop Pro: Power multitasking! -- DockExtender: Powerful, efficient launcher for Apps, Docs and everything else!
     
kman42  (op)
Professional Poster
Join Date: Sep 2000
Location: San Francisco
Status: Offline
Reply With Quote
Apr 17, 2003, 04:34 PM
 
Wow! You rule (as does Virtual Desktops!). That actually cleared things up quite a bit!

One question: so, you can post a notification from one thread and it will be rec'd in another? I'm not sure why I didn't think that was possible, but it makes life much clearer knowing that it is. I'll try to work your suggestions into my app tonight and see how it works. Thanks for the effort: it really is enlightening.

kman
     
Forum Regular
Join Date: Jun 2001
Location: Savoy, IL USA
Status: Offline
Reply With Quote
Apr 17, 2003, 04:43 PM
 
Originally posted by kman42:
Wow! You rule (as does Virtual Desktops!). That actually cleared things up quite a bit!

One question: so, you can post a notification from one thread and it will be rec'd in another? I'm not sure why I didn't think that was possible, but it makes life much clearer knowing that it is. I'll try to work your suggestions into my app tonight and see how it works. Thanks for the effort: it really is enlightening.

kman
Yeah, the notification just goes to the notification center, which will distribute it to the controller. This has worked for me in the past, and shouldn't be a problem.

No problem on the effort! Just passing on the knowledge... Glad it's useful to you. I'll check back tomorrow to see if you have any other questions.
Software Architect, CodeTek Studios, Inc.

12" AlBook 867 (Combo drive) 640 MB/40 GB (work development machine) -- TiBook 400MHz/384MB/10GB (home machine)
CodeTek VirtualDesktop Pro: Power multitasking! -- DockExtender: Powerful, efficient launcher for Apps, Docs and everything else!
     
Addicted to MacNN
Join Date: May 2001
Location: Cupertino, CA
Status: Offline
Reply With Quote
Apr 17, 2003, 04:53 PM
 
Originally posted by Mskr:
Here're my suggestions. Everything that I say is stuff that I've used, and you should probably have the NSThread & NSLock documentation open in either Cocoa Browser or a web browser.
Good post. Like I said, I'm not a Cocoa expert, but isn't the fact that you've got the timer thread acquiring the lock and the data refresh thread releasing the lock a problem?

I think the point I mangled in my earlier post was that it makes more sense to me to not use a timer or a lock at all in this situation. Just have a thread running for the life of your program that occasionally checks for new data and notifies the controller when necessary. You can just have a loop running to check for data, then notify, then sleep for however long is necessary, repeat. In this situation, you would only need a lock if the data can be modified both by the data refresh thread and by some other thread (due to UI manipulation, for example) simultaneously. This solution is even a performance enhancement, since you never have to worry about locking and you don't have the overhead of forking new threads all the time.

I also think this use of NSNotificationCenter is dangerous. It's a thread-safe class, but it delivers notifications to registered objects in the thread that posted the notification. Hence, the refreshDoneNotificationHandler in your Controller class will run from within the data refresh thread. This will probably work correctly in this scenario since there is only one data refresh thread at a time, but it's an unsafe habit to get into. I'm not very familiar with how to do these things in Cocoa, but I know there are NSDistributedNotificationCenter and NSConnection classes that are supposed to facilitate communication between threads. The point being that you want any UI updates to happen in the main thread, not in the data refresh thread.

Also, just want to reiterate the distinction that thinking of concurrency issues in terms of how many OBJECTS can execute particular blocks of code is incorrect. The real issue is how many threads can execute the code.
(Last edited by itai195; Apr 17, 2003 at 05:39 PM. )
     
Senior User
Join Date: Dec 2001
Status: Offline
Reply With Quote
Apr 17, 2003, 08:23 PM
 
Excellent post Mskr, but you are wrong about notifications. From the "Class Description" section of Apple's docs for NSNotificationCenter:

In a multithreaded application, notifications are always delivered in the thread in which the notification was posted, which may not be the same thread in which an observer registered itself.

You must explicitly send a message to another thread. This can be done the (somewhat) heavyweight Distrubted Objects way with NSConnections, or the lightweight way with NSMessagePorts. Here is an excellent class for doing it the lightweight way (it's called ThreadMessage). Also, you can do it the totally wrong way with NSDistributedNotificationCenter Both of the correct techniques are actually quite simple, but it takes a day or so of practice to wrap your brain around how and why it works. Once you've got it, it's like riding a bike.
"Think Different. Like The Rest Of Us."

iBook G4/1.2GHz | 1.25GB | 60GB | Mac OS X 10.4.2
Athlon XP 2500+/1.83GHz | 1GB PC3200 | 120GB | Windows XP
     
Forum Regular
Join Date: Jun 2001
Location: Savoy, IL USA
Status: Offline
Reply With Quote
Apr 18, 2003, 11:24 AM
 
Originally posted by itai195:
Good post. Like I said, I'm not a Cocoa expert, but isn't the fact that you've got the timer thread acquiring the lock and the data refresh thread releasing the lock a problem?
No, I don't think that this is a problem simply because the lock is going to protect the manipulation of the data. The lock protects the users of the data, in that the data objects are guarenteed not to be manipulated while they're being used (since the way I was doing it was to return the updated data object with the Notification). Now, on further reflection, this is probably not the way to do that either, since there is no read lock around the data. So, perhaps there should be a read lock that is used in an accessor method that the main thread (Controller) uses. Gah. I don't really have time to rewrite the code, but basically, what I'm saying is that the update code should lock the readlock before it updates data and release it immediately after the update, while the accessor method would lock the readlock when the data is requested, copy the data, and release the readlock, returning the copy. I know that this isn't the most memory efficient solution, but it's the first one I thought of, which generally means that I need to think more about it.


I think the point I mangled in my earlier post was that it makes more sense to me to not use a timer or a lock at all in this situation. Just have a thread running for the life of your program that occasionally checks for new data and notifies the controller when necessary. You can just have a loop running to check for data, then notify, then sleep for however long is necessary, repeat. In this situation, you would only need a lock if the data can be modified both by the data refresh thread and by some other thread (due to UI manipulation, for example) simultaneously. This solution is even a performance enhancement, since you never have to worry about locking and you don't have the overhead of forking new threads all the time.
But, I think that you do need to use a lock around anything that you're going to be accessing while it's possible that another thread is modifying it. For example, if you use an NSArray, you definitely don't want that array to be updating while you enumerate through it, since you'll likely get runtime errors.


I also think this use of NSNotificationCenter is dangerous. It's a thread-safe class, but it delivers notifications to registered objects in the thread that posted the notification. Hence, the refreshDoneNotificationHandler in your Controller class will run from within the data refresh thread. This will probably work correctly in this scenario since there is only one data refresh thread at a time, but it's an unsafe habit to get into. I'm not very familiar with how to do these things in Cocoa, but I know there are NSDistributedNotificationCenter and NSConnection classes that are supposed to facilitate communication between threads. The point being that you want any UI updates to happen in the main thread, not in the data refresh thread.

Also, just want to reiterate the distinction that thinking of concurrency issues in terms of how many OBJECTS can execute particular blocks of code is incorrect. The real issue is how many threads can execute the code.
True, true. That was a mistake that I made in my sketched out code, and the thoughts behind it. I was a little hesitant to write my post because I don't have a ton of threading experience, but I did have some, where the other people posting didn't seem to have direct Cocoa threading experience. But, I guess I should have thought a little bit more about this before I did post...

I don't think that I'm using notifications anywhere in the places that we use threading in our apps, but I definitely need to go through and see that we aren't. Thank you very much for this info. Thankfully, the other developers here tend to have more experience and knowledge about threads, and hopefully this isn't being done anywhere...

Excellent post Mskr, but you are wrong about notifications. From the "Class Description" section of Apple's docs for NSNotificationCenter:

In a multithreaded application, notifications are always delivered in the thread in which the notification was posted, which may not be the same thread in which an observer registered itself.

Originally posted by macmike42:
You must explicitly send a message to another thread. This can be done the (somewhat) heavyweight Distrubted Objects way with NSConnections, or the lightweight way with NSMessagePorts. Here is an excellent class for doing it the lightweight way (it's called ThreadMessage). Also, you can do it the totally wrong way with NSDistributedNotificationCenter Both of the correct techniques are actually quite simple, but it takes a day or so of practice to wrap your brain around how and why it works. Once you've got it, it's like riding a bike.
Thanks for this info, macmike. I'll take a look at this and use it where appropriate. I also wouldn't recommend using distributed notifications, which just seems like a hack to get around this problem... We use Mach ports a lot in some of our code, but its not something that I've worked with very much; our CTO is the one that did most of that work, so I don't have much direct experience with it. But thanks very much for your pointers & corrections to my post. Perhaps we should all pool our resources on this and write a Framework or something that would make this a bit easier, though less general and less powerful? Nah, maybe the thing to do is just get an Advanced Cocoa book written by someone that covers these topics.

Related to that last comment, does anyone have Cocoa Programming by Scott Anguish? I've seen it locally and really want to get it, but if someone would review it, that would be great. Perhaps I should just start a new thread about this book, but this thread was supposed to be about cocoa books...
Software Architect, CodeTek Studios, Inc.

12" AlBook 867 (Combo drive) 640 MB/40 GB (work development machine) -- TiBook 400MHz/384MB/10GB (home machine)
CodeTek VirtualDesktop Pro: Power multitasking! -- DockExtender: Powerful, efficient launcher for Apps, Docs and everything else!
     
Addicted to MacNN
Join Date: May 2001
Location: Cupertino, CA
Status: Offline
Reply With Quote
Apr 18, 2003, 11:40 AM
 
Originally posted by Mskr:
No, I don't think that this is a problem simply because the lock is going to protect the manipulation of the data. The lock protects the users of the data, in that the data objects are guarenteed not to be manipulated while they're being used (since the way I was doing it was to return the updated data object with the Notification). Now, on further reflection, this is probably not the way to do that either, since there is no read lock around the data. So, perhaps there should be a read lock that is used in an accessor method that the main thread (Controller) uses. Gah. I don't really have time to rewrite the code, but basically, what I'm saying is that the update code should lock the readlock before it updates data and release it immediately after the update, while the accessor method would lock the readlock when the data is requested, copy the data, and release the readlock, returning the copy. I know that this isn't the most memory efficient solution, but it's the first one I thought of, which generally means that I need to think more about it.
Mskr, if the way the above code acquires/releases the lock isn't a problem, then Cocoa must do things very differently from other platforms. In Java, for example, a thread cannot call an object's notify method (thereby releasing the lock) unless that same thread is holding the lock. Cocoa may do things differently, who knows.

Either way, it's extremely easy to make a lot of mistakes with multithreaded apps. Everyone does it. I know I've used NSNotificationCenter inappropriately in my apps as well.
     
Senior User
Join Date: Dec 2001
Status: Offline
Reply With Quote
Apr 18, 2003, 07:10 PM
 
Originally post by Mskr
Related to that last comment, does anyone have Cocoa Programming by Scott Anguish? I've seen it locally and really want to get it, but if someone would review it, that would be great. Perhaps I should just start a new thread about this book, but this thread was supposed to be about cocoa books...
*sound of screeching tires while thread gets back on the road*

This is an excellent book, and the only Cocoa book I own. I've skimmed all the mainstream books, and for my learning style, Scott's is the best (although it was also written by Erick M Buck and Donald A Yackman, credit where credit is due). It is not an advanced book, and as far as I know, none exist yet for Cocoa. It can probably be considered a beginner-to-intermediate-level book, as it provides only a minimal amount of hand-holding. My only complaint about Cocoa Programming is that it doesn't delve into the Foundation framework in great detail. All the true gold is really found there, but that level is coverage would probably fall under the "advanced" category.

The chapter on threads and tasks is wonderful, but once again, it only scratches the surface. 1000 pages could easily be written about threads alone. Cocoa Programming does, however, introduce the use of NSConnections in thread, and it does it the correct way. The code seems quite convoluted at first glance (and it is, to a degree, but it is for training purposes). You'll need to experiment with it for quite a bit before you can get it right. Once you understand it, though, you'll never be able to stand code that does it the "simple" way.

Long story short, proper communication between threads in Cocoa is something that is hard to grasp. Cocoa Programming is the only source I could find that explained it well. It is far from second nature to me yet, and I couldn't explain it to anyone if I tried, but at least I know how to do it. I highly recommend the book.
"Think Different. Like The Rest Of Us."

iBook G4/1.2GHz | 1.25GB | 60GB | Mac OS X 10.4.2
Athlon XP 2500+/1.83GHz | 1GB PC3200 | 120GB | Windows XP
     
   
Thread Tools
Forum Links
Forum Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On
Top
Privacy Policy
All times are GMT -5. The time now is 03:24 PM.
All contents of these forums © 1995-2011 MacNN. All rights reserved.
Branding + Design: www.gesamtbild.com
vBulletin v.3.8.7 © 2000-2011, Jelsoft Enterprises Ltd., Content Relevant URLs by vBSEO 3.3.2