objective-c runtime - seriot

44
Objective-C Runtime Cocoa’s Jewel in the Crown NSConference 2011 Nicolas Seriot @nst021

Upload: others

Post on 21-Jan-2022

6 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Objective-C Runtime - Seriot

Objective-C Runtime

Cocoa’s Jewel in the Crown

NSConference 2011

Nicolas Seriot@nst021

Page 2: Objective-C Runtime - Seriot

[isa kindOf:magic]

Page 3: Objective-C Runtime - Seriot

1. Objective-C2. Recipes3. Introspection4. Debugging

Page 4: Objective-C Runtime - Seriot

Objective-C Runtime

http://opensource.apple.com/source/objc4/

/usr/lib/libobjc.A.dylib /usr/include/objc/

OO, Smalltalk-like, dynamic extensions over C

Page 5: Objective-C Runtime - Seriot

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

Page 6: Objective-C Runtime - Seriot

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

Page 7: Objective-C Runtime - Seriot

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:));

Page 8: Objective-C Runtime - Seriot

[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)

Page 9: Objective-C Runtime - Seriot

[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

Page 10: Objective-C Runtime - Seriot

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

Page 11: Objective-C Runtime - Seriot

1. Objective-C2. Recipes3. Introspection4. Debugging

Page 12: Objective-C Runtime - Seriot

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];

Page 13: Objective-C Runtime - Seriot

Categories

A

m1

m2

A

m1

m2

m3

@implementation A (A+Ext) - (id)m2 { /**/ } - (id)m3 { /**/ }@end

Page 14: Objective-C Runtime - Seriot

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

Page 15: Objective-C Runtime - Seriot

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

Page 16: Objective-C Runtime - Seriot

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'

Page 17: Objective-C Runtime - Seriot

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"];

Page 18: Objective-C Runtime - Seriot

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")

Page 19: Objective-C Runtime - Seriot

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:

Page 20: Objective-C Runtime - Seriot

1. Objective-C2. Recipes3. Introspection4. Debugging

Page 21: Objective-C Runtime - Seriot

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);

Page 22: Objective-C Runtime - Seriot
Page 23: Objective-C Runtime - Seriot
Page 24: Objective-C Runtime - Seriot
Page 25: Objective-C Runtime - Seriot
Page 26: Objective-C Runtime - Seriot
Page 27: Objective-C Runtime - Seriot

Runtime Browser

• Think class-dump, but dynamic!

• First version by Ezra Epstein, 2002

• A development tool I found useful

https://github.com/nst/RuntimeBrowser

Page 28: Objective-C Runtime - Seriot
Page 29: Objective-C Runtime - Seriot
Page 30: Objective-C Runtime - Seriot

embeddedweb server

Page 31: Objective-C Runtime - Seriot
Page 32: Objective-C Runtime - Seriot

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];

Page 33: Objective-C Runtime - Seriot

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;

Page 34: Objective-C Runtime - Seriot

Mapping the Network

https://github.com/nst/MobileSignal

Page 35: Objective-C Runtime - Seriot

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", ...)

Page 36: Objective-C Runtime - Seriot

1. Objective-C2. Recipes3. Introspection4. Debugging

Page 37: Objective-C Runtime - Seriot

$ 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:(...)

Page 38: Objective-C Runtime - Seriot

$ 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

Page 39: Objective-C Runtime - Seriot

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:] (...)

Page 40: Objective-C Runtime - Seriot

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"]

Page 41: Objective-C Runtime - Seriot

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 ================- -- ---- -------- ----------------- -------- ---- -- -

Page 42: Objective-C Runtime - Seriot

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; (...)

Page 43: Objective-C Runtime - Seriot

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!

Page 44: Objective-C Runtime - Seriot

[session release];