Objective-C Runtime
Cocoa’s Jewel in the Crown
NSConference 2011
Nicolas Seriot@nst021
[isa kindOf:magic]
1. Objective-C2. Recipes3. Introspection4. Debugging
Objective-C Runtime
http://opensource.apple.com/source/objc4/
/usr/lib/libobjc.A.dylib /usr/include/objc/
OO, Smalltalk-like, dynamic extensions over C
Instances and Classestypedef struct objc_object { Class isa;} *id;
typedef struct objc_class *Class;
int i 0
NSString *s 0x0
instance of B
NSString *s
Class B : A
int i
Class A
(...)
(...)
BOOL bBOOL NO
superclass
Class isa 0xA
Runtime Structure
setWithCapacity: 0x_
isa
NSMutableSet (meta)
addObject: 0x_
removeObject: 0x_
isa
NSMutableSet
count 0x_
allObjects 0x_
isa
NSSet
init 0x_
dealloc 0x_
isa
NSObject
set 0x_
setWithArray: 0x_
isa
NSSet (meta)
alloc 0x_
isa
NSObject (meta)
Class isa 0xA
myMutableSet
Message Resolution
objc_msgSend(myObject, @selector(setValue:), 3.0);
[myObject setValue:3.0];
bracket syntax is converted into objc_msgSend
it works thanks to meta-classes…
setValue_(myObject, @selector(setValue:), 3.0);
objc_msgSend looksup the functions pointer
IMP setValue_ = class_getMethodImplementation( [myObject class], @selector(setValue:));
[NSMutableSet set]
setWithCapacity: 0x_
isa
NSMutableSet (meta)
addObject: 0x_
removeObject: 0x_
isa
NSMutableSet
count 0x_
allObjects 0x_
isa
NSSet
init 0x_
dealloc 0x_
isa
NSObject
set 0x_
setWithArray: 0x_
isa
NSSet (meta)
alloc 0x_
isa
NSObject (meta)
[myMutableSet count]
setWithCapacity: 0x_
isa
NSMutableSet (meta)
addObject: 0x_
removeObject: 0x_
isa
NSMutableSet
count 0x_
allObjects 0x_
isa
NSSet
init 0x_
dealloc 0x_
isa
NSObject
set 0x_
setWithArray: 0x_
isa
NSSet (meta)
alloc 0x_
isa
NSObject (meta)
Class isa 0xA
myMutableSet
Runtime Benefits• OO capabilities
• inheritance
• polymorphism
• Cocoa magic
• key-value coding
• plugins and frameworks
• UI with Interface Builder
• responder chain
• database faulting
• undo managerbold
underline
bigger
1. Objective-C2. Recipes3. Introspection4. Debugging
Key-Value Coding
-valueForKey: looks for methods, then iVars
- (void)setValue:(id)value forUndefinedKey:(NSString *)key { NSLog(@"-- unhandled key:%@", key);}
{ firstName = John; lastName = Doe;}
Person
firstName
[person setValuesForKeysWithDictionary:d];
Categories
A
m1
m2
A
m1
m2
m3
@implementation A (A+Ext) - (id)m2 { /**/ } - (id)m3 { /**/ }@end
Method Swizzling
[a m] -> 0xB()[a m2] -> 0xA()
[a m] -> 0xA()[a m2] -> 0xB()
// Google for Mike Ash implementation Swizzle([A class], @selector(m), @selector(m2));
A
m 0xA
A+Ext
m2 0xB
Logging NSURL Creationstatic IMP original_initWithString_;
@implementation NSURL (Ext)
+ (void)swizzleMethods { // call this once, early original_initWithString_ = method_getImplementation( class_getInstanceMethod([self class], @selector(initWithString:)));
Swizzle([self class],! ! ! @selector(initWithString:), @selector(my_initWithString:));}
// -[NSURL urlWithString:] will now execute this method- (NSURL *)my_initWithString:(NSString *)s {! NSLog(@"-- my_initWithString: %@", s);
! return original_initWithString_(self, @selector(initWithString:), s);}
@end
High Order Functions$ python
>>> l = ['a', 'bb', 'ccc']
>>> filter(lambda x:len(x) > 1, l)['bb', 'ccc']
>>> map(lambda x:'-'+x, l)['-a', '-bb', '-ccc']
>>> reduce(lambda x,y:x+y, l)'abbccc'
Filtering NSArrayNSArray *a = [NSArray arrayWithObjects:@"a", @"bb", @"ccc", nil];NSArray *b = [NSArray arrayWithObjects:@"a", nil];
// 1. with plain C functionsb = filter(a, startsWithA);
// 2. with NSInvocation https://github.com/nst/nsarray-functionalb = [a filterUsingSelector:@selector(hasPrefix:), @"a", nil];
// 3. with Objective-C blocks, since Mac OS X 10.6 and iOS 4b = [a filteredArrayUsingPredicate:! [NSPredicate predicateWithBlock:! ^BOOL(id s, NSDictionary *bindings) {! ! return [s hasPrefix:@"a"];! }]]);
// 4. with high order messaging// http://www.metaobject.com/papers/Higher_Order_Messaging_OOPSLA_2005.pdfb = [[a filter] hasPrefix:@"a"];
High Order Messaging (1)[[a collect] uppercaseString];
[a valueForKey:@"uppercaseString"];
("A", "BB", "CCC")
a proxy runtimecollect
uppercaseString
forwardInvocation:collect:
("a", "bb", "ccc")
("A", "BB", "CCC")
High Order Messaging (2)
users.collect.nameusers.do.logoutusers.sortedBy.age
users.any.is.adminusers.all.are.admin
Maybe one day we’ll write Objective-C like this:
1. Objective-C2. Recipes3. Introspection4. Debugging
Runtime Introspection// NSObject.h- (Class)class;- (id)performSelector:(SEL)aSelector;- (BOOL)isKindOfClass:(Class)aClass;- (BOOL)respondsToSelector:(SEL)aSelector;
// NSObjCRuntime.hNSClassFromString(@"NSArray");NSStringFromClass([NSArray class]);NSSelectorFromString(@"objetAtIndex:");NSStringFromSelector(@selector(objectAtIndex:));
// runtime.hMethod class_getInstanceMethod(Class cls, SEL name);const char *ivar_getTypeEncoding(Ivar v);IMP method_getImplementation(Method m);const char *property_getName(objc_property_t property);
Runtime Browser
• Think class-dump, but dynamic!
• First version by Ezra Epstein, 2002
• A development tool I found useful
https://github.com/nst/RuntimeBrowser
embeddedweb server
Browsing the Runtime$ grep "hack" * -Ri
// no, you are not alone...
void *_odiousHashHackStorage;BOOL _HACKpreviouslyHitPuck;BOOL _HACKpreviouslyHitKnob;BOOL _unused_ical_hack_[32];
-[NSWindow _evilHackToClearlastLeftHitInWindow];
-[UIDocumentInteractionController updatePopoverContentSizeForPresentationOfTableViewHack];
-[PLStackView _validateTableViewLayerAsAHackForRadar8952327];
Private APIs• (un)safe on the App Store, see @0xced blitz talk
• but as a dev you can write your own app anyway!
// in UIStatusBarServerThreadstruct { ! // ...! NSInteger gsmSignalStrengthRaw; ! // ...! NSUInteger dataNetworkType;! // ...} _statusBarData;
Mapping the Network
https://github.com/nst/MobileSignal
Scripting Cocoa$ python>>> from AppKit import NSSpeechSynthesizer>>> NSSpeechSynthesizer.availableVoices()( "com.apple.speech.synthesis.voice.Agnes", "com.apple.speech.synthesis.voice.Albert", "com.apple.speech.synthesis.voice.Alex", ...)
1. Objective-C2. Recipes3. Introspection4. Debugging
$ export OBJC_HELP=YES
$ /Applications/TextEdit.app/Contents/MacOS/TextEdit (...)OBJC_HELP: describe available environment variablesOBJC_PRINT_IMAGES: log image and library names as they are loadedOBJC_PRINT_LOAD_METHODS: log calls to class and category +load methodsOBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methodsOBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:(...)
$ export NSObjCMessageLoggingEnabled=YES
$ /Applications/TextEdit.app/Contents/MacOS/TextEdit
$ tail -f /tmp/msgSends-<pid> - NSLock NSLock lock+ NSThread NSThread currentThread- NSThread NSObject hash- NSCFArray NSCFArray countByEnumeratingWithState:objects:count:- NSLock NSLock unlock- NSLock NSLock lock+ NSThread NSThread currentThread- NSThread NSObject hash- NSCFArray NSCFArray countByEnumeratingWithState:objects:count:- NSLock NSLock unlock
DTrace$ cat objc_calls.d pid$target::*ClassDisplay*:entry {}pid$target::*ClassDisplay*:return {}
$ sudo dtrace -s objc_calls.d -F -c ./RuntimeBrowser dtrace: script 'objc_calls.d' matched 64 probesCPU FUNCTION 0 -> +[ClassDisplay classDisplayWithClass:] 0 -> -[ClassDisplay setRepresentedClass:] 0 <- -[ClassDisplay setRepresentedClass:] 0 <- +[ClassDisplay classDisplayWithClass:] 0 -> -[ClassDisplay setDisplayPropertiesDefaultValues:] 0 <- -[ClassDisplay setDisplayPropertiesDefaultValues:] 0 -> -[ClassDisplay header] 0 -> -[ClassDisplay setRefdClasses:] 0 <- -[ClassDisplay setRefdClasses:] (...)
gdb Cheat Sheet(gdb) break setPassword:(gdb) break -[Person setPassword:]
(gdb) po self<Person: 0x10010c7a0>(gdb) po [invocation debugDescription]Some day, NSInvocation will have a useful debug(gdb) p (char *)_cmd$1 = 0x10fbb "setPassword:"
(gdb) frame(gdb) info locals(gdb) info (classes|selectors) [regex](gdb) list +[Person string](gdb) call [self setPassword:@"welcome"]
NSAutoreleasePool#include <Foundation/NSDebug.h>
[NSAutoreleasePool showPools];
- -- ---- -------- Autorelease Pools -------- ---- -- -==== top of stack ================ 0x583f3c0 (__NSDate) 0x583f350 (Measure) 0x5a24960 (UIWindow) (...)==== top of pool, 23 objects ==================== top of pool, 0 objects ==================== top of pool, 0 objects ================- -- ---- -------- ----------------- -------- ---- -- -
Useful Methods – UIKit#ifdef DEBUG// strengthen your code[NSTimer scheduledTimerWithTimeInterval:1.0 target:application selector:@selector(_performMemoryWarning) userInfo:nil repeats:YES];#endif
(gdb) po [UIView recursiveDescription]
<UIView: 0x4b4d3d0; frame = (0 20; 320 460); (...) | <MyView: 0x4b4b800; frame = (20 20; 237 243); (...) | | <UIRoundedRectButton: 0x4b4e790; (...) | | | <UIButtonLabel: 0x4b4f190; (...)
Conclusion
✓ Objective-C Runtime is not magic,it is just simple and clever
✓ Understanding Objective-C Runtime improves your code and debugging skills
✓ Objective-C is dynamic,it makes it powerful and fun!
[session release];