I'm probably missing something here as this seems like a pretty basic Objective-C question, but not finding any similar questions (maybe I'm searching the wrong thing, sorry in advance if so!). My question is around enforcing a class that conforms to a protocol to have the correct type in it's method signatures. Since that doesn't seem to be enforced by the compiler, what's the best practice here.
Let me provide an example.
// View.h
#protocol View <NSObject>
- (void)createNewViewUsing:(UIView *)view;
#end
// SomeView.h
#interface SomeView : NSObject <View>
#end
- (void)createNewViewUsing:(UIStackView *)view {
NSLog(#"The view is %#", view);
NSLog(#"%#", view.arrangedSubviews); // CRASH!
}
// Implementation
SomeView *const view = [[SomeView alloc] init];
UIView *const anotherView = [[UIView alloc] init];
[view createNewViewUsing:anotherView];
How do I avoid this crash? Is the correct thing to do to just make sure I'm always matching the method signature as my protocol and then check the type? How do I ensure someone else (or my future self) does this? Why does the compiler not enforce this?
In Objective-C you have to keep track of it yourself. So you are totally allowed to do
- (void)createNewViewUsing:(UIStackView *)view
with the understanding that you will then only message this with stackviews and not with views. If you deviate from this you do so at your own risk.
However, a better approach is to stick to the original protocol with something as below
- (void)createNewViewUsing:(UIView *)view {
if ( [view isKindOfClass:UIStackView.class] )
{
// Cast it
UIStackView * sv = view;
// Now you are safe ... use sv in what follows
}
else // ... not a stackview ... maybe pass to parent class
This of course adds a lot of overhead to your code but you may totally need it for e.g. a large project. You are the one making the judgement call here.
Related
Being new to objective-C coding I started out writing a basic app, fully programmatically (not using storyboards or xib) in one file, my AppViewController h and m files.
Everything worked lovely.
So then I wanted to break up the mass of code by subclassing sections, and everything went well apart from the UIPickerView. In fact simply commenting out the [background addSubview:colorPicker]; seemed to totally fix the issue. I never found the answer online so I proceeded to make a new document to replicate said issue.
So here goes:
UIPickerViewController.h
#import <UIKit/UIKit.h>
#import "Picker.h"
#interface UIPickerViewController : UIViewController
#end
Simply imports my new class.
UIPickerViewController.m
#import "UIPickerViewController.h"
#interface UIPickerViewController ()
#end
#implementation UIPickerViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *superview = self.view;
int height = superview.bounds.size.height;
int width = superview.bounds.size.width;
CGRect popupRect = CGRectMake(0, 0, width, height);
UIView *popup = [[UIView alloc]initWithFrame:popupRect];
popup.tag = 8;
[superview addSubview:popup];
Picker *picker = [[Picker alloc]initWithFrame:popupRect];
[picker viewAddTypeScreenToView:superview];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#end
Sets up a new view with a tag (so that i could reference it later with my new class)
Then actions a method from my new class to populate my new view.
Picker.h
#import <UIKit/UIKit.h>
#interface Picker : UIView
<UIPickerViewDataSource,UIPickerViewDelegate>
{
UIPickerView *colorPicker;
NSMutableArray *colorsArray;
}
#property (nonatomic, retain) UIPickerView *colorPicker;
#property (nonatomic, retain) NSMutableArray *colorsArray;
#property (strong,nonatomic) UILabel *myValue;
-(void)viewAddTypeScreenToView:(UIView*)superview;
#end
Setting up my variables and accessible method.
Picker.m
#import "Picker.h"
#implementation Picker
#synthesize colorsArray;
#synthesize colorPicker;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
-(void)viewAddTypeScreenToView:(UIView*)superview
{
UIView *baseView =[superview viewWithTag:8];
int height = baseView.bounds.size.height;
int width = baseView.bounds.size.width;
CGRect fullScreen = CGRectMake(0, 0, width, height);
UIView *background = [[UIView alloc]initWithFrame:fullScreen];
background.backgroundColor = [UIColor blackColor];
colorsArray = [[NSMutableArray alloc] initWithObjects:#"Red",#"Blue",#"Yellow",#"Green",nil];
CGRect myPickerRect = CGRectMake(10, 70, (width/2)-40, 200);
colorPicker = [[UIPickerView alloc]initWithFrame:myPickerRect];
colorPicker.dataSource = self;
colorPicker.delegate = self;
colorPicker.showsSelectionIndicator = YES;
[colorPicker selectRow:2 inComponent:0 animated:YES];
CGRect labelFrame = CGRectMake(10, 10, 180, 50);
_myValue = [[UILabel alloc]initWithFrame:labelFrame];
_myValue.textColor = [UIColor redColor];
_myValue.text = #"select colour";
[background addSubview:_myValue];
[background addSubview:colorPicker];
[baseView addSubview:background];
}
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
return 1;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
return colorsArray.count;;
}
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return colorsArray[row];
}
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
_myValue.text = [NSString stringWithString:colorsArray[row]];
}
#end
And finally the initiation called by the method in the picker class file.
This gives me an error along these lines
-[UITableViewCellContentView pickerView:titleForRow:forComponent:]: unrecognized selector sent to instance 0x8f2b000
2014-03-19 10:29:48.407 Briefcase[1800:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UITableViewCellContentView pickerView:titleForRow:forComponent:]: unrecognized selector sent to instance 0x8f2b000'
Which i've read is to do with either the datasource, or ARC systems, however none of the responses that I have found relate to or work with the type of set up that I have above. I'm sure it's something really simple but after a few days of failed searching, it's officially driving me crazy.
The problem is most likely that the instance of Picker that is being created in UIPickerViewController is never added to the view hierarchy and thus gets released prematurely (provided we're talking about a project using ARC here).
This leads to the pickerview's delegate and datasource becoming invalid and, basically, pointing at any random object. That's what is causing your crash: A message to your delegate cannot be delivered because the delegate is dead already. The picker still keeps a pointer which used to point at the delegate, but which has become invalid and points at a random object now, in this case a table view cell, which basically doesn't know what to do with this message and crashes.
The problem should go away if you add Picker *picker as an ivar or a retaining / strong property to UIPickerViewController.h - this will retain the picker beyond the scope of the viewDidLoad method and should keep it alive.
But that would be just a workaround, the real problem is your overall design. You said you're new to objective-c and indeed, it looks like you lack a basic understanding of iOS view and view controller hierarchies and, to some degree, the concept of object oriented programming. You might want to dig into something more basic before trying to fix your code because, quite frankly, it should be rather re-written than fixed.
I'd be happy to provide you with suggestions about how to structure your code, but please provide some information about what functionality you'd like to achieve first.
Edit (in response to your comment):
As a rule of thumb, do not spread functionality over several classes unless necessary. For objects, which serve a rather infrastructural purpose, like a specialized textfield or a pickerview, always ask yourself: "If I would like to reuse that object in another project, would that be as easy as using any other existing object, like, for example, UILabel?" If the answer is "No", then something is wrong. Ideally, interface objects are self-contained and to use them, you just invoke them, add them to a view and tell them, which text to display or which options to offer. If that information is subject to change or if the object needs to interact with other parts of your code, make use of delegation and protocols. Under no circumstances should the functionality of your object be tied to hard coded values or rely to some view to have a certain tag.
If you subclass UIView, the resulting object should behave like any other instance of UIView. It should be added to the view hierarchy by you or some object, but it shouldn't add or remove itself. If it works without being added to the view hierarchy at all, something is wrong. A view serves the purpose of being a part in your interface and all the logic it contains should work to that end, not more, not less.
Normally, interface objects should not interfere with one another. If something happens to one object (button pressed, option selected, text changed...) and another object is supposed to reflect that change, it is the view controllers responsibility to make that happen. The view controller is the place where the logic happens. If there is a task which requires a lot of complex logic, it might be a good idea to encapsule that logic into a purpose build class. One such example would be a class which manages network connections. This class should be again self contained: If the view controller needs some remote information, it asks your network class. Once your network class has that information (or failed to retrieve it), it reports back to your view controller. The view controller then updates the interface - under no circumstance should the networking class contain code which affects the interface.
It is important to understand that you could very well ignore these rules and still end up with a working app. And in some cases, the "direct" way may appear to be easier to implement and thus may look very tempting. But you'll pay the price later - once you start debugging your code. If your picker does not behave the way it should, you need to look into several places and wrap your mind around several objects, just to make one interface object behave right. And likely you will break one functionality while fixing the other.
So, try to make it right from the start, even though it requires more planning and learning. Trust me, it pays out, I started out just like you several years ago ;)
Well I'm just confused when the lazy instantiation should be used.
I understand the basic concept of lazy instantiation though.
" I understand that all properties start out as nil in Objective-C and that sending a message to nil does nothing, therefore you must initialize using [[Class alloc] init]; before sending a message to a newly created property. "(Lazy instantiation in Objective-C/ iPhone development)
m.file:
#property (strong, nonatomic) NSMutableArray *cards;
- (NSMutableArray *)cards
{
if (!_cards) _cards = [[NSMutableArray alloc] init];
return _cards;
}
- (void)addCard:(Card *)card atTop:(BOOL)atTop
{
if (atTop) {
[self.cards insertObject:card atIndex:0];
} else {
[self.cards addObject:card];
} }
Well, what I really don't get is when I'm supposed to use this type of instantiation?
Mostly I see the code like this:
h.file:
#interface Card : NSObject
#property (strong, nonatomic) NSString *contents;
m.file:
if([card.contents isEqualToString:self.contents]){
score = 1;
}
*This might be a stupid question but I'm really confused. I'm new here, Thanks.
There is no reason to use Lazy Instantiation/Lazy Initialization if you find it confusing; simply initialize your instance variables/properties in the class init methods and don't worry about it.
As the object is created as a side-effect of calling the getter method, it's not immediately obvious that it is being created at all, so one alternative, which would also mean you can use the default compiler-generate getter method, is to explicitly check for it in addCard:
- (void)addCard:(Card *)card
atTop:(BOOL)atTop
{
if (!self.cards)
self.cards = [NSMutableArray new];
if (atTop) {
[self.cards insertObject:card atIndex:0];
} else {
[self.cards addObject:card];
}
}
(and removing the user-supplied getter method)
However the net-effect is the same as the code you posted, with the exception that self.cards will return nil until addCard is called, however I doubt this will cause a problem.
When using dot notation to access your instance variables, you are calling your getter method for that given property. Therefore, by using dot notation and lazy instantiation, your getter will always assert that a property is not nil before you send it a message. Therefore, code such as
[self.cards insertObject:card atIndex:0];
will actually call the getter at self.cards; if you use dot notation on your objects and program the getters accordingly, you will always ensure that your instance variables are allocated and initialized, while simultaneously cleaning up your init method for code that is much more important.
Lazy instantiation is a common practice among Objective-C programmers; I suggest getting into the flow of the convention.
EDIT: thanks for Raphael mentioning this in a comment previously.
Lazy instantiation is a performance enhancement in certain types of scenarios. One example would be a class that has a very expensive user facing UI string.
If you create many of instances of that class but only a very small subset of those instances will be shown in your UI, you waste a lot of CPU resources creating a very expensive UI string that rarely will be used.
Im trying to make it so that every single UIControl in my application (UIButton, UISlider, etc) all have special extra properties that I add to them.
I tried to accomplish this by creating a UIControl Category and importing it where needed but I have issues.
Here is my code.
My setSpecialproperty method gets called but it seems to be getting called in an infinite loop until the app crashes.
Can you tell me what Im doing wrong or suggest a smarter way to add a property to all of my UIControls?
#interface UIControl (MyControl)
{
}
#property(nonatomic,strong) MySpecialProperty *specialproperty;
-(void)setSpecialproperty:(MySpecialProperty*)param;
#end
////////
#import "UIControl+MyControl.h"
#implementation UIControl (MyControl)
-(void)setSpecialproperty:(MySpecialProperty*)param
{
self.specialproperty=param;
}
///////////////
#import "UIControl+MyControl.h"
#implementation ViewController
UIButton *abutton=[UIButton buttonWithType:UIButtonTypeCustom];
MySpecialProperty *prop=[MySpecialProperty alloc]init];
[abutton setSpecialproperty:prop];
While you can't add an iVar to UIControl via a category, you can add Associated Objects, which can be used to perform much the same function.
So, create a category on UIControl like this:
static char kControlNameKey;
- (void) setControlName: (NSString *) name
{
objc_setAssociatedObject(self, &kControlNameKey, name, OBJC_ASSOCIATION_COPY);
}
- (NSString *) controlName
{
return (NSString *)objc_getAssociatedObject(array, &kControlNameKey);
}
There's more to it than that, I guess you'll need to check if an association exists before setting a new one, otherwise it will leak, but this should give you a start.
See the Apple Docs for more details
self.specialproperty=param is exactly the same as calling [self setSpecialproperty] (see here for some totally non biased coverage of Obj-C dot notation), which makes your current usage infinitely recursive.
What you actually want to do is:
-(void)setSpecialproperty:(MySpecialProperty*)param
{
_specialproperty = param;
}
Where _specialproperty is the implicitly created ivar for your property.
I'm assuming there's some reason why you've implemented your setSpecialproperty setter? Why not just use the one that is implicitly created for you?
the problem is that you can not add a property to a category, you can add behavior (methods) but not properties or attributes, this can only be done to extensions, and you can not create extensions of the SDK classes
use your method as
change your method name to
-(void)setSpecialproperty:(MySpecialProperty *)specialproperty
-(void)setSpecialproperty:(MySpecialProperty*)specialproperty
{
if(_specialproperty!=specialproperty)
_specialproperty = specialproperty;
}
and synthesize your specialProperty as
#synthesize specialproperty=_specialproperty;
I have a MKPolyline subblass which I want to implement NSCoding, i.e.
#interface RSRoutePolyline : MKPolyline <NSCoding>
I asked a question on the best way to encode the c-array and got an excellent answer. However, there is no init method defined on MKPolyline, i.e. there is no other way to give it data other than its class method polylineWithPoints:points.
Is this code where my comment is ok?
- (void)encodeWithCoder:(NSCoder *)aCoder
{
MKMapPoint *points = self.points;
NSUInteger pointCount = self.pointCount;
NSData *pointData = [NSData dataWithBytes:points length:pointCount * sizeof(MKMapPoint)];
[aCoder encodeObject:pointData forKey:#"points"];
[aCoder encodeInteger:pointCount forKey:#"pointCount"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
NSData* pointData = [aDecoder decodeObjectForKey:#"points"];
NSUInteger pointCount = [aDecoder decodeIntegerForKey:#"pointCount"];
// Edit here from #ughoavgfhw's comment
MKMapPoint* points = (MKMapPoint*)[pointData bytes];
// Is this line ok?
self = (RSRoutePolyline*)[MKPolyline polylineWithPoints:points count:pointCount];
return self;
}
You should call an init method on any subclass of NSObject. Since MKPolyline is an NSObject, you should init it.
But MKPolyline has no methods and no init. This is Objective C's was of telling you that you can't subclass it.
Instead, as WDUK suggested, define your own class. It keeps track of your list point points, and manages NSCoding to save and restore them as needed.
#interface RSPolyline: NSObject<NSCoding>
- (id) initWithPoints: (NSArray*) points;
- (id) initWithCoder:(NSCoder *)aDecoder;
- (void) encodeWithCoder:(NSCoder *)aCoder;
- (MKPolyline*) polyLine;
#end
Your class can generate a polyline on request, perhaps caching the result if performance is an issue.
As a rule, don't reach for inheritance first. When you want to extend and improve a class, think first of composition.
It's dirty not to call [super init], and it doesn't bode well with my idea of good programming. Without calling super yourself, it isn't a true subclass; just a bastardization of composition that relies on a side effect of calling a convenience constructor. Saying this, I believe your method described will work OK, but it goes against the grain of good Objective-C programming and its conventions.
What I would suggest is to use MKPolyLine as an MKPolyLine instance, and use a category to add the extra bells and whistles you need. As for adding extra instance variables and such, you can use associated objects. An introduction to this concept can be found here, and this SO question addresses the use of them with categories: How do I use objc_setAssociatedObject/objc_getAssociatedObject inside an object?
While it is generally allowed to create and return a different object in an init method, there are three problems with that line (explained below). Instead of this, I would suggest overriding the points and pointCount properties so that you can return values stored in an instance variable, and call the super implementation there if the instance variable is empty. Then, your initializer just sets these instance variables so that they will be used.
- (MKMapPoint *)points {
if(myPointsIvar == NULL) return [super points];
else return myPointsIvar;
}
// similarly for pointCount
The first problem is that you are creating a new object, but not releasing the old one, which means you are leaking it. You should store the result in a different variable, then release self, then return the result (you don't need to store it in self).
Second, polylineWithPoints:count: returns an autoreleased object, but initWithCoder: should return a retained one. Unless there is another retain on it, it could be deallocated while you are still using it.
If these were the only problems, you could solve both like this:
MKPolyline *result = [MKPolyline polylineWithPoints:points count:pointCount];
[self release];
return [result retain];
However, there is a third problem which cannot be solved so easily. polylineWithPoints:count: does not return a RSRoutePolyline object, and the object it returns may not be compatible with your subclass's methods (e.g. it probably won't support NSCoding). There really isn't a way to fix this, so you can't use polylineWithPoints:count:.
I want' to implement "Fix and continue functionality" that was in Xcode 3.
CONTEXT:
The main idea is:
When I need to "Fix something fast", I'm not re-compiling, project. I'm compiling small Attacker class with 'updated' method implementation, loading it into memory and replacing VictimClass's method which have incorrect implementation in runtime.
I think that this method will work faster that full project recompilation.
When i'm done with fixes i'm just copying source of Attacker class method to Victim class.
PROBLEM
At the moment, I don't know how correctly call [super ...] in Attacker class.
For example, i have VictimClass
#interface VictimClass : UIView #end
#implementation VictimClass
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
}
#end
#interface AttackerClass : NSObject #end
#implementation AttackerClass
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
[self setupPrettyBackground];
}
#end
....
// EXCHANGE IMPLEMENTATIONS
Method m = class_getInstanceMethod([AttackerClass class], #selector(drawRect:));
const char * types = method_getTypeEncoding(m);
IMP attackerImp = method_getImplementation(m);
class_replaceMethod([VictimClass class], #selector(drawRect:), attackerImp, types);
// Invoking drawRect on Victim
VictimClass * view = /* */;
[view setNeedsDisplay];
At this point , when drawRect: method will be called, this will lead to exception, since drawRect: will be called on NSObject class, but not on UIView class
So, my question is, how correctly call [super drawRect:] in AttackerClass, to have possibility to correctly exchange implementation in runtime?
Main idea is to provide a way to correctly replace any method in Victim class by Attacker's class method. Generally, you don't know, superclass of Victim class.
UPDATE: Replacing implementation code added.
You will have to
get the receivers class (e.g. with object_getClass(rcv))
then get the super class of it (with class_getSuperclass(class))
then get the implementation of it (with class_getMethodImplementation(superclass, sel))
then call the imp.
done
Stop at any step if you got nil or NULL.
Oh, and all this seems silly. But I assume that the question just lacks of context to see the motivation for such a hack.
[Update]
An explanation for future readers:
The super keyword is resolved at compile time. Therefore it does not the intended thing when changing methods at runtime. A method which is intended to be injected in some object (and its class hierarchy) at runtime has to do super calls via runtime as outlined above.
Assuming that the runtime changes you're making involve modifying the superclass, you'll have to do something like this:
#implementation AttackerClass
-(void) drawRect:(CGRect)rect
{
if( [super respondsToSelector:#selector(drawRect:)] )
{
[super drawRect:rect];
}
[self setupPrettyBackground];
}
#end
This will check if the superclass "knows about" drawRect:, and doesn't call it in the case that super has no drawRect: selector.
Hence, when the superclass is NSObject the drawRect: message will not be sent. When you change it to UIView at runtime (whatever your reason for that is), the message can safely be sent.
One approach is to use objc_msgSendSuper. Your method -[AttackerClass drawRect:] will have the following implementation:
- (void)drawRect:(CGRect)rect {
struct objc_super superTarget;
superTarget.receiver = self;
superTarget.class = object_getClass(self);
objc_msgSendSuper(&superTarget, #selector(drawRect:), rect);
[self setupPrettyBackground];
}
but why do you need to call draw rect method for superclass NSObject, when NSObject hasn't got that method? just don't do it... call it just in VictimClass drawrect