Objective-C: How to change the class of an object at runtime? - objective-c

I tried to answer Using a UITableView subclass with a UITableViewController with ISA Switching like so:
self.tableView->isa = [MyTableView class];
But, I get the compile error: Instance variable 'isa' is protected.
Is there a way to get around this? And, if so, is it safe to do?
I'm asking because #AmberStar's answer to that question seems slightly flawed. (See my comment.)

If your tableview class provides ANY storage this will break. I would not recommend the path you're going down. But the correct method would be to use object_setClass(tableView, [MyTableView class]).
Please make sure this is really what you want.
Here is a small code-sample showing how this is a horrible idea.
#import <objc/runtime.h>
#interface BaseClass : NSObject
{
int a;
int b;
}
#end
#implementation BaseClass
#end
#interface PlainSubclass : BaseClass
#end
#implementation PlainSubclass
#end
#interface StorageSubclass : BaseClass
{
#public
int c;
}
#end
#implementation StorageSubclass
#end
int main(int argc, char *argv[])
{
BaseClass *base = [[BaseClass alloc] init];
int * random = (int*)malloc(sizeof(int));
NSLog(#"%#", base);
object_setClass(base, [PlainSubclass class]);
NSLog(#"%#", base);
object_setClass(base, [StorageSubclass class]);
NSLog(#"%#", base);
StorageSubclass *storage = (id)base;
storage->c = 0xDEADBEEF;
NSLog(#"%X == %X", storage->c, *random);
}
and the output
2011-12-14 16:52:54.886 Test[55081:707] <BaseClass: 0x100114140>
2011-12-14 16:52:54.889 Test[55081:707] <PlainSubclass: 0x100114140>
2011-12-14 16:52:54.890 Test[55081:707] <StorageSubclass: 0x100114140>
2011-12-14 16:52:54.890 Test[55081:707] DEADBEEF == DEADBEEF
As you can see the write to storage->c wrote outside the memory allocated for the instance, and into the block I allocated for random. If that was another object, you just destroyed its isa pointer.

The safe way is to create a new instance.
Swapping isa is not safe - you have no idea what the memory layout of a class is or what it will be in the future. Even moving up the inheritance graph is really not safe because objects' initialization and destruction would not be performed correctly - leaving your object in a potentially invalid state (which could bring your whole program down).

Related

Objective-C protocol property compiler warning

I can't get rid of compiler warning when I define property inside protocol. Strange thing is that I have two properties defined, and I only get warnings for the second one (which is object type, while the first property is value type).
Here is screenshot:
Can anyone tell me how to get rid of this warning, and why it is generated? The code is working normally, it is just this warning that annoys me :)
In your program, the property is called view. There must be a getter called view and a setter called setView. If you do not use #synthesize you must supply these two methods, and this is the reason of the compiler warning.
The code is working normally because you do not reference the property using dot notation or call the getter and setter methods.
Your issue is that the compiler cannot find an implementation for the properties you defined in the protocol.
For this reason, it is not recommended to add properties to a protocol, instead, you would define just a simple method to access the property, and one to set it. That would give you the proper error messages, and while you couldn't use dot-notation, it keeps the warnings in the right place.
Alternatively, you could do something like this (not recommended, but for educational reasons):
#import <objc/runtime.h>
#protocol myProto
#property (assign) int myProperty;
#end
#implementation NSObject(myProto)
-(int) myProperty
{
return [objc_getAssociatedObject(self, "myProperty") intValue];
}
-(void) setMyProperty:(int) myProperty
{
objc_setAssociatedObject(self, "myProperty", [NSNumber numberWithInt:myProperty], OBJC_ASSOCIATION_RETAIN);
}
#end
#interface MyObj : NSObject<myProto>
#end
#implementation MyObj
#dynamic myProperty;
#end
int main(int argc, char *argv[])
{
#autoreleasepool
{
MyObj *myObj = [MyObj new];
myObj.myProperty = 10;
NSLog(#"%i", myObj.myProperty);
}
}

Variable access in multiple classes

I have a Cocoa project with an object that holds information from a SQLite database.
By now the information is stored in memory by this object and is used in the user interface to read and write new information.
But now I came to a small problem... I decided to create a new controller class to handle the actions of an NSTableView and I want to access this same database object that was declared elsewhere.
Which is the best option to access this information? I wish to avoid loading the information more than once in memory and also avoid use pure C/C++ codes with global variables.
It is better to understand my point by looking at the code.
I accept other solutions as well, naturally.
My idea of code is currently like this:
FirstClass.h
#import <Foundation/Foundation.h>
#import "DatabaseModel.h"
#interface FirstClass : NSObject {
IBOutlet NSScrollView *informationListTable;
NSMutableArray *informationList;
}
#end
FirstClass.m
#import "FirstClass.h"
#implementation FirstClass
- (void)awakeFromNib{
DatabaseModel *list = [[DatabaseModel alloc] init];
informationList = [[NSMutableArray alloc] initWithArray:[list loadList]];
[list release];
[machinesListTable reloadData];
}
SecondClass.h
#import <Foundation/Foundation.h>
#interface SecondClass : NSObject {
IBOutlet NSTextField *labelName;
NSString *name;
}
- (IBAction)showName:(id)sender;
#end
SecondClass.m
#import "FirstClass.h"
#import "SecondClass.h"
#implementation SecondClass
- (IBAction)showName:(id)sender{
/*
Here name must get something like:
[[FirstClass.informationList objectAtIndex:3] name]
Here labelName must display name.
*/
}
#end
you can either create the object once then pass the object around, with each controller retaining it as needed. Or you can use a singleton instance. I would say the singleton instance is easier to read, but it depends on the application
One solution would be to make FirstClass a singleton. Then, anywhere else in your code, you could call [FirstClass sharedInstance] (replace sharedInstance with the name you'll give to your class method) and use this object. You'll have to be careful about concurrency issues though.

Why am I getting "incompatible pointer type"?

I am trying to create a custom object that simply inherits the NSString class and overrides the 'description' method.
When I compile, however, I am getting a warning:
Incompatible pointer types initializing 'OverrideTester *' with an expression of type 'NSString *'
Here is my code:
main.m
#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSAutoreleasePool.h>
#import "OverrideTester.h"
int main (int argc, char *argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString *str = #"Programming is fun";
OverrideTester *strOverride = #"Overriding is fun";
NSLog (#"%#", str);
NSLog (#"%#", strOverride);
[pool drain];
return 0;
}
OverrideTester.h
#import <Foundation/Foundation.h>
#interface OverrideTester : NSString
-(void) description;
#end
OverrideTester.m
#import "OverrideTester.h"
#implementation OverrideTester
-(void) description
{
NSLog(#"DESCRIPTION!\n");
}
#end
NSString is part of a class cluster. You cannot just create arbitrary subclasses of it, and when you do, you can't assign constant strings to them (which are type NXConstantString). See Subclassing Notes in the NSString documentation. Generally you don't want to subclass NSString. There are better solutions for most problems.
you are assigning an instance of NSString to your variable of type OverrideTester. If you want an instance of your class, you need to instantiate an instance of that class; type-casting will never change the class of an instance.
description is defined as returning an NSString*:
- (NSString *)description;
Do not try to learn about subclassing and overriding methods by subclassing NSString (or any other class cluster). If you want to play with subclassing and such -- a very good idea when new to the language, assuredly -- then subclass NSObject, potentially multiple levels , and play there.
How do you mean to subclass NSObject,
potentially multiple levels? Isn't it
possible NSObject might have
conflicting methods compared to other
class clusters or just not have them
available to override?
If your goal is to figure out how method overrides work (which I thought it was), then you'd be better off doing it entirely yourself.
I may have mis-read your question.
In any case, subclassing NSString is pretty much never done. There are very very few cases where it is useful. Overriding description in anything but custom classes specifically for debugging purposes is useful, yes. Calling description in production code should never be done.
Also, why would description return an
NSString* in this code?
What would happen if something that expects an NSString* return value were to call your version that doesn't return anything?
A crash.
You are declaring a variable named strOverride of type pointer to OverrideTester. But to that variable, you are trying to assign a pointer to an NSString. You cannot assign a superclass to a variable of a subclass. Imagine a generic class TwoWheeled and a derived class Motorbike. A Motorbike can be treated like a TwoWheeled, but not the other way round as the Motorbike has features a normal TwoWheeled might not have like a motor.

Question about creating classes in Objective-C

I am really new to objective C, and I want to make a class that is an NSArray of NSDictionary, and then have a method that grabs a random entries. I know how to make that but I don't understand how to make it in the class. What I mean is I thought that you could put the code that declared (or whatever the correct terminology is) the array just sort of in the middle of the implementation file and then I would write a method under that. The only instance variable I had was the NSArray and that was in the interface file, along with the method prototype (or whatever) and these were the only things that were in the interface file.
I couldn't figure out the problem so I made a test class that was the same but with just an array of simple text strings. I used the same logic here and I'm pretty certain it is totally backward, I don't know in which way though.
This is the interface for the test class:
#import <Foundation/Foundation.h>
#interface TestClass : NSObject {
NSArray *TestArray;
}
#end
And this is the implementation file
#import "TestClass.h"
#implementation TestClass{
NSArray *TestArray;
}
TestArray = [[NSArray alloc] arrayWithObjects:#"stuff",#"things",#"example",#"stuffThings",nil];
#end
You should really read Apple's introduction to Objective-C. It explains the syntax and structure of the language. You must also read the Objective-C memory management guide so that your programs don't leak memory and crash.
Having said that, here's probably what you're trying to create (I took the liberty of changing some of your variable names):
TestClass.h
#import <Foundation/Foundation.h>
#interface TestClass : NSObject {
NSArray* strings_;
}
// Method declarations would go here
// example:
- (NSString*)randomElement;
#end
TestClass.m
#import "TestClass.h"
#import <stdlib.h>
// Notice how the implementation does NOT redefine the instance variables.
#implementation TestClass
// All code must be in a method definition.
// init is analogous to the default constructor in other languages
- (id)init {
if (self = [super init]) {
strings_ = [[NSArray alloc] initWithObjects:#"stuff", #"things", nil];
}
return self;
}
// dealloc is the destructor (note the call to super).
- (void)dealloc {
[strings_ release];
[super dealloc];
}
- (NSString*)randomElement {
NSUInteger index = arc4random() % [strings_ count];
return [strings_ objectAtIndex:index];
}
#end
For random number generation, it's easy to use arc4random() because it doesn't require setting the seed value.

Handling class methods when sub-classing in objective-c

While attempting my first sub-class in Objective-C I have come across the following warning which I cannot seem to resolve. The call to decimalNumberWithMantissa gives a warning of "initialization from distinct Objective-C type".
#import <Foundation/Foundation.h>
#interface NSDecimalNumberSub : NSDecimalNumber {
}
#end
#implementation NSDecimalNumberSub
#end
int main (int argc, char *argv[]) {
NSDecimalNumberSub *ten = [NSDecimalNumberSub
decimalNumberWithMantissa:10
exponent:0
isNegative:NO];
}
Does a class method have to be treated differently with a sub-class? Am I missing something simple? Any help would be appreciated.
NSDecimalNumber defines the decimalNumberWithMantissa:... method to return an NSDecimalNumber, so you're going to get back an instance of the base class and not your custom subclass. You'll have to create your own convenience method to return an instance of your subclass, or just alloc and initialize it another way.
If you're writing your own class you can define a convenience method like that to return type id, and then use [[self alloc] init] when creating the instance to make your class safe for subclassing.