The short answer is “this is the way Apple chose to do it, so you must do it this way.” But that answer isn’t very fulfilling.
In regards to having the font object determine the size given a string parameter: the font object is a model object as well. Regardless, the implementation of finding the size of a string given a particular font would rely on pulling data from both of these objects; the object that does the actual work (i.e., the one that has implemented the method to perform this task) will be doing the same thing, the only difference is which object’s ivars are accessed directly and which are accessed via an accessor method.
Now, in terms of the context object doing the drawing versus the string object doing the drawing… the context object is actually doing the drawing here. These NSString category methods actually make Core Graphics function calls, like CGContextShowText, CGContextShowGlyphs, etc. The Core Graphics routines are fairly “low level” and they are not easy to gather the correct parameters for. Therefore, the UIStringDrawing category (which contains the drawInRect:withFont: method) was added to NSString so you don’t have to do that. You have a string, you want to draw it, you invoke one of the UIStringDrawing methods, and all of the Core Graphics code is written for you.
In effect, UIStringDrawing is a set of convenience methods to help bridge the gap between NSString/NSFont and Core Graphics. The UIStringDrawing additions do not fall exactly into what an NSString is (a model object representing a string of characters), but they are very strongly related to the process of drawing a string and rely on the existence of an NSString. Also, because the Core Graphics library is C, it cannot be extended via a category nor would it even understand what an NSString is (since NSString is an Objective-C object). Therefore, the decision was made to add these gap-briding methods as a category of NSString.
This is a fairly common practice with model objects that can be represented in some other context. Apple puts these convenience methods onto the object itself, and in practice, it works out rather well. Another example is UIColor. UIColor has an instance method, set (also: setStroke, setFill) that does something very similar. When you send the message set to a UIColor instance, it ends up calling CGContextSetFillColor/CGContextSetStrokeColor with its instance variables that represent the red, green, blue and alpha channels it stores.
Hope that helps.