Why am I getting the superclass and not the subclass in my method call? - objective-c

I have this class, which is a port from a C# abstract class; this is the .h file:
#interface Schedule : NSObject {
}
#property (strong, nonatomic) NSDate *apptStartTime;
#property (strong, nonatomic) NSDate *apptEndTime;
#property (strong, nonatomic) NSString *key;
-(BOOL) occursOnDate: (NSDate *) timeOfAppointment;
#end
This is the .m file for Schedule:
#implementation Schedule {
}
#synthesize apptStartTime;
#synthesize apptEndTime;
#synthesize key;
/**
The OccursOnDate method is abstract and must be implemented by subclasses. When passed a date, the schedulers must determine if an appointment falls on that date. If one does, the method should return true. If not, the method returns false.
*/
-(BOOL) occursOnDate: (NSDate *) dateOfAppointment {
return YES:
}
Because it's a C# abstract class, it has to be overridden (or subclassed) which I have done here (this is the .h file):
#interface SingleSchedule : Schedule {
}
#property (strong,nonatomic) NSDate *apptDate;
-(BOOL) occursOnDate: (NSDate *)date;
#end
This is the .m file:
#implementation SingleSchedule {
}
#synthesize apptDate;
-(BOOL) occursOnDate: (NSDate *)date {
return (apptDate == date); // <--------- TODO where is apptDate set?
}
This is where I call the occursOnDate class, expecting to get the subcclass, but I get the superclass class instead:
-(void) addAppointmentsForDate:(NSDate *)checkDate scheduleSet: (NSMutableSet *)setOfSchedules appointmentSet:(NSMutableSet *)setOfAppts {
Schedule *sc = [[Schedule alloc]init];
Appointment *newAppt = [[Appointment alloc]init];
NSArray *scheduleArray = [setOfSchedules allObjects];
for(int i = 0; i < scheduleArray.count; i++) {
if([sc occursOnDate: checkDate]) { // <-------- method called is the superclass, not the override
newAppt = [self generateAppointment:checkDate andSchedule: scheduleArray [i]];
[setOfAppts addObject:newAppt];
}
}
}
What am I missing here to be able to get the subclass method and not the other? (I have looked at SO and Google but found nothing which would answer this question exactly).

The base class implementation is being called because sc is of type Schedule, not SingleSchedule. When you instantiate a class, the new object is aware of it's own implementation and it's base class chain, but the object is not aware of it's inherited classes.
Maybe what you want is:
SingleSchedule *sc = [[SingleSchedule alloc]init];

Related

What's the best way to unit test a method with a parameter that's immutable and does not have an initializer?

Question
I'm trying to unit test my implementation of a protocol's method that takes an immutable object that's not in my project as a parameter. This object doesn't have any initializers exposed that set its properties. What's the best way to unit test in this scenario?
What I tried
I tried subclassing and casting the properties to readwrite, but you only have access to a super-class' ivars if they're explicitly marked as #protected. Since the class isn't within my project, I can't re-assign a new scope to the ivar.
I also can't mock a setter that doesn't exist, and ivars are not directly writable through mock objects.
Eventually, I decided to add an additional method that the method in question would call, passing only objects whose properties I can set, and I covered that additional method in unit tests.
Is there a better way to unit test in this scenario?
Specifics of my scenario
I'm trying to unit test my implementation of userContentController:didReceiveScriptMessage: from WebKit's WKScriptMessageHandler protocol and can't find a way to pass an instance of WKScriptMessage or of WKUserContentController with any properties set.
To unit test this, I created a new method that takes a parameter of type 'id' and called it from userContentController:didReceiveScriptMessage:, passing WKScriptMessage's body property.
Does this help you?
TestMessage.h
#import <WebKit/WebKit.h>
#interface TestMessage : WKScriptMessage
- (id) initWithBody:(id)body
frameInfo:(WKFrameInfo*)frameInfo
name:(NSString*)name
webView:(WKWebView*)webView;
#end
TestMessage.m
#import "TestMessage.h"
#interface TestMessage()
#property (nonatomic, strong) id messageBody;
#property (nonatomic, strong) WKFrameInfo* messageFrameInfo;
#property (nonatomic, strong) NSString* messageName;
#property (nonatomic, strong) WKWebView* messageWebView;
#end
#implementation TestMessage
- (id) initWithBody:(id)body
frameInfo:(WKFrameInfo*)frameInfo
name:(NSString*)name
webView:(WKWebView*)webView
{
self = [super init];
if (self)
{
_messageBody = body;
_messageFrameInfo = frameInfo;
_messageName = name;
_messageWebView = webView;
}
return self;
}
- (id) body
{
return self.messageBody;
}
- (WKFrameInfo *)frameInfo
{
return self.frameInfo;
}
- (NSString *)name
{
return self.messageName;
}
- (WKWebView *)webView
{
return self.messageWebView;
}
#end
So you're basically creating a subclass with matching properties and then overriding the getters for the superclass properties to return values for the ones you have full access to.
This worked for me with the simple test (I'm sure your scenario is more involved):
- (void) testMessageHandler
{
WKWebView* webView = [[WKWebView alloc] init];
WKUserContentController* controller = [[WKUserContentController alloc] init];
WKFrameInfo* info = [[WKFrameInfo alloc] init];
NSDictionary* testBody = #{#"testKey" : #"testVal" };
TestMessage* testMessage = [[TestMessage alloc] initWithBody:testBody
frameInfo:info
name:#"Message Name"
webView:webView];
// some object conforming to the WKScriptMessageHandler protocol
[self userContentController:controller
didReceiveScriptMessage:testMessage];
}
- (void)userContentController:(nonnull WKUserContentController *)userContentController
didReceiveScriptMessage:(nonnull WKScriptMessage *)message
{
NSLog(#"Received Script Message... (%#)", message.body);
}

Getting an error on my (first ever) CardGameViewController

I'm getting the error incompatible pointer types assigning to Deck *__strong from PlayCards *
And i'm not sure why is that. Its in the first method implemented (deck):
#import "CardGameViewController.h"
#import "PlayingCards.h"
#interface CardGameViewController ()
#property (weak, nonatomic) IBOutlet UILabel *cardLabel;
#property (nonatomic) NSUInteger flipsCount;
#property (strong, nonatomic) Deck *deck;
#end
#implementation CardGameViewController
-(Deck *) deck {
if (!_deck) _deck = [[PlayingCards alloc] init];
return _deck;
}
-(void) setFlipsCount:(NSUInteger)flipsCount {
_flipsCount = flipsCount;
self.cardLabel.text = [NSString stringWithFormat:#"Flips:%d", self.flipsCount];
}
- (IBAction)flipCard:(UIButton *)sender {
sender.selected = !sender.isSelected;
self.flipsCount++;
}
#end
This is the header file(nothing going on here):
#import <UIKit/UIKit.h>
//#import "Card.h"
//#import "Deck.h"
//#import "PlayingCards.h"
#interface CardGameViewController : UIViewController
#end
And the PlayingCard class inheriting from Deck class..
this is the PlayingCards.m
#import "PlayingCards.h"
#implementation PlayingCards
#synthesize suit = _suit;
//modifying the contents getter so it will return array with the ranks and rank+suit
-(NSString *) contents {
NSArray *cardsRank = [PlayingCards rankStrings];
return [cardsRank[self.rank] stringByAppendingString:self.suit];
}
//creating a method to make sure we get validated suits
+(NSArray *) validSuit {
return #[#"♠",#"♣",#"♥",#"♦"];
}
//creating calss method to validate the rank
+(NSArray *) rankStrings {
return #[#"?",#"A",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10",#"J",#"Q",#"K"];
}
//creating a new setter for suit to make sure we get the valitated suits, uding the validateSuit method
-(void) setSuit:(NSString *)suit {
if ([[PlayingCards validSuit] containsObject:suit]) {
_suit = suit;
}
}
//creating new getter for suit to make sure its not empty
-(NSString *) suit {
return _suit? _suit: #"?";
}
//creating a class method to make sure when user set the rank he will will
+(NSUInteger) maxRank {
return [self rankStrings].count - 1;
}
//creating a new setter to the renk to make sure the rank is validates
-(void) setRank:(NSUInteger)rank {
if (rank <= [PlayingCards maxRank]) {
_rank = rank;
}
}
#end
PlayingCards.h
#import "Card.h"
#import "Deck.h"
#interface PlayingCards : Card
#property (strong, nonatomic) NSString *suit;
#property (nonatomic) NSUInteger rank;
+(NSArray *) validSuit;
+(NSUInteger) maxRank;
#end
This line:
if (!_deck) _deck = [[PlayingCards alloc] init];
Should be:
if (!_deck) _deck = [[PlayingCardDeck alloc] init];
If the parent for Card is of class NSObject as you say, and given that PlayingCards inherits from Card, then you can't assign an instance of PlayingCards to a variable of type Deck*. That's what the compiler is telling you.
If you really need to do it, you have to write:
if (!_deck) _deck = (Deck*)[[PlayingCards alloc] init];
It would only be valid because in Objective-C the implementation is given at runtime and which method of which class is called is only decided at runtime when the message is dispatched. However, this pattern is very unusual and you better be certain that PlayingCards is implementing all the selectors that might be called on a Deck instance. A better way would be to use protocols.
You can define a protocol and then use:
id <myProtocol> deck = [[PlayingCards alloc] init];
Put in the protocol all the selectors you need.
Why can't you use this ?
PlayingCards* deck = [[PlayingCards alloc] init];

Objective-C Where to define initial Class Properties

just wanted to ask where I define initial class properties?
From other languages I am used to define some standard properties in the head before the content of the class starts.
For example paths to files. Settings and so on.
Where I fill these initial properties with values in Objective-C?
Thanks
Generally it's something like:
MyClass.h:
extern NSString * const staticValue1;
extern NSString * const staticValue2;
#interface MyClass : NSObject
{
NSString *_strval;
int _intval;
float _fltval;
}
#property (retain, nonatomic, readwrite) NSString *strval;
#property (assign, nonatomic, readwrite) int intval;
#property (assign, nonatomic, readwrite) float fltval;
#end
MyClass.m:
NSString * const staticValue1 = #"Something";
NSString * const staticValue2 = #"Something else";
#interface MyClass
#synthesize strval = _strval;
#synthesize intval = _intval;
#synthesize fltval = _fltval;
- (id)init
{
self = [super init];
if (self != nil)
{
[self setStrval:[NSString stringWithFormat:#"This is a %#", #"string"]];
[self setIntval:10];
[self setFltval:123.45f];
}
return self;
}
- (void)dealloc
{
[self setStrval:nil];
[super dealloc];
}
#end
This demonstrates the use of synthesized properties which are being used here to manage the memory of the instance variable _strval, which requires retaining/releasing to avoid memory leaks. Note that [self setStrval] is initialised with an autoreleased object (from [NSString stringWithFormat) and will be retained by the setter method. Alternatively these methods can be called using the following syntax, if you prefer:
self.strval = [NSString stringWithFormat:#"This is a %#", #"string"];
self.intval = 10;
self.fltval = 123.45f;
Maybe some of what you are after can be implemented with class methods.
Class methods are coded with a + (instead of the instance methods' -), and can't refer to instance variables, as they are not associated with any specific instance of the class.
This is a class method to return a default string:
+ (NSString *)myDefaultString
{
return #"Some default value";
}
You call it by simply calling it with the class name at the receiver's place. Imagine you have defined the method in a class called MyClass, the you call it like this:
NSString *str = [MyClass myDefaultString];
You'll notice that there is no alloc/init calls in this.
Public property needs to be define in .h file.
#interface MyClass {
}
#property(nonatomic, reatin) NSString *a;//Define as per needs, then synthesise in .m file
#end
For private property you need define inline category in .m file-
#interface MyClass ()
#property(nonatomic, reatin) NSString *b;//Define as per needs, then synthesise in .m file
#end
#implementation MyClass
#synthesize a = _a;
#synthesize b = _b;
- (void)viewDidLoad {
//You can initialise property here or in init method
self.a = #"Demo1";
self.b = #"Demo2";
}
//Now you can have other code for this class.
#end

In Objective-C, how do I pass a property as an argument for a function and then call the setter/getter methods?

The code is probably the best way to see what I am trying to do:
AcInfo.h:
#interface AcInfo : NSManagedObject {
#private
}
#property (nonatomic, retain) NSString *registrationNumber;
#end
AcInfo.m:
#implementation AcInfo
#dynamic registrationNumber;
#end
AnotherClass.h:
#interface AnotherClass : NSObject {
}
#property (nonatomic, retain) AcInfo *detailItem;
#property (nonatomic, retain) IBOutlet UITextField *registrationNumberTextField;
- (void)setDetailItemValueFromUIElement:(id *)uiElement forAcInfoTarget:(id *)acInfoTarget;
#end
AnotherClass.m:
#import "AcInfo.h"
#implementation AnotherClass
#synthesize detailItem, registrationNumberTextField;
- (void)viewDidLoad
{
[super viewDidLoad];
registrationNumberTextField.text = #"Test";
// I expect this to set detailItem.registrationNumber to the value of
// registrationNumberTextField.text (Test) but it doesn't change anything!
setDetailItemValueFromUIElement:registrationNumberTextField forAcInfoTarget:detailItem.registrationNumber;
}
- (void)setDetailItemValueFromUIElement:(id *)uiElement forAcInfoTarget:(id *)acInfoTarget
{
if ([(id)uiElement isKindOfClass:[UITextField class]]) {
// This doesn't do anything when it returns!
(NSString *)acInfoTarget = (UITextField *)uiElement.text
return;
}
}
#end
In short, I want acInfoTarget to call the getter [detailObject registrationNumber] and the setter [detailObject setRegistrationNumber] in the setDetailItemValueFromUIElement: function...
You can set or read properties by name using
// setter
NSString *propertyName = #"myProperty";
[object setValue:newValue forKey:propertyName];
// getter
id value = [object valueForKey:propertyName];
This is slower than using the normal dot notation, though, and it's frequently (though not always) a sign of poorly-designed code.
Also note that id is a pointer type, so you probably don't actually mean "(id*)".
Your code wants to look something like this, I think:
- (void)setDetailItemValueFromUIElement:(id)uiElement forAcInfoTarget:(NSString*)acInfoTarget {
if ([(id)uiElement isKindOfClass:[UITextField class]]) {
NSString *newValue = ((UITextField*)uiElement).text;
[self.detailItem setValue:newValue forKey:acInfoTarget];
}
}
Properties are just syntax sugar for a couple of accessor methods. They are not, in essence, variables so you shouldn't treat them as such. If you want to affect a property, then what you wanting to do is call a method. So you should pass a id and selector parameter and not pointer to a variable type.

setting new properties in category interface/implementation

Ok, so I have this, but it wont work:
#interface UILabel (touches)
#property (nonatomic) BOOL isMethodStep;
#end
#implementation UILabel (touches)
-(BOOL)isMethodStep {
return self.isMethodStep;
}
-(void)setIsMethodStep:(BOOL)boolean {
self.isMethodStep = boolean;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if(self.isMethodStep){
// set all labels to normal font:
UIFont *toSet = (self.font == [UIFont fontWithName:#"Helvetica" size:16]) ? [UIFont fontWithName:#"Helvetica-Bold" size:16] : [UIFont fontWithName:#"Helvetica" size:16];
id superView = self.superview;
for(id theView in [(UIView *)superView subviews])
if([theView isKindOfClass:[UILabel class]])
[(UILabel *)theView setFont:[UIFont fontWithName:#"Helvetica" size:16]];
self.font = toSet;
}
}
#end
If I take out the getter and setter methods then it doesn't work it tells me I need to create some getter and setter methods (or use #synthesize - but putting #synthesize in the #implementation throws an error too). But with the getter and setter methods I get an EXC_BAD_ACCESS and a crash. Any ideas? Thanks
Tom
It is not possible to add members and properties to an existing class via a category — only methods.
https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/Category.html
One possible workaround is to write "setter/getter-like" methods, that uses a singleton to save the variables, that would had been the member.
-(void)setMember:(MyObject *)someObject
{
NSMutableDictionary *dict = [MySingleton sharedRegistry];
[dict setObject:someObject forKey:self];
}
-(MyObject *)member
{
NSMutableDictionary *dict = [MySingleton sharedRegistry];
return [dict objectforKey:self];
}
or — of course — write a custom class, that inherits from UILabel
Note that nowadays an associated object can be injected during runtime. The Objective C Programming Language: Associative References
Checked all answers and did not find the most common solution:
#import <objc/runtime.h>
static void const *key;
#interface ClassName (CategoryName)
#property (nonatomic) BOOL myProperty;
#end
#implementation ClassName (CategoryName)
- (BOOL)myProperty {
return [objc_getAssociatedObject(self, key) boolValue];
}
- (void)setMyProperty:(BOOL)value {
objc_setAssociatedObject(self, key, #(value), OBJC_ASSOCIATION_RETAIN);
}
#end
swift:
private struct AssociatedKeys {
static var keyName = "keyName"
}
extension Foo {
var bar: Any! {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.keyName)
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.keyName , newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
There is actually a way, which may not be ideal, but does work.
For it to work, you will need to create a category for a class X and can only be used on subclasses of the same X (e.g. category UIView (Background) can be used with class MyView : UIView, but not directly with UIView)
// UIView+Background.h
#interface UIView (Background)
#property (strong, nonatomic) NSString *hexColor;
- (void)someMethodThatUsesHexColor;
#end
// UIView+Background.h
#implementation UIView (Background)
#dynamic hexColor; // Must be declared as dynamic
- (void)someMethodThatUsesHexColor {
NSLog(#"Color %#", self.hexColor);
}
#end
Then
// MyView.m
#import "UIView+Background.h"
#interface MyView : UIView
#property (strong, nonatomic) NSString *hexColor;
#end
#implementation MyView ()
- (void)viewDidLoad {
[super viewDidLoad];
[self setHexColor:#"#BABACA"];
[self someMethodThatUsesHexColor];
}
#end
Using this method, you will need to "redeclare" your properties, but after that, you can do all of its manipulation inside your category.
You could inject an associated object during runtime.
#import <objc/runtime.h>
#interface UIView (Private)
#property (nonatomic, assign) CGPoint initialTouchPoint;
#property (nonatomic, strong) UIWindow *alertWindow;
#end
#implementation UIView (Private)
#dynamic initialTouchPoint, alertWindow;
- (CGPoint)initialTouchPoint {
return CGPointFromString(objc_getAssociatedObject(self, #selector(initialTouchPoint)));
}
- (void)setInitialTouchPoint:(CGPoint)initialTouchPoint {
objc_setAssociatedObject(self, #selector(initialTouchPoint), NSStringFromCGPoint(initialTouchPoint), OBJC_ASSOCIATION_RETAIN);
}
- (void)setAlertWindow:(UIWindow *)alertWindow {
objc_setAssociatedObject(self, #selector(alertWindow), alertWindow, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIWindow *)alertWindow {
return objc_getAssociatedObject(self, #selector(alertWindow));
}
#end
EDIT: Warning: This property would have a unique value for all the instances of the class.
This worked for me, but only because I had only one instance of this class in my app.
#import <AVFoundation/AVFoundation.h>
#interface AVAudioPlayer (AstroAVAudioPlayer)
#property (nonatomic) BOOL redPilot;
#end
#import "AVAudioPlayer+AstroAVAudioPlayer.h"
#implementation AVAudioPlayer (AstroAVAudioPlayer)
BOOL _redPilot;
-(void) setRedPilot:(BOOL)redPilot
{
_redPilot = redPilot;
}
-(BOOL) redPilot
{
return _redPilot;
}
#end
A solution that I found to this was to just give each object that you want flagged a unique tag.
I made a UILabel category to add custom fonts to all my labels but on some i wanted them to be bold so i did this ->
- (void) layoutSubviews {
[super layoutSubviews];
[self addCustomFont];
}
- (void) addCustomFont {
if (self.tag == 22) {
[self setFont:[UIFont fontWithName:SEGOE_BOLD size:self.font.pointSize]];
}else{
[self setFont:[UIFont fontWithName:SEGOE_LIGHT size:self.font.pointSize]];
}
}
It seems as if since Xcode 7 (7.0.1, 7A1001), properties are supported in categories. I noticed that Xcode generates categories now for Core Data subclasses.
For example, I got the files:
Location+CoreDataProperties.h
#import "Location.h"
NS_ASSUME_NONNULL_BEGIN
#interface Location (CoreDataProperties)
#property (nullable, nonatomic, retain) NSNumber *altitude;
#property (nullable, nonatomic, retain) NSNumber *latitude;
#property (nullable, nonatomic, retain) NSNumber *longitude;
#end
NS_ASSUME_NONNULL_END
Location+CoreDataProperties.m
#import "Location+CoreDataProperties.h"
#implementation Location (CoreDataProperties)
#dynamic altitude;
#dynamic latitude;
#dynamic longitude;
#end
So looks like properties in categories might work now. I haven't tested on non-Core Data classes.
What I've noticed is that they do include the category file back into the original class:
Location.h
#interface Location : NSManagedObject
#end
#import "Location+CoreDataProperties.h"
This allows the original class to edit the properties specified by the category.