Search code examples
objective-cswiftcocoacore-datansmanagedobject

Swift: @objc(...) Attribute


In Apple-generated code (Core Data NSManagedObject subclasses, for example) I see this:

@objc(LPFile)
public class LPFile: NSManagedObject {
   ...
}

My question is: why is the @objc declaration done as above, instead of:

@objc public class LPFile: NSManagedObject {
   ...
}

or

@objcMembers public class LPFile: NSManagedObject {
   ...
}

What is special about the separate @objc(identifier) declaration? I can't seem to find documentation about it and googling just turns up the other two approaches. Thanks.

(NB: I'm aware class prefixes are not idiomatic Swift.)


Solution

  • @Alladinian is right. Suppose you have a framework SharedSwift with two classes:

    @objc public class Foo: NSObject {}
    @objc(Bar) public class Bar: NSObject {}
    

    You can import this framework in the Objective-C code and use these two classes directly:

    @import SharedSwift;
    
    Bar *b = [[Bar alloc] init];
    Foo *f = [[Foo alloc] init];
    

    But because Objective-C has a powerful runtime, you can do a lot of magic. One example is the NSClassFromString function:

    - (id _Nullable)instanceByName:(NSString * _Nonnull)name {
        Class c = NSClassFromString(name);
        return [[c alloc] init];
    }
    
    Foo *foo = [self instanceByName:@"Foo"];
    Bar *bar = [self instanceByName:@"Bar"];
        
    NSLog(@"%@ %@", foo, bar);
    

    And the output is:

    (null) <Bar: 0x6000015c4200>
    

    What's the problem? className ...

    NSLog(@"%@ %@", [Foo className], [Bar className]);
    

    ... returns SharedSwift.Foo in one case (@objc) and Bar in another one (@objc(Bar)).

    SharedSwift.Foo Bar
    

    Let's add AnotherSwift framework to the mix with same classes and try to use Foo from both:

    @import SharedSwift;
    
    NSLog(@"%@", [Foo className]); // SharedSwift.Foo
    
    @import AnotherSwift;
    
    NSLog(@"%@", [Foo className]); // AnotherSwift.Foo
    

    Works as expected. Try the same thing with the Bar class:

    @import SharedSwift;
    
    NSLog(@"%@", [Bar className]); // Bar
    
    @import AnotherSwift;
    
    NSLog(@"%@", [Bar className]); // Bar
    

    Bar class is defined in both frameworks and which one will be used is undefined. See the error in the console when you try this:

    Class Bar is implemented in both
    .../Debug/SharedSwift.framework/Versions/A/SharedSwift (0x102b931c0) and
    .../Debug/AnotherSwift.framework/Versions/A/AnotherSwift (0x102b841c0).
    One of the two will be used. Which one is undefined.
    

    What's the reason for this?

    As you can see, there's a difference between Objective-C code (@import SharedSwift & direct usage of Foo) & Objective-C runtime name (NSClassFromString, ...).

    There's one namespace for everything in the Objective-C world. This is the reason for these two letters prefixes in the Apple frameworks (NS, UI, CF, ...) and three letters prefixes in the 3rd party code. Some 3rd party developers still do use two letters, but that's another story.

    Swift has more namespaces - they're based on modules. It's a safe bet to include module name when the pure @objc attribute is used. To avoid possible ambiguity.

    Check the NSEntityDescription class for example - managedObjectClassName property:

    The name of the class that represents the receiver’s entity.

    There's a lot of stuff around which leverages Objective-C runtime features, lot of stuff is based on just names (strings), ...