Ruby-like nil messaging in Objective-C
来源:互联网 发布:生猪体重精确计算法 编辑:程序博客网 时间:2024/05/22 06:08
http://ddeville.me/2013/06/ruby-like-nil-messaging-in-objective-c/?utm_source=iOS+Dev+Weekly&utm_campaign=iOS_Dev_Weekly_Issue_101&utm_medium=email
Ruby-like nil messaging in Objective-C
Nil messaging in Objective-C has a fairly well document behavior. In brief, messaging nilhas not effect and the return value from a message sent to nil is discussed in theProgramming with Objective-C
If you expect a return value from a message sent to nil, the return value will be nil for object return types, 0 for numeric types, and NO for BOOL types.
This is a very handy feature of Objective-C that might seem at first unfamiliar to Ruby programmers. In fact, invoking a method on nil in Ruby gives a NoMethodError as following:
NoMethodError: undefined method `length' for nil:NilClass
It’s a very different behavior from Objective-C that many Ruby programmers have come to rely on. I myself prefer the way nil is handled in Objective-C but let’s assume for a second that we wanted to achieve the Ruby behavior in Objective-C.
Needless to say that it is probably a very bad idea but since it could be fun, let’s have a look at the ObjC runtime and give it a try!
We first need to find which part of the runtime is in charge of handling messages to nil. As described very nicely in this post, a message to nil is handled directly in objc_msgSend.
Given that the ObjC runtime is open-source, we can have a look at the objc_msgSendimplementation for x86_64. It is assembly but, trust me, it is fairly readable.
Our first step is to find the entry point of objc_msgSend that looks as following:
ENTRY _objc_msgSendDW_START _objc_msgSendNilTest NORMALGetIsaFast NORMAL // r11 = self->isaCacheLookup NORMAL, _objc_msgSend // r11=method, eq set (nonstret fwd)jmp *method_imp(%r11) // goto *imp...
We can quickly notice that one of the first instruction is a NilTest macro that itself looks as following:
.macro NilTest.if $0 != STRET testq %a1, %a1.else testq %a2, %a2.endif jz LNilTestSlow_fLNilTestDone:.endmacro.macro NilTestSupport .align 3LNilTestSlow:.if $0 != STRET movq __objc_nilReceiver(%rip), %a1 testq %a1, %a1 // if (receiver != nil).else movq __objc_nilReceiver(%rip), %a2 testq %a2, %a2 // if (receiver != nil).endif jne LNilTestDone_b // send to new receiver.if $0 == FPRET fldz.elseif $0 == FP2RET fldz fldz.endif.if $0 != STRET xorl %eax, %eax xorl %edx, %edx xorps %xmm0, %xmm0 xorps %xmm1, %xmm1.endif ret.endmacro
After a few tests for the return type (struct and floating-point need special handling), we can notice that a nil receiver, if set (not nil) is given a chance to act as the message receiver. Otherwise, a few registers usually holding return values are cleant and the function returns.
The __objc_nilReceiver is not usually set but if we found a way to set it to an object that we create we could alter the behavior of nil messaging!
Luckily, objc-private.h declares the following function:
extern id _objc_setNilReceiver(id newNilReceiver);
That’s it, if we call this function with our custom object we will able to intercept any message to nil.
Our NilCatcher class will only need to implement two methodsmethodSignatureForSelector: and forwardInvocation:. Since our implementation offorwardInvocation: won’t actually need a valid NSMethodSignature we will return the method signature of a basic method on NSObject in methodSignatureForSelector:. Eventually, we will throw an exception in forwardInvocation:, logging the selector.
Instead of addign a new class in our project we will create the class at runtime and provide a couple of method implementations with blocks, just because it quicker and more fun. The code is shown below but also available as a gist which should be slightly easier to read.
#import <Foundation/Foundation.h>#import <objc/runtime.h> extern id _objc_setNilReceiver(id newNilReceiver); static id _createNilCatcherObject(void){ Class NilCatcher = objc_allocateClassPair([NSObject class], "NilCatcher", 0); NSMethodSignature * (^methodSignatureForSelectorBlock)(id, SEL) = ^ NSMethodSignature * (id _block, SEL selector) { /* We will not actually use the method signature in forwardInvocation so any signature will do it. */ return [NSObject instanceMethodSignatureForSelector:@selector(description)]; }; IMP methodSignatureForSelectorIMP = imp_implementationWithBlock(methodSignatureForSelectorBlock); Method methodSignatureForSelectorMethod = class_getClassMethod([NSObject class], @selector(methodSignatureForSelector:)); class_addMethod(NilCatcher, @selector(methodSignatureForSelector:), methodSignatureForSelectorIMP, method_getTypeEncoding(methodSignatureForSelectorMethod)); void (^forwardInvocationBlock)(id, NSInvocation *) = ^ void (id _block, NSInvocation * invocation) { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"Attempting to message %s to nil", sel_getName([invocation selector])] userInfo:nil]; }; IMP forwardInvocationIMP = imp_implementationWithBlock(forwardInvocationBlock); Method forwardInvocationMethod = class_getClassMethod([NSObject class], @selector(forwardInvocation:)); class_addMethod(NilCatcher, @selector(forwardInvocation:), forwardInvocationIMP, method_getTypeEncoding(forwardInvocationMethod)); return [NilCatcher new];} int main(int argc, const char **argv){ @autoreleasepool { id nilCatcher = _createNilCatcherObject(); _objc_setNilReceiver(nilCatcher); [(id)nil isEqualToString:@"Cat"]; } return 0;}
And that’s it! If you build and run you should crash on anNSInternalInconsistencyException when attempting to messaging nil, which should make any Rubyist feel at home! ;)
I cannot stress enough on the fact that you should probably never even think of using this. The Cocoa frameworks surely rely heavily on nil messaging being allowed and having no effect.
That said, it was a fun experiment and I hope you learnt something new.
- Ruby-like nil messaging in Objective-C
- nil、Nil、NULL、NSNull、[NSNull null] in Objective-C
- nil、Nil、NULL、NSNull、[NSNull null] in Objective-C
- nil、Nil、NULL、NSNull、[NSNull null] in Objective-C
- Objective-C object messaging
- Objective-C Runtime Messaging
- Objective-C Messaging
- objective c中的nil,Nil
- Objective-c nil & 初始化
- Objective-c - nil, Nil, NULL和NSNull
- objective-c nil Nil NULL NSNULL总结
- objective-c -- nil / Nil / NULL / NSNull
- Objective-C中nil用法
- [Objective-C] NULL and nil
- Objective-c 中 nil, Nil, NULL和NSNull的区别
- Objective-c 中 nil, Nil, NULL和NSNull的区别
- 区分nil Nil NULL和NSNill(Objective C语言)
- Objective C中NULL、Nil、nil、NSNull 的区别
- 算法-----劳斯-赫尔维茨(Routh-Hurwitz)稳定判据(转)
- Pirated Apps in the App Store
- Microsoft SQL Server Error 5030
- bootlodaer简介
- 算法-----龙格-库塔法(转)
- Ruby-like nil messaging in Objective-C
- iphone 禁止系统屏保锁屏
- 注册DLL时出现错误0x80004005
- PostgreSQL数据库远程连接功能的开启
- Android编译、反编译工具总结
- hdu 2121 Ice_cream’s world II(不定根的最小树形图)
- SMSquashView
- 39台阶 振兴中华
- Core Data浅谈系列之三 : 了解NSManagedObject和NSPredicate