In the previous tutorial you used LLBD to gain insight as to whether and how you might become the delegate of UIWebView
‘s scrollView
while learning some tips and tricks for using LLDB to discover how to insert code into the UIScrollViewDelegate
methods of the UIWebView
without getting in the way of Apple’s implementation of the UIWebView
‘s handling of the UIScrollViewDelegate
methods.
Dig Deeper
How does the delegate forwarder work? By what specific mechanism does both the web view and the scroll view’s delegate receive these callbacks and how does the delegate forwarder insinuate itself between the scroll view and these objects?
You can find out by using reverse engineering tools to inspect the UIKit library itself. UIWebView
and its constituent objects are closed-source. Some third party tools can help you to dig deeper into their implementation.
Refined Hypothesis
Hypothesis: _UIWebViewScrollViewDelegateForwarder
is somehow a sort of hidden delegate of the scroll view. This hidden delegate object implements all of the UIScrollViewDelegate
methods and forwards those methods both to the web view and to your delegate, favoring the return values from your delegate in methods like viewForZoomingInScrollView:
that return values.
Here is an object diagram including you saw in the previous tutorial of what you’ve discovered so far and this working hypothesis.
class-dump
Class-dump is a “command-line utility for examining the Objective-C runtime information stored in Mach-O files”. You can use class-dump to generate header files for libraries for which you do not have source. It will also generate headers for classes Apple does not expose in the framework’s headers containing private implementation details. In this section, you will use class-dump to generate headers for the UIKit framework to see if you can find some additional support for the working hypothesis.
You can install class-dump through the Homebrew package manager. If you already have homebrew installed, you can type brew install class-dump
into the terminal. Otherwise, you can install class-dump by downloading the installer from the web site or by downloading and building its source.
Once you have installed class-dump, open Terminal.app and type in the following commands. This will generate headers for UIKit and place them into a headers directory on your desktop.
$ cd /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.3.sdk/System/Library/Frameworks/UIKit.framework/
$ mkdir ~/Desktop/headers
$ class-dump ./UIKit -H -o ~/Desktop/headers
Note: As of this writing, the path to the UIKit framework for the iPhone Simulator is accurate for the latest non-beta version of Xcode (6.3.2). You may need to modify the iPhoneSimulator8.3.sdk
part of the path to match the version of the SDK you have installed. I have found it least painful to point class-dump to frameworks compiled for the x86 simulator, especially on iOS 9 betas where Apple no longer provides the compiled framework binaries. The output should be the same on x86 as you would see with the arm variant.
Now search in the Finder in ~/Desktop/headers folder for file names matching “UIWeb“.
First open the generated UIWebView.h
file. You will notice that it has a lot more detail than the public UIWebView.h
file. It lists every instance variable and method of UIWebView
.
If you were to search the text inside for “scrollView”, you would find these lines.
#import "UIScrollViewDelegate.h"
@class NSString, NSURLRequest, UIScrollView, UIWebViewInternal;
@interface UIWebView : UIView <NSCoding, UIScrollViewDelegate>
- (id)_scrollView;
- (void)scrollViewDidScroll:(id)arg1;
- (void)scrollViewWasRemoved:(id)arg1;
- (void)scrollViewDidScrollToTop:(id)arg1;
- (void)scrollViewDidEndDecelerating:(id)arg1;
- (void)scrollViewDidEndDragging:(id)arg1 willDecelerate:(_Bool)arg2;
- (void)scrollViewWillBeginDragging:(id)arg1;
- (void)scrollViewDidEndZooming:(id)arg1 withView:(id)arg2 atScale:(double)arg3;
- (void)scrollViewDidZoom:(id)arg1;
- (void)scrollViewWillBeginZooming:(id)arg1 withView:(id)arg2;
- (id)viewForZoomingInScrollView:(id)arg1;
@property(readonly, retain, nonatomic) UIScrollView *scrollView;
- (void)_updateBrowserViewExposedScrollViewRect;
UIWebView
appears to implement the UIScrollViewDelegate
methods directly. This matches assumptions.
You discovered earlier that the web view’s scroll view in the test project is an instance of _UIWebViewScrollView
. Open the generated _UIWebViewScrollView.h
file.
Here is an interesting snippet.
@interface _UIWebViewScrollView : UIWebScrollView
{
_UIWebViewScrollViewDelegateForwarder *_forwarder;
_Bool _bouncesSetExplicitly;
UIWebBrowserView *_browserView;
}
Note: UIWebScrollView
(without the underscore), which is the superclass of _UIWebScrollView
is itself a direct subclass of UIScrollView
. Open UIWebScrollView.h
if you’re curious what is in there.
Notice the _UIWebViewScrollViewDelegateForwarder
‘s’ _forwarder
ivar. UIWebScrollView has a pointer to an instance of _UIWebViewScrollViewDelegateForwarder
.
Here is a snippet of _UIWebViewScrollViewDelegateForwarder.h
that class-dump generated.
@interface _UIWebViewScrollViewDelegateForwarder : NSObject <UIScrollViewDelegate>
{
id <UIScrollViewDelegate> _delegate;
UIWebView *_webView;
}
@property(nonatomic) UIWebView *webView; // @synthesize webView=_webView;
@property(nonatomic) id <UIScrollViewDelegate> delegate; // @synthesize delegate=_delegate;
- (void)forwardInvocation:(id)arg1;
- (_Bool)respondsToSelector:(SEL)arg1;
- (id)methodSignatureForSelector:(SEL)arg1;
The _UIWebViewScrollViewDelegateForwarder
conforms to the UIScrollViewDelegate
protocol, has a delegate
property that also conforms to the UIScrollViewDelegate
protocol as well as a webView
property, presumably to hold a pointer to the UIWebView
within which the scroll view is contained so it can always forward delegate methods to the web view.
Here is an updated object diagram containing the additional information gained from headers generated by class-dump.
Message forwarding
You may have noticed that unlike UIWebView
, even though _UIWebViewScrollViewDelegateForwarder
reports conformance to the UIScrollViewDelegate
protocol, it does not actually implement any of the UIScrollViewDelegate
methods itself. They are conspicuously missing from the generated header files you made with class-dump. The current working hypothesis assumes that this object does implement these methods to call through to the web view and to send delegate methods to your delegate. You might also have noticed that it implements a curiously named method called forwardInvocation:
.
Normally, when an Objective-C object receives a message, it or one of its ancestors (super, super’s super, etc.) will contain an implementation corresponding to the message that the Objective-C runtime resolves and executes. When the Objective-C runtime cannot resolve a method, it has a mechanism allowing an object that does not handle a given message directly to forward the message to another object. This technique is called message forwarding. After the runtime has failed to find a method implementation by searching the object hierarchy, it calls - (void)forwardInvocation:(NSInvocation *)anInvocation
as a sort of last-ditch effort to let the object figure out what to do before the default implementation of forwardInvocation:
is called, which will cause this familiar error.
error: no visible @interface for 'SomeObjectNameHere' declares the selector 'someSelectorItDoesNotRecognize'
You can override forwardInvocation:
to do something else, such as ignore the message or forward the message along to another object. The primary intent of forwardInvocation:
is to allow an object to parcel methods out to one or more other objects for implementation. Using this technique, Objective-C can mimic multiple inheritance through composition. In addition to overriding forwardInvocation:
, you should also call through to the object you want to proxy for both respondsToSelector:
and methodSignatureForSelector:
.
For example, imagine you have a protocol called Talking
with a single optional method called talk
that returns an NSString
like this:
@protocol Talking
@optional
-(NSString *)talk;
@end
And you have a class called Foo
that implements the Talking
protocol like this:
@interface Foo : NSObject <Talking>
@end
@implementation Foo
-(NSString *)talk
{
return @"hello!";
}
@end
If you wanted to implement a class called BarProxiesFoo
that forwards the talk
method to an instance of Foo
stored in a property, you could implement that behavior with message forwarding like this:
@interface BarProxiesFoo : NSObject <Talking>
@property(strong)Foo *foo;
@end
@implementation BarProxiesFoo
-(BOOL)respondsToSelector:(SEL)aSelector
{
//Check if self responds first.
BOOL responds = [super respondsToSelector:aSelector];
//If not, check to see if self.foo responds. This will catch the "talk" method.
if (!responds) {
responds = [self.foo respondsToSelector:aSelector];
}
return responds;
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
//Similar to the above, first check if self has a signature for the selector.
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
//If not, check to see if self.foo does. This will catch the "talk" method.
if(signature == nil)
{
signature = [self.foo methodSignatureForSelector:aSelector];
}
return signature;
}
-(void)forwardInvocation:(NSInvocation *)inv
{
if([self.foo repondsToSelector:inv.selector])
{
[inv invokeWithTarget:self.foo];
}
}
@end
Here is the snippet of _UIWebViewScrollViewDelegateForwarder.h
again.
@interface _UIWebViewScrollViewDelegateForwarder : NSObject <UIScrollViewDelegate>
{
id <UIScrollViewDelegate> _delegate;
UIWebView *_webView;
}
@property(nonatomic) UIWebView *webView; // @synthesize webView=_webView;
@property(nonatomic) id <UIScrollViewDelegate> delegate; // @synthesize delegate=_delegate;
- (void)forwardInvocation:(id)arg1;
- (_Bool)respondsToSelector:(SEL)arg1;
- (id)methodSignatureForSelector:(SEL)arg1;
It implements all three methods used in message forwarding. This is a clue that _UIWebViewScrollViewDelegateForwarder
uses this technique to forward UIScrollViewDelegate
methods.
Further Refined Hypothesis
Hypothesis: _UIWebViewScrollViewDelegateForwarder
is a sort of hidden delegate for the scroll view. This hidden delegate object does not implement any of the UIScrollViewDelegate
methods, rather it forwards those methods both to the web view and to a delegate you set using the message forwarding mechanism described above.
Here is an object diagram including what you’ve discovered so far along with the hypothetical communications between these objects.
Digging Even Deeper with Hopper Disassembler
Hopper Disassembler “is a reverse engineering tool for OS X and Linux, that lets you disassemble, decompile and debug your 32/64bits [sic] Intel Mac, Linux, Windows and iOS executables”. Like class-dump, Hopper Disassembler reads Objective-C metadata from compiled code and it goes much, much further. In Hopper you can view disassembly for the code in a compiled binary and search to find code of interest. It can even decompile to generate pseudo-code for a function or method. You can use Hopper as a tool with which to peer into the implementation details of Apple’s frameworks. In this section you will use Hopper Disassembler to look into the implementation of the _UIWebViewScrollViewDelegateForwarder
‘s invocation forwarding and to see whether the _UIWebViewScrollViewDelegateForwarder
is really the scroll view’s delegate “under the covers”.
You can download a very usable, limited demo version from the Hopper website.
In Hopper Disassembler navigate to the “File -> Read Executable To Disassemble” menu item. Select the binary at the following path:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/UIKit.framework/UIKit
Note: While the open sheet is visible You can use the (⇧⌘G) keyboard shortcut, then copy-paste the path into the text field to quickly navigate the open sheet to the binary file.
Set the “Loader” drop down menu to FAT Archive and “FAT archive options” to x86 (32 bits). Click “Next” to continue.
Set the “Loader” to MachO 32bits and enable the Start automatic analysis after file is loaded and Parse Objective-C sections if present options as shown above. These are the defaults.
Let Hopper load and parse the file. This can take a while. You can watch the bottom right corner of the Hopper window for the current status.
You will use Hopper to dig into _UIWebViewScrollViewDelegateForwarder
to get a better understanding of how it uses the methodSignatureForSelector:
, respondsToSelector:
, and forwardInvocation:
to work its magic.
In the left pane enter UIWebViewScrollViewDelegateForwarder in the search field. Select -[_UIWebViewScrollViewDelegateForwarder respondsToSelector:]
from the results to reveal the disassembly.
Here is the pseudo code Hopper Disassembler generates for -[_UIWebViewScrollViewDelegateForwarder respondsToSelector:]
.
char -[_UIWebViewScrollViewDelegateForwarder respondsToSelector:](void * self, void * _cmd, void * arg_8) {
esi = @selector(respondsToSelector:);
eax = objc_msgSendSuper2(self, esi, arg_8);
ebx = 0x1;
if (eax == 0x0) {
eax = self->_webView;
eax = objc_msgSend(eax, esi, arg_8);
if (eax == 0x0) {
eax = self->_delegate;
eax = objc_msgSend(eax, esi, arg_8);
ebx = eax != 0x0 ? 0x1 : 0x0;
}
}
eax = ebx & 0xff;
return eax;
}
Note: esi
, eax
and ebx
are 32-bit x86 registers, since you opened the simulator version of the UIKit library and asked Hopper to open the 32 bit version.
Here is the respondsToSelector:
pseudo code translated into Objective-C.
-(BOOL)respondsToSelector:(SEL)theSelector
{
BOOL responds = [super respondsToSelector:theSelector];
if (!responds) {
responds = [self.webView respondsToSelector:theSelector];
if (!responds) {
responds = [self.delegate respondsToSelector:theSelector];
}
}
return responds;
}
Similar to the BarProxiesFoo
implementation in the message forwarding example, this code:
- Checks to see if the superclass responds to the selector.
- If not, checks to see if the web view responds.
- If not, checks to see if the delegate responds.
- If any of the above respond (super, web view, or delegate), return
YES
.
Here is the pseudo code Hopper Disassembler generates for -[_UIWebViewScrollViewDelegateForwarder forwardInvocation:]
.
void -[_UIWebViewScrollViewDelegateForwarder forwardInvocation:](void * self, void * _cmd, void * arg_8) {
ebx = self->_webView;
var_1C = @selector(selector);
eax = [arg_8 selector];
var_20 = @selector(respondsToSelector:);
if ([ebx respondsToSelector:eax] != 0x0) {
eax = self->_webView;
[arg_8 invokeWithTarget:eax];
var_28 = 0x1;
eax = self;
}
else {
var_28 = 0x0;
eax = self;
}
ebx = eax;
if (objc_msgSend(ebx->_delegate, var_20, objc_msgSend(arg_8, var_1C)) != 0x0) {
eax = ebx->_delegate;
[arg_8 invokeWithTarget:eax];
}
else {
ecx = arg_8;
if (var_28 == 0x0) {
[[ebx super] forwardInvocation:ecx];
}
}
return;
}
Here is the forwardInvocation:
pseudo code translated into Objective-C.
-(void)forwardInvocation:(NSInvocation *)anInvocation;
{
BOOL webViewOrDelegateRespondedToInvocation = NO;
if([self.webView respondsToSelector:anInvocation.selector])
{
[anInvocation invokeWithTarget:self.webView];
webViewOrDelegateRespondedToInvocation = YES;
}
if([self.delegate respondsToSelector:anInvocation.selector])
{
[anInvocation invokeWithTarget:self.delegate];
webViewOrDelegateRespondedToInvocation = YES;
}
if(!webViewOrDelegateRespondedToInvocation)
{
[super forwardInvocation:anInvocation];
}
}
This code:
- Checks to see if the web view responds to the invocation’s selector, if so invoke the invocation on the web view.
- Irrespective of the web view’s ability to respond, checks to see if the delegate responds to the invocation’s selector, if so invoke the invocation on the delegate.
- If neither the web view nor the delegate respond, calls through to super’s implementation of
forwardInvocation:
, otherwise it does not call through to super since the web view, the delegate, or both handled the invocation.
The invocation forwarding hypothesis appears correct! Apple has implemented all three invocation forwarding methods in the way you would expect for forwarding delegate messages to both the web view and to the scroll view’s delegate.
Now to find out whether and how the delegate forwarder becomes a hidden proxy of the scroll view delegate.
Search in Hopper Disassembler for -[_UIWebViewScrollView setDelegate:]
. While the result is selected, generate the pseudo code.
Here is the pseudo code generated for -[_UIWebViewScrollView setDelegate:]
.
void -[_UIWebViewScrollView setDelegate:](void * self, void * _cmd, void * arg_8) {
[self->_forwarder setDelegate:arg_8];
[[self super] setDelegate:0x0];
eax = self->_forwarder;
[[self super] setDelegate:eax];
return;
}
Here is the setDelegate:
pseudo code translated into Objective-C code.
-(void)setDelegate:(id)delegate
{
[self.forwarder setDelegate:delegate];
[super setDelegate:self.forwarder];
}
Here _UIWebViewScrollView
overrides the setDelegate
method of UIScrollView
, where it sets the forwarder’s delegate to the object passed into the method and makes the forwarder its “real” delegate by calling through to super’s implementation of setDelegate
. Since super’s delegate is the forwarder, causing the _delegate
ivar to point to the forwarder object, super’s implementation will send delegate methods to the forwarder. The forwarder then sends the messages along as you saw above.
Here is the getter’s pseudo code.
void * -[_UIWebViewScrollView delegate](void * self, void * _cmd) {
eax = [self->_forwarder delegate];
return eax;
}
Getter pseudo code translated into Objective-C code.
-(id)delegate
{
return [self.forwarder delegate];
}
This shows why the scroll view initially reported its delegate as nil
and later reported the view controller as its delegate instead of the forwarder object that actually recieves the delegate methods from the scroll view first. Although the “real” delegate of the scroll view is the forwarder object itself, you did not notice that in the initial LLDB investigation because _UIWebViewScrollView
returns the contents of the forwarder’s delegate property in the getter. Sneaky. Externally, it looks as though the _UIWebViewScrollView
has no delegate at all by default when actually its forwarder object is always treated as its delegate.
Try po scrollView->_delegate
in LLDB where you had previously used po scrollView.delegate
to print out the value of the instance variable for the delegate directly. You should see the forwarder.
Syntheses
Here is what you have discovered:
- The delegate forwarder object is (probably) instantiated when the web view’s scroll view is instantiated.
- This is actually a supposition. You have not yet verified this to be true. Here’s a fun challenge: think of a symbolic breakpoint you can set in LLDB to see when the
_UIWebViewScrollViewDelegateForwarder
object is allocated to prove or disprove this theory. Is there a way in Hopper Disassembler to verify?
- This is actually a supposition. You have not yet verified this to be true. Here’s a fun challenge: think of a symbolic breakpoint you can set in LLDB to see when the
- The
delegate
getter and setter inUIWebScrollView
actually aliases the delegate forwarder’s property of the same name, so when you set the scroll view’s delegate, you are really setting the forwarder’s delegate. - The forwarder object is the scroll view’s “real” delegate, stored in the _delegate ivar by calling through to super’s implementation of
setDelegate:
within the forwarder. - When you set a delegate on a
UIWebView
‘s scroll view, it sets the_UIWebViewScrollViewDelegateForwarder
proxy object as its delegate, The forwarder uses the message forwarding feature of Objective-C to send delegate callbacks always to the web view, provided it implements the selector in question, as well as to your delegate if you choose to provide one that implements the selector in question. - When a
UIScrollViewDelegate
method that returns a value is overridden in your delegate, it takes precedence over the method that is implemented on theUIWebView
itself.- This precedence is set by the order of operations in
forwardInvocation:
. The web view is called first and the delegate is called second. The method returns the last value it finds, so if your delegate returnsnil
fromviewForZoomingInScrollView:
, the_UIWebViewScrollViewDelegateForwarder
will returnnil
when the scroll view calls that delegate method.
- This precedence is set by the order of operations in
Here is an object diagram of everything you have discovered.
Conclusions
Is it safe to set the delegate of a UIWebView
‘s scroll view? It seems that it is, for the most part. Thanks to the delegate forwarder, the UIWebView
will always receive the delegate methods it needs to maintain its state, such as updating the page number of a PDF or making sure that un-renderered portions of the content inside of the view get rendered for display when the scroll view scrolls, etc.. Your UIScrollViewDelegate
object, such as the view controller in the example, will have its UIScrollViewDelegate
methods called only if you choose to implement them. The implementation of the delegate forwarder always checks first to see if the selector it may call is implemented in your delegate. Your delegate gets called right after the web view gets called, so the web view has already had a chance to react to the method before the method is called on your delegate.
Two UIScrollViewDelegate methods return a value. They are:
// return a view that will be scaled. if delegate returns nil, nothing happens
- (UIView *)viewForZoomingInScrollView:(`UIScrollView` *)scrollView;
// return a yes if you want to scroll to the top. if not defined, assumes YES
- (BOOL)scrollViewShouldScrollToTop:(`UIScrollView` *)scrollView;
Delegate methods that return a value have a hand in controlling the behavior of the scroll view, so those methods are the only ones that could trip-up the UIWebView
. Overriding either of these delegate methods in your scroll view delegate means the web view’s return values for these methods will not be used to control the scroll view.
You will need to decide if your return values could pose a problem for UIWebView
‘s implementation. You can override whether the view scrolls to the top when the user double taps above by returning NO
from scrollViewShouldScrollToTop:
. That is probably innocuous. You have already seen that you can keep the web view from zooming by returning nil
in viewForZoomingInScrollView:
without causing obvious problems. That’s probably mostly innocuous as well. You might choose to return a view to scale other than the content of the web view when the user zooms. That might cause some trouble for the web view. I would recommend against it. You should probably avoid returning anything other than the return value from the web view’s viewForZoomingInScrollView:
, which you might choose to do if you simply want to know when the view is zooming without changing behavior. If you must return a different view for zooming or nil to stop zooming, you should take care to ensure that the view you return is a subview of the scroll view and read the documentation for viewForZoomingInScrollView:
carefully. Returning a view that is not a subview of the scroll view will throw an exception and crash your app. Look at the pseudo code in Hopper for -[UIScrollView _getDelegateZoomView]
to see that this is the case. Tread lightly.
Note: You can prevent zooming in a UIWebView
by setting its scalesPageToFit
property to NO.
Apple engineers presumably spent the effort creating an object with a name like _UIWebViewScrollViewDelegateForwarder
and made it behave in this way for a particular reason. You have discovered that it is cleverly designed to allow users of the UIWebView
to receive scroll view delegate callbacks without getting in the way of the web view also receiving those calls.
Note: UIWebView
in versions of iOS previous to iOS 5 did not expose its scrollview. Those previous versions that did not expose the scrollView also did not have this delegate forwarder behavior. I could find neither _UIWebViewScrollViewDelegateForwarder
nor UIWebScrollView
in the iOS 4 headers generated by class-dump.
You may have also solved part of the mystery as to why UIWebView
should not be subclassed. Apple has not documented the internal implementation of UIWebView
. If you did not know about this delegate forwarder and decided to subclass UIWebView
, you might inadvertently do something within our subclass that can mess-up UIWebView
. Classes that are not designed to be subclassed tend to be complex and will often contain undocumented, tightly coupled implementation details that would present sharp edges to a subclass implementation, such as this delegate forwarder object being the “real” delegate of the UIScrollView
inside of the UIWebView
itself. Take a look at the number of header files that class-dump created that have “Web” in their name and look around inside some of those files. UIWebView is complicated under the hood. As much as anything else, it seems likely that Apple asks you not to subclass UIWebView
to shield you from the many sharp edges of complex, undocumented implementation details.
Why Reverse Engineer?
Learning reverse engineering techniques has proved extraordinarilly useful for augmenting my understanding of software. In this tutorial, you used LLDB, class-dump, and hopper disassembler to discover how UIWebView
works with its UIScrollView
in an effort to clarify Apple’s documentation about whether and how you can respond to the behavior the web view’s scroll view by implementing an object that conforms to UIScrollViewDelegate
. Along the way, you gained an arsenal of reverse engineering tools and techniques to dig beneath the documentation and to learn some of how Apple implements their frameworks on iOS.
Gaining deep, inside information can improve the design of your own code. Perhaps you will find a use for message forwarding in your own projects as a result of this investigation. These deep explorations can sometimes seem tedious, but teasing implementation details out of the iOS frameworks will “level-up” your game considerably on the platform. Digging deep teaches you how the pros at Apple design software. In any case, knowing your platform deeply helps immensely. Nerds cannot code on documentation alone.
Augmenting the information available in documentation by experimenting with and reverse engineering platform libraries will also help you to write more stable apps, ensuring that your use of the frameworks integrates well with actual, internal design. This type of reverse engineering does not make you a “hacker” in the typically unethical, pop-culture sense. It makes you a better programmer. Cultivating curiosity by digging deep when you write code atop a particular API produces more stable code and will leave you better equipped to respect the sometimes unwritten intentions and assumptions of the platform engineers on whose shoulders we all stand.
As is often the case when I find myself diving deep into Apple’s platforms, I came across a blog post or two by my friend ++md; //!
Mark Dalrymple while writing this tutorial. I highly recommend reading his post called “Leveling Up”. His four steps to becoming a better programmer therein summarize my own thoughts on the topic, so I’ll quote him here:
- Read – Throw a lot of facts at the brain. Some may stick. I find that kind of stuff fun to read, so it’s an enjoyable pastime.
- Dissect – Get comfortable with a wide range of tools, from high to low level, and don’t be afraid to use them. And don’t be afraid to use them in weird ways. DTrace can be a big hammer. But sometimes you can smash a bug into tiny little pieces with it. Use these tools to dig into software to see how it works. Promise you will use this knowledge for good, not evil.
- Experiment – Write code. Write a lot of code. Experiment. Play with all the new found toys. Tease out the patterns.
- Assimilate – Reflect. Lather, rinse, repeat. Then keep doing it for the next twenty years. Building deep skills is a long-term work in progress.
As my dad might say, book learning is great, but nothing can replace turnin’ screws and gettin’ a little dirty.
References
- Objective-C Runtime Programming Guide: Messaging
- Class-dump
- Homebrew Package Manager
- Hopper Disassembler
- Leveling Up