Can the new Clang Objective-C literals be redirected to custom classes?
Asked Answered
E

2

19

Although the overloading of @ begins to tread on dangerous territory, I love the addition of the new Objective-C literals in Clang 3.1. Unfortunately the new literals are of limited use to me. Except for instances where code needs to interface with AppKit, I've mostly dropped the use of Foundation classes in favor of my own custom framework (for a variety of reasons; most of which is that I need direct control over the memory allocation patterns used by objects).

I could always use some runtime trickery to pass off the newly created object as my custom class (and is what I already have to do with string object literals, since only the non-Apple GCC runtime supports the -fconstantstring=class flag), but this is a hack at best and throws out all the benefits I gained by replacing the equivalent Foundation class to begin with.

Unlike string object literals, the new literals Clang implements are not actually constant classes (where the memory layout is hardcoded); instead the appropriate messages are sent to their respective classes to create and initialize a new object at runtime. The effect is no different than if you had created the object yourself. In theory it means that the classes used and the methods called by the new literals are not hardcoded. In practice I can't find any way to change them to point to my own custom classes and methods (I would in fact be happy just to point to a custom class; pointing a dummy method to an actual method at runtime isn't difficult).

When I first looked into this, I was really hoping to find a set of flags that could be used to do what I'm asking, but as I haven't found any, I'm hoping someone has a solution.

Edee answered 28/6, 2012 at 20:28 Comment(7)
It's possible to hook into the new syntax: mikeash.com/pyblog/…, but making the compiler create instances of your own classes will probably require hacking on your compiler: aussiebloke.blogspot.com/2012/06/llvm-clang-hacking-part-3.htmlKendrick
Rather than resorting to compiler hackery, it might be easier for now to use a macro: e.g. expand MO(@{@"foo":@1}) to [MyObject myObjectWithDictionary: @{@"foo":@1} ]. It's a little ugly and autoreleases a temporary object, but it's still more concise (and safer) than the old syntax.Extempore
@Extempore This may be what I end up doing; not ideal but not a terrible solution. Although it would be a better solution if the preprocessor could handle a more extensive set of characters. Then it would be possible to use @I(45), @U(16) or something else that would equally stand out. Instead I'll likely have to name the macros so it's obvious they're meant to be literal replacements such as LitInt(45) or LitUInt(16).Edee
Not sure if this will completely answer your question, but Mike Ash has written really good post outlining Objective-C literals. This may help answer your questions. mikeash.com/pyblog/… Cheers!Magnetometer
That's why hard-coding API/function/class names into a language is terrible practice. Epic conceptual, design and engineering fail for Apple/NeXT/whoever designed Objective-C.Lyte
@H2CO3 The problem is, only string literals have ever been hard-coded into ObjC, and that was to deal with limitations at the time it was created. The new hard-coded literals are thanks to a push from Apple, and I wholeheartedly agree that it's a terrible practice and leading my favorite language into dark territory. I really wish Apple would stop trying to insulate the dev pipeline; they should try to make ObjC and their frameworks more open to promote cross-platform development. It would bring in a lot of new blood if devs knew they could use ObjC and Cocoa, and support all PC platforms.Edee
@Edee Absolutely agreed. Pushing it further: homebrew iOS development on Windows/Linux.Lyte
L
12

You can substitute class for some Objective-C literals with @compatibility_alias keyword trick.

Here's an example.

@compatibility_alias NSNumber AAA;

Of course, you should provide proper implementation for new class.

#import <Foundation/NSObject.h>

@interface  AAA : NSObject
+ (id)numberWithInt:(int)num;
@end

@implementation AAA
+ (id)numberWithInt:(int)num
{
    return  @"AAAAA!!!";    // Abused type system just to check result.
}
@end

@compatibility_alias NSNumber AAA;

Now Clang will do the job for you. I confirmed this is working for number, array, dictionary literals. Unfortunately string literals seem to be emitted statically, so it won't work.

For more information about @compatibility_alias keyword, see here.

Note

Because @compatibility_alias keyword is a compiler directive which applies to current compilation unit, you need to separate compilation unit to avoid symbol duplication with NSObject class in Apple's Foundation Kit. Here's how I did it.

main.m

#import "test.h" // Comes before Foundation Kit.
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        NSLog(@"return of test = %@", test());
        // insert code here...
        NSLog(@"Hello, World!");

    }
    return 0;
}

test.h

id test();

test.m

#import "test.h"
#import <Foundation/NSObject.h>

@interface  
AAA : NSObject
+ (id)numberWithInt:(int)v;
+ (id)arrayWithObjects:(id*)pobj count:(int)c;
+ (id)dictionaryWithObjects:(id*)pvals forKeys:(id*)pkeys count:(int)c;
@end
@implementation AAA
+ (id)numberWithInt:(int)v
{
    return  @"AAAAA as number!!!";
}
+ (id)arrayWithObjects:(id*)pobj count:(int)c
{
    return  @"AAAAA as array!!!";
}
+ (id)dictionaryWithObjects:(id*)pvals forKeys:(id*)pkeys count:(int)c
{
    return  @"AAAAA as dictionary!!!";
}
@end



@compatibility_alias NSDictionary AAA;
@compatibility_alias NSArray AAA;
@compatibility_alias NSNumber AAA;



id test()
{
//  return  @{};
//  return  @[];
    return  @55;
}

Result.

2013-03-23 08:54:42.793 return of test = AAAAA!!!
2013-03-23 08:54:42.796 Hello, World!
Lessielessing answered 23/3, 2013 at 0:8 Comment(1)
This is quite a bit of a hack, but I think it's the closest anyone can get without directly modifying the compiler.Edee
S
11

The comments have it all correct, but just to summarize:

  • No.

The meanings of Apple's @{}, @[], and @"" literals are hard-coded into Clang. You can see it here: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/NSAPI.cpp?view=markup It's all fairly modular, meaning that it wouldn't be hard for a Clang hacker to add her own literal syntax... but "modular" doesn't mean "accessible from the outside". Adding a new syntax or even redirecting the existing syntax to new classes would definitely require rebuilding Clang yourself.

Here's a blog post about adding NSURL literals to Clang by hacking on its internals: http://www.stuartcarnie.com/2012/06/llvm-clang-hacking-part-3.html (Thanks @Josh Caswell)


If you're willing to use Objective-C++ with C++11 extensions, you can has "user-defined literals", which allow you to write things like

NSURL *operator ""URL (const char *s) { return [NSURL URLWithString: @(s)]; }

int main() {
    ...
    NSURL *myurl = "ftp://foo"URL;
    ...
}

This was mentioned in the comments on Mike Ash's blog. http://www.mikeash.com/pyblog/friday-qa-2012-06-22-objective-c-literals.html But this doesn't look very Objective-C-ish (or very C++ish!), and it works only with an Objective-C++11 compiler, and in general please don't do this. :)

Straley answered 24/8, 2012 at 22:17 Comment(2)
As you're the only one to actually submit as an answer, I'm going to accept yours.Edee
Hooray! :) I've taken to trolling through the "unanswered questions" list to see where I can help, and I find that most of the "unanswered" questions on StackOverflow have actually been answered satisfactorily, just in comments rather than real answers. This was one of those cases. :)Straley

© 2022 - 2024 — McMap. All rights reserved.