Swift: #objc(...) Attribute - objective-c

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

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

Related

Determine class of an object from id<protocol>

I have a class like so:
#interface Foo : NSObject <FooDataProvider>
...
#end
and somewhere along the line in another class I have a method with the interface:
-(void) doStuff:(id<FooDataProvider>)fooProvider{
...
}
And yet somwhere else I have
-(void) passMeClasses:(Class)theClass
{
<do stuff based on the class>
}
Usually I pass things to this method simply like
Foo* f = [[Foo alloc] init];
...
[bar passMeClasses:[f class]];
But Im not sure how to pass things I only have the id.. like so
id<FooDataProvider> f = [[Foo alloc] init];
....
[bar passMeClasses:[f ?????]];
or alternatively how to do it from the doStuff method
-(void) doStuff:(id<FooDataProvider>)fooProvider{
Class c = [fooProvider howDoIGetMyClass];
bar passMeClasses:c];
}
Can someone help me in determining the class from the id?
Sorry for the long winded explanation, but hopefully its clear!
[f class]
class is a method. It's called on the object. The object will return its class (or it should; it can technically return something else and sometimes does). The type of the variable is completely irrelevant at runtime. At runtime all object pointers are id. That's why method signature type encodings only designate "an object goes here." (See #.) They can't express the specific type of the object.

In Objective-C, can we have a generic or base class reference to call different class methods?

In Objective-C, can we have a class Foo and a class Bar, and then do something like
Class aClass = [Foo class];
[aClass getURLString];
where getURLString is a class method of the class Foo or Bar. So in other words, invoke different class methods based on the class, and this class is referenced by a variable.
Foo and Bar have the same base class of NSObject in this example. I tried using the code above and it won't work. If I use
id aClass = Foo;
or
id aClass = [Foo class];
It won't work either. Must I instantiate a dummy object? Can Objective-C do it without instantiation?
What you're trying should work fine. For example, the following works:
Class NSStringClass = [NSString class];
NSString *string = [NSStringClass string];
Is it possible you've neglected to #import the definition of Foo?

Class Name with a "+"

I am working on an iOS project in Xcode and I see some classes that have names with a "+"; for example:
TableViewController+TableView.h and then the class is named: #interface RKTableViewController (TableView) as opposed to RKTableViewController+TableView.
What is this + and the (TableView)? If its subclassing UITableView shouldn't the class be declared as: Subclassed name : Parent class name format?
The + in the filename isn't semantically important. Naming a file "ClassName+CategoryName.h/m" is just a popular convention for naming files containing categories.
#interface RKTableViewController (TableView)
#end
declares a category called "TableView" on the RKTableViewController class. Categories are used to add methods to a class outside its main implementation. See the Apple documentation on categories here: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/chapters/occategories.html
These are categories. The are very helpful at times.
You can add methods to a class by declaring them in an interface file
under a category name and defining them in an implementation file
under the same name. The category name indicates that the methods are
additions to a class declared elsewhere, not a new class. You cannot,
however, use a category to add additional instance variables to a
class.
The methods the category adds become part of the class type. For
example, methods added to the NSArray class in a category are included
as methods the compiler expects an NSArray instance to have in its
repertoire. Methods added to the NSArray class in a subclass, however,
are not included in the NSArray type. (This matters only for
statically typed objects because static typing is the only way the
compiler can know an object’s class.)
Category methods can do anything that methods defined in the class
proper can do. At runtime, there’s no difference. The methods the
category adds to the class are inherited by all the class’s
subclasses, just like other methods.
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/chapters/occategories.html
Example:
Here is an example of a category I use all the time. I don't own NSMutableArray but I would love for there to be a simple move function. Instead of subclassing just to add a simple function I attach a category.
// NSMutableArray+Move.h
#interface NSMutableArray (move)
- (void)moveObjectFromIndex:(NSUInteger)from toIndex:(NSUInteger)to;
#end
// NSMutableArray+Move.m
#implementation NSMutableArray (move)
- (void)moveObjectFromIndex:(NSUInteger)from toIndex:(NSUInteger)to
{
if (to != from) {
id obj = [self objectAtIndex:from];
[self removeObjectAtIndex:from];
if (to >= [self count]) {
[self addObject:obj];
} else {
[self insertObject:obj atIndex:to];
}
}
}
This allows me to do new things with a class thats already been created all over my app. So anywhere I use an NSMutableArray I can call my added method like so
NSMutableArray *myArray = [NSMutableArray arrayWithObjects:#"Object A", #"Object B", #"Object C", nil];
[myArray moveObjectFromIndex:0 toIndex:2];

Find the Selector of a Class method

I'm quite a newbie in Objective C, though I have some background in Java reflection.
Here, I have a classic class method findAll that find all the domain objects from the database. The class Univers directly inherits from DomainObject
#interface DomainObject : NSObject
- (NSString *) execute : (NSString*) method withJson:(NSString*)json;
+ (NSString*)findAll: (NSString*)json;
#end
#implementation DomainObject
- (NSString *) execute: (NSString*) method withJson:(NSString*)json{
method = [NSString stringWithFormat:#"%#%#", method, #":"];
//method is 'findAll:'
NSString* result = [ self performSelector:
NSSelectorFromString(method) withObject:json];// Error here
return result;
}
#end
The code was working when findAll was NOT a class method (ie -findAll declaration), but now I have the error : NSInvalidArgumentException -[Univers findAll:]
It clearly seems that the runtime is looking for an instance method.
Any idea to find my class method ?
Instead of calling
NSString* result = [self performSelector:NSSelectorFromString(method) withObject:json];
you need to call
NSString* result = [[self class] performSelector:NSSelectorFromString(method) withObject:json];
for class methods.
After all it's the object instance's class that supposed to be calling the method, not the instance itself.
Short explanation: NSObject implements - (Class)class; (not to be mistaken with + (Class)class of similar effect, which NSObject implements, too!) which returns the Class object of your instance object. Keep in mind that in Objective-C in addition to plain instance objects, Classes are actual objects, too: objects of type Class, that is (vs. id, NSObject, …).
See the documentation for the -class method here.
Btw, you should probably wrap your method call into an conditional block to prevent exceptions caused by calls to missing methods.
SEL selector = NSSelectorFromString(method);
if ([[self class] respondsToSelector:selector]) {
NSString* result = [[self class] performSelector:selector withObject:json];
}
In general it's a common pattern in Objective-C to call an object's class method by receiving the class object via [object class].
Consider this case of a class called Foo implementing a convenience method for returning an autporeleased instance of itself (to be called via: Foo *newFoo = [Foo foo];):
While it would certainly be possible to implement said method like this (after all we know the object's class name, right?):
+ (id)foo {
return [[[Foo alloc] init] autorelease];
}
the correct way is this:
+ (id)foo {
return [[[self alloc] init] autorelease];
}
As the first one would cause problems with polymorphism in subclasses (Such as a subclass called FooBar, for which it should clearly be [FooBar alloc] …, not [Foo alloc] …. Luckily [[self class] alloc] solves this dynamically).
While this is clearly not the right place for a thorough explanation of this (rather offtopic one might say) it's certainly worth noting/warning about, imho.

Instantiate Class Programmatically in iOS

I am working a big iOS project, the design is not a nice as I would like it to be, but I must stick to it. (life can be a bitch sometimes).
The thing is that we have a Library that basically let's you browse a catalog. You have a filter, where you specify a certain search criteria, and you are presented with a list were you can press on the items that you are interested. When you press an item, you can see a more detailed description of it.
The company were a work for, sells this same software to many different companies that have different catalogs. The idea is that the Library has all the main functionality, and the project that use it, might in some way extend or completely override some of the given interfaces.
To give you an example, imagine my library has 2 classes that manages 2 views. They would be "FilterViewController" and "DetailsViewControllers". In some place of the code this classes gets instantiated. It would look something like this
My approach is something like this:
ProjectA side
// Here I configure the library
Library.FilterViewClass = ProjectAFilterViewController;
Library.DetailsViewClass = ProjectADetailViewController;
ProjectB side
Library.FilterViewClass = ProjectBFilterViewController;
Library.DetailsViewClass = nil;
Library side
// Did the user configure the library?
if(self.FilterViewClass == nil){
// I alloc the default ViewController
myFilterViewController = [[FilterViewController alloc] init];
}else{
// Here I would like to alloc the custom ViewController
myFilterViewController = [[Library.FilterViewClass alloc] init]; // DaProblem!
}
The problem with that approach is that I actually don't know if it's possible to instantiate object programmatically. Or at least I don't know how. Maybe I am using the wrong approach, some direction would be appreciated. Txs in advance!
To get class from string you can use this function
Class cl = NSClassFromString(#"MyClass");
To get class of existing variable just call class method.
Class cl = [obj class]; // assuming obj1 is MyClass
Now you can create instance of MyClass
MyClass *myClass = (MyClass*)[[cl alloc] init];
...
[myClass release];
Use
myFilterViewController = [[[Library.FilterViewClass class] alloc] init];
You can also instantiate from a class name, should that be useful to you:
id obj = [[NSClassFromString(#"MyClass") alloc] init];
Class someClass = [Foo1 class];
Foo * someObject = [[someClass alloc] init];
[someObject bar];
Class someClass2 = [Foo2 class];
Foo * someObject2 = [[someClass2 alloc] init];
[someObject2 bar];
Interface+Implementation:
#interface Foo : NSObject
- (void)bar;
#end
#interface Foo1 : Foo
#end
#interface Foo2 : Foo
#end
#implementation Foo
- (void)bar {
NSLog(#"abstract foo");
}
#end
#implementation Foo1
- (void)bar {
NSLog(#"foo1bar");
}
#end
#implementation Foo2
- (void)bar {
NSLog(#"foo2bar");
}
#end
Output:
2011-11-24 11:24:31.117 temp[21378:fb03] foo1bar
2011-11-24 11:24:31.118 temp[21378:fb03] foo2bar