Draft Proposal for Namespaces in Objective-C

Adding namespaces to Objective-C is a non-trivial problem. This proposal is a working draft; it may have bugs. (In particular, the definition of @namespace blocks and the @using directive is incomplete, but it’s analogous enough to other languages that the intent should be obvious.) This draft will certainly need updates; I welcome comments at optshiftk [at] optshiftk [dot] com.

UPDATE September 11th, 2012: You can now find this proposal on GitHub. Further development will occur there.

UPDATE April 18th, 2012: Fixed a problem with the FwkProto example. The @implementation directive was all sorts of screwy. Thanks to Greg Parker for catching it.

Namespaces in Objective-C

Objective-C has lacked namespaces since its creation. Unfortunately because it wholly embraces its message-passing model of OOP, it is very difficult to retrofit existing other languages’ approaches to namespacing atop Objective-C without breaking binary compatibility. A naive namespacing approach might make it impossible for code that is not namespace-aware to send meaningful selectors to namespace-aware code.

This proposal introduces a new kind of selector that carries a namespace in addition to, but distinct from, its keywords. It redefines classes, categories and protocols to be members of namespaces, and provides rules for what namespaces their methods belong to.

Motivation

In practical Cocoa applications, the lack of namespaces leads to two conventions that are less than ideal:

  1. Class, category, and protocol names must begin with distinct character strings, such as NS or OF.

  2. Private and internal methods and selectors must be named in such a way that no other code in the process (and in particular in the inheritance chain) would refer to that selector unintentionally.

A common technique to achieve requirement 2 is to prefix method names with an underscore. This has led to multiple clashes between application and framework codebases and even rejection from the Mac and iOS App Stores for falsely-detected attempts to invoke private API.

Adding namespaces to the language will provide a well-known technique to naturally avoid this issue and make code more readable.

Namespaces

A namespace is a conceptual grouping of classes, categories, protocols, and selectors in which no two types of the same kind may have the same name. Two types of the same kind belonging to two different namespaces may have the same name. No two namespaces in an application may have the same name.

A namespace is named by a string consisting of valid C identifier characters and periods. There is one flat collection of namespaces; namespaces can only contain Objective-C classes, categories, protocols, and selectors. No provision is made for mapping Objective-C namespaces to C++ namespaces, even when compiling Objective-C++ code.1

The Default Namespace

There exists one namespace named default to which all classes, categories, and protocols are assumed to belong unless otherwise specified. This namespace exists primarily to support interoperating with existing code that is not namespace-aware. In particular, to remain compatible with existing code, framework classes should remain in the default namespace while their private methods should be moved to categories in a private namespace.

Qualified Identifiers

A qualified identifier names the namespace to which the object referred to by the identifier belongs. For example, default.NSObject is a qualified identifier that refers to the NSObject class (or protocol, depending on context) in the default namespace.

Namespace Blocks

The @namespace keyword begins a namespace block that exists until a balancing @end keyword:

@namespace <ns>
  .
  .
  .
@end

Namespace blocks cannot occur within any other block (in either the Objective-C @ ... @end or C “delimited by curly-braces” sense), including within any other namespace block.2

Unqualified declarations, including forward declarations, declared within a namespace block belong to the namespace named by that block. Namespaces can be reopened in the same or other translation units in any module using a @namespace block, but those symbols may not be visible to code in scopes that cannot see the actual declaration.

Namespace Scopes and the @using Directive

At any given point in a translation unit there exists a stack of namespace scopes. A namespace scope consists of a set of identifiers and namespace aliases. Each C scope and Objective-C @-block creates a correspondingly-lived namespace scope. When the compiler encounters an unqualified identifier, it looks up the stack of namespace scopes to find a matching identifier of the appropriate type.

All identifiers in the namespace named by a @namespace block are a member of the namespace scope created by that block.3

The @using directive can be used to create a new scope that contains either a single identifier or all the identifiers from a namespace:

- (void)foo;
{               // introduces a namespace scope (and a C scope)
  . . .
  @using ns1.a; // introduces a namespace scope containing the identifier a from namespace ns1
  . . .
  @using ns2;   // introduces a namespace scope containing all identifiers from namespace ns2
  . . .
}               // end of namespace (and C) scopes

The scope created by the @using directive extends from the @using directive to the end of the containing scope.

The @using directive can also create a namespace scope containing a namespace alias:

- (void)bar;
{
  . . .
  @using ns = some.long.namespace; // introduces a namespace scope that contains an alias `ns` for the namespace some.long.namespace
  . . .
}                                  // end of namespace (and C) scopes

Within a scope containing a namespace alias, references to that namespace alias are treated as if they were spelled as references to the original namespace.

A @using directive MAY NOT create an ambiguity in identifier resolution. A @using directive that would cause an unqualified identifier to refer to two different symbols is an error.

Namespaced Selectors

At the heart of this proposal is the redefinition of the selector (whose type is known as SEL in Objective-C code). The selector is currently defined as a set of keywords of the form @selector(key1:key2). Under this proposal, selectors gain an optional namespace. A new comparison between selectors is defined:

  • Selectors with namespaces are considered equal if and only if their namespaces and keywords are equal.
  • Selectors without namespaces are considered equal if and only if their keywords are equal.
  • A selector with a namespace is considered “compatible with” a selector lacking a namespace if and only if their keywords are equal.

New syntax is defined to refer to namespaced selectors: @selector(<ns>, <kw>) refers to a selector in the namespace <ns> composed of keywords <kw>. The existing @selector(<kw>) syntax looks up a selector based on the namespace scope resolution rules above. The compiler should warn if multiple matching selectors are found in the current stack of namespace scopes.

To produce a selector in the default namespace, use the spelling @selector(default, <kw>). To produce a selector that has no namespace, use the spelling @selector(nil, <kw>).4

Namespaced Classes, Categories, and Protocols

Every class, category, and protocol must belong to one and only one namespace. However, a category on a class in namespace A may be defined in namespace B; their namespaces are separate. Likewise, a class in namespace A may declare its conformance to a protocol in namespace C (potentially by a category that is declared in namespace B). Metaclasses belong to the same namespace as their corresponding class.

This makes it possible to expose publicly expose a class that implements some private methods with which external subclasses or categories cannot conflict. The private methods would be declared in a category on the public class defined in a separate namespace that is not visible to other code.

The methods declared in an @interface or @protocol block belong to the namespace of the container that declares them. In an @implementation block, the compiler must use a heuristic to determine which namespace the method being defined belongs to.

When the compiler encounters a method definition, it looks at the declarations of the entire inheritance and conformance chain for the class or category being implemented. If it finds only one method declaration whose signature matches the method definition, then it associates the definition with that declaration. This makes it possible to implement conformance to a protcol in namespace B from within a class or category defined in namespace A. If multiple declarations are found from classes or protcols in different namespaces, the compiler SHOULD produce a warning.

Method definitions that do not have a corresponding declaration visible to the compiler at the point of definition belong to the namespace of their containing @implementation block.

Method Resolution

When resolving a selector to a method, the dispatch machinery follows a certain resolution order, where S refers to the selector being dispatched and C is the class for the receiver (or a metaclass if the receiver is a class).

  1. If a matching method is found on C that belongs to the same namespace as S, that method is chosen. Only one such method can exist.5

  2. Else, if S does not have a namespace and a matching method is found on C in the default namespace, that method is chosen. Only one such method can exist.

  3. Else, if S does not have a namespace and a matching method is found on C in any other namespace, one such method is chosen. Which one is chosen is undefined.

  4. Else, the resolution fails.

When emitting a message send, the compiler encodes a selector with a namespace chosen based on the static type of the receiver and the current namespace scope. If the compiler finds a method whose keywords match those in the message send, it encodes that namespace in the selector, preferring namespaces closer in scope to the message send. If the receiver is of static type id, then no selector is encoded.

The programmer can override the compiler’s preferred namespace selection using the @namespace() keyword:

[<receiver> @namespace(<ns>) arg1:... arg2:...];

A namespace of nil instructs the compiler not to encode a namespace in the selector for the message send. This is discouraged, but can be useful when code needs to call methods in a namespace it cannot see (for example, calling private API to work around a framework or operating system bug).

As noted above, the compiler should warn if multiple methods in different namespaces with the same keywords are seen defined on the class of the receiver.

@class() Expressions

Because an arbitrary expression can be used as the receiver of an Objective-C message send, there is a potential ambiguity between a qualified class identifier ns.SomeClass and a member of an aggregate in local scope named foo.SomeClass.

New syntax is introduced to alleviate this ambiguity as well as provide a desired feature. The @class() parameterized keyword can be used as the argument to a message send as well wherever an expression of type “pointer to class” can appear. The parameter to the keyword is a (qualified or unqualified) identifier naming a class; the keyword expression has the type of a pointer to the named class.

With this feature, the ambiguity in message sends is resolved by always choosing variables ahead of namespaces if there is a conflict:

@class ns.SomeClass;

void f() {
  struct {
    Class SomeClass;
  } ns;

  [ns.SomeClass description]; // always refers to the member of struct ns
  [[ns.SomeClass class] description]; // same - refers to member of struct 
  [@class(ns.SomeClass) description]; // refers to class in namespace ns
}

This is consistent with current behavior.

Examples

The simplest way to adopt namespaces is to add explicit namespaces to identifiers.

// ExplicitNamespaces.h
#import <Foundation/NSObject.h>
@interface MyNS.MyClass : default.NSObject
- (void)publicMethod;
@end

// ExplicitNamespaces.m
#import "ExplicitNamespaces.h"
@interface MyNS.MyClass (PrivateNS.PrivateMethods)
- (void)privateMethod;
@end

@implementation MyNS.MyClass
- (void)privateMethod;
{
    // Compiler resolves this implementation to @selector(PrivateNS, privateMethod)
}

- (void)publicMethod;
{
    // Compiler resolves this implementation to @selector(MyNS, publicMethod)
}
@end

To avoid redundancy, you can use a @namespace block:

// NamespaceBlock.h
#import <Foundation/NSObject.h>
@namespace MyNS
@interface MyClass : NSObject
    // Compiler resolves NSObject to default.NSObject, because that is the closest declaration of NSObject that it sees
@end
@end

A common but complicated scenario involves using protocols from other namespaces in a private category:

// MyFramework.h
@namespace MyFramework
@protocol FwkProto
@optional
    - (void)fwkMethod;
@end
@end

// MyClass.h
#import <Foundation/NSObject.h>
#import <MyFramework/MyFramework.h>
@namespace MyNS
@interface MyClass : NSObject
- (void)publicMethod;
@end
@end

// MyClass.m
#import "MyClass.h"

@interface MyNS.MyClass (PrivateNS.PrivateMethods) <MyFramework.FwkProto>
- (void)privateHelperMethod;
@end

@implementation MyNS.MyClass
- (void)publicMethod;
{
    // @selector(MyNS, publicMethod)
}

- (void)fwkMethod;
{
    // NOTE: This resolves to @selector(MyNS, fwkMethod) !!!
    // Because the inheritance and conformance chain of _this @implementation_ does not include a class or protocol that declares a selector named fwkMethod, the compiler considers this to be a definition of an undeclared method.
    // But because the compiler can see a category on this class that conforms to a protocol that declares a selector with a matching signature (even though both the selector and the category are in namesapces different from this implementation), it SHOULD issue a warning about this confusing behavior.
}
@end

@implementation MyNS.MyClass (PrivateNS.PrivateMethods)

// Omit definition of optional -fwkMethod. If we included the definition here, the compiler would issue another warning that it could see two methods were defined on the same class with matching selectors in different namespaces.

@end

  1. It’s not the goal to make life hard for Objective-C++ developers, but it’s likewise not the goal to make Objective-C developers deal with all the intricacies of C++ namespaces, or (potentially worse) just a subset of them. 

  2. It might be worth considering relaxing this restriction to allow use of Objective-C @namespace within C++ namespace blocks, but since C++ namespaces can also be reopened there doesn’t seem to be a pressing need. 

  3. With two-pass compilation, this means that a namespace scope includes even identifiers declared after the end of the namespace scope in which they are used. With one-pass compilation, this may require appropriate placement or qualification of forward declarations. 

  4. If the definition of nil is not changed to become a true keyword (or an alias thereof) rather than a macro for a null pointer constant, this definition may likewise permit the use of any null pointer constant as the first argument to the @selector()

  5. It’s possible to produce an binary that has multiple definitions of a method in the same namespace, but the loader will have chosen one winning method. This situation already exists independent of namespacing. 

5 thoughts on “Draft Proposal for Namespaces in Objective-C

  1. I’m just curious because I couldn’t tell reading through this draft, but are you suggesting if no namespace is specified in the selector (i.e. you’re using or you’re using nil, ) the system would use the default namespace? As in, with this proposal does no namespace = default namespace automatically or does no namespace mean the namespace is nil, but there is still a default namespace that is different and explicit?

    Once again, I’m just curious. Thanks!

    • There is some subtlety at play here.

      @selector(nil, do:with:) produces a SEL that carries no namespace information. When dispatching such a selector (using -performSelector, for example, or when executing code that was compiled with an non-namespace-aware compiler), the dispatcher follows the dispatch rules. Usually that does the simple thing and resolves to a method whose body is defined in the main @implementation block of that class.

      If it can’t find a method on that class in that namespace, it looks for a matching method from the default namespace. This is to cover the other common case, where you’re sending -retain or some other similar selector that’s declared in the default namespace. The final case is a fallback case; it’s useful if, say, Apple moves all their private methods to a namespace like “com.apple.private” which they obviously won’t expose in their framework headers, but you need to call one of those methods to work around a bug.

      @namespace(nil) in a message send is a way of encoding @selector(nil, do:with:) statically during compilation of that message send.

      Omitting a namespace altogether is different. @selector(do:) (the one-argument form) looks up the namespace of the selector at compile time. If you do @selector(allocWithZone:), for example, the compiler will see that allocWithZone: is declared in the default namespace, and will emit exactly the same code as if you had typed @selector(default, allocWithZone:) yourself.

      You cannot do @namespace() (empty parens) in a message send. You just leave out the @namespace altogether.

  2. Greetings Mr, Sluder,

    This is a bit long for a comment post, but I attempted to send this feedback to the email address at the top of this post. The message was bounced back with an error stating that the e-mail address does not exist.

    Although I have many critiques of, and questions about, your proposal, I’m sending them to you in the hopes that it results in a stronger proposal, and with the full knowledge that, as you pointed out, it is a working draft with bugs. I’m not sending them to try to belittle or tear down the proposal. I share your desire for namespaces in Objective-C. Although it is not too often that I run into collisions that would be solved with namespaces, it is very frustrating when they happen, and prefixing class and method names is quite ugly.

    I also beg your patience, as some of my suggestions may be borne out of ignorance. I’m probably not as familiar with all the issues taken into account in writing the draft proposal (e.g. binary compatibility).

    In your proposal, you list two motivations. I want to first address the parts of the draft that address the first motivation, as it does not require namespaced selectors, and is thus simpler.

    The requirement that framework classes belong to the default namespace greatly reduces the usefulness of namespaces. As Objective-C is now, if one of your classes conflicts with a class in a Framework you’re linking against, it’s easy, though not ideal to rename your class. (In most situations that is. If your code allows the user to load bundles at runtime, you can’t pre-emtively guess which frameworks the user might load). However, if you are attempting to link against two frameworks which each have identical class names, the collision cannot be avoided.

    However, namespacing classes has the potential to alleviate this problem for new code, and it would be a shame to miss out on that benefit. It would be nice if frameworks could be compiled as namespece-supported, namespace-unsupported, or namespace required, much in the same way frameworks are compiled for garbage collection. If it is not possible to produce namespace-supported frameworks without causing linker errors, I still believe it would be preferable to split between namespace-required and namespace-unsupported.

    The Objective-c runtime would have expanded to include new methods anyway (e.g. a namespaced version of objc_getClass), so I’m not sure it’s possible to produce backwards-compatible frameworks by keeping classes in the default namespace.

    I’d like to voice my support for not allowing nested @namespace blocks. I don’t think they’re necessary, and I don’t think they cause a problem with C++.

    I’m a bit skeptical about @using. Granted, this may be due to the draft being an early work-in-progress, and not containing examples showing why someone would use it. It seems to be that, like in C++, it would be used primarily to prevent people from having to type the namespace repeatedly. After all, no one wants to write “com.example.namespace.ClassName* foo = [com.example.namespace.ClassName new]”

    There’s obviously been a lot of debate over the using keyword in C++. Lots of bugs have cropped up when people have accidentally introduced more symbols than intended into the namespace. This is the same reason why “from foo import *” is frowned upon in Python.

    So I would propose replacing @using with @alias. Aliases would only be allowed in implementation files, and would only work in their scope (file scope, C {} scope, or Obj-C @ scope). “@alias com.example.namespace.ClassName ClassName” would be functionally equivalent to “@using com.example.namespace.ClassName”. The two differences are that @alias would only work with fully-qualified classes (no “@alias com.example.namespace”) and would also allow renaming of classes. This allows for aliasing classes with the same name from different namespaces. e.g.

    @alias com.example.namespace.ClassName MySuperCoolClass
    @alias com.example.otherspace.ClassName MyLessCoolClass

    This has the added benefit over @using of allowing the user to use conflicting classes in different namespaces in the same scope without fully qualifying either of them.

    I was wondering if there was a way to avoid the need for @class expressions. Perhaps it would be possible to use a different separator for namespaces? I can foresee some nasty bugs coming about because some programmer forgot to add @class. Maybe something like ClassName@com.example.namespace? I’m not sure I really like that, or have a better suggestion.

    I was also wondering if any consideration in this draft proposal had been given to language bridges like PyObjC and MacRuby. On iOS, this is obviously not an issue, and like with ObjC++, I’m not sure if it’s worth inconveniencing Obj-C developers for these languages. I’ve recently heard grumblings that Apple’s support for these languages is waning anyway. I’m not an expert on these languages, but from what I can recall, their identifiers only allow for alphanumeric characters and underscores. They already encode selectors by replacing colons with underscores, and if they had to encode the periods in namespaces with underscores too, it might lead to some ambiguities.

    As to the second motivation, I’m not sure the proposal for namespaced selectors really fixes the problem. In particular, the resolution rules are problematic. For example, let’s say (not that this is a good idea), that I’ve implemented a category on NSString with a +randomString method. If I’m writing a library, I don’t want to conflict with any other +randomString method that the user might load. I also don’t want any code outside the library calling my +randomString method.

    There are no problems if both libraries explicitly provide a namespace when calling +randomString (rule 1).

    If one or both libraries do not provide an explicit namespace, relying on rule 3 to resolve it correctly, can create a problem where rule 3 chooses the wrong selector. As the author of my library, I may never see this, as I may never use another library with a +randomString method. However, my users may run into this problem. The author of the other library put +randomString in their namespace to prevent outsiders from calling it, but here I am calling it.

    Even worse, if they load a library with a +randomString method in the default namespace, rule 2 will come into effect, and I’m guaranteed to call the incorrect method.

    Either of these rules would be particularly disastrous if methods in two different namespaces have the same selector, but different argument types. There are instances where the compiler would be able to catch this, but others (e.g. iterating over a heterogenous container) where it would not.

    So I would propose the following rules instead:

    1. If a selector has a namespace explicitly provided using the “[ @namespace() arg1:... arg2:...];” syntax, the selector is called in that namespace. Only one such method can exist per namespace-selector pair.
    2. Else, if a selector does not have a namespace explicitly provided, it is called in the default namespace. Only one such method can exist.
    3. Else, the resolution fails.

    These rules still allow me to define private namespaced methods which outsiders should not call, and prevents outsiders from accidentally calling them. I’m not entirely sure, but this may also allow the nil namespace to be removed. As already pointed out in the comments, there is a subtle difference between the default namespace and the nil namespace, and I could see there being some confusion about that. @selector(someMethod:) without a namespace would fall back to the default namespace.

    I’m sorry for the text dump. I hope that my comments were helpful, and that your lighting talk at Lang.NEXT went well. If you move forward with the proposal, you may find it advantageous to take the discussion to the clang development community, many of whom are Apple employees, as it seems that clang is driving the recent improvements to Objective-C.

  3. I have thought about this a bit. In a similar vein I have been thinking a simpler feature to add that would solve the accidental overriding of private methods would be to make private methods a part of the language. My idea is that private methods would internally have the selector ., this would be easy for the compiler to resolve since it needs to only deal with messages to self, for dynamic stuff, (methods from strings or @selector()) developers could just spell out the whole selector name. Another big advantage of this is the potential for the compiler to apply c function calling type optimisations to calling private methods from self. Namespace in general I don’t think at that desirable for Objective-C considering the amount of changes to how the language would have to work.

  4. Pingback: Objective-C 的命名空间 | 小虎的开发笔记

Comments are closed.