I am learning iOS programming and am confused by the following code regarding the use of keyword self.
From my understanding, self is like Java's this. It refers to the current instance. When I want to call a class method, the usual way should be like [PlayingCard validSuits]; But it's also OK to invade a class method on an instance, right? Like [self validSuits]; (I am in the class so self refers to an instance of PlayingCard)
But in the following code, it gives error somewhere but looks ok elsewhere.(Pointed out by 3 comments, this is within Xcode 5.1)
Am I missing anything?
(P.S. I think I am having the similar problem as here, which no one answered yet. He got the same error even using [PlayingCard validSuits]. )
// PlayingCard.m
#import "PlayingCard.h"
#implementation PlayingCard
#synthesize suit = _suit;
+ (NSArray *)validSuits {
return #[#"♠︎", #"♣︎", #"♥︎", #"♦︎"];
}
+ (NSArray *)rankStrings {
return #[#"?", #"A", #"2", #"3", #"4",#"5",#"6",#"7",#"8",#"9",#"10",#"J",#"Q",#"K"];
}
+ (NSUInteger)maxRank {
return [[PlayingCard rankStrings] count] -1;
//1. [self rankStrings] works fine.**
}
//override super class's method
- (NSString *)contents {
NSArray *rankStrings = [PlayingCard rankStrings];
//2. if change rankStrings to self, then error:
//No visible #interface for 'PlayingCard' declares the selector 'rankStrings'
return [rankStrings[self.rank] stringByAppendingString:self.suit];
}
- (void) setSuit:(NSString *)suit {
if ( [[PlayingCard validSuits] containsObject:suit]) {
//3.error when changed to [self validsuits]
//No visible #interface for 'PlayingCard' declares the selector 'validsuits'**
_suit = suit;
}
}
- (NSString *) suit {
return _suit ? _suit : #"?";
}
#end
The header file:
// PlayingCard.h
#import "Card.h"
#interface PlayingCard : Card
#property (nonatomic, strong) NSString *suit;
#property (nonatomic) NSUInteger rank;
+ (NSArray *) validSuits;
+ (NSUInteger) maxRank;
#end
If you are calling another class method from inside a class method (of the same class) you can just use [self classMethod]. If however you are in an instance method and you need to call that classes class method you can use [[self class] classMethod]
As pointed out by #Martin R - if you subclass PlayingCard, calling self in a class method will then be that subclass and not PlayingCard.
EDIT:
For completeness you need to do:
// PlayingCard.m
#import "PlayingCard.h"
#implementation PlayingCard
#synthesize suit = _suit;
+ (NSArray *)validSuits {
return #[#"♠︎", #"♣︎", #"♥︎", #"♦︎"];
}
+ (NSArray *)rankStrings {
return #[#"?", #"A", #"2", #"3", #"4",#"5",#"6",#"7",#"8",#"9",#"10",#"J",#"Q",#"K"];
}
+ (NSUInteger)maxRank {
return [[self rankStrings] count] -1;
}
//override super class's method
- (NSString *)contents {
NSArray *rankStrings = [[self class] rankStrings];
return [rankStrings[self.rank] stringByAppendingString:self.suit];
}
- (void) setSuit:(NSString *)suit {
if ( [[[self class] validSuits] containsObject:suit]) {
_suit = suit;
}
}
- (NSString *) suit {
return _suit ? _suit : #"?";
}
#end
Related
I'd like to implement KVO for an NSArray property that is declared as readonly. The getter for this readonly property returns a copy of the private NSMutableArray that backs the backs the public readonly one:
In my .h:
#interface MyClass : NSObject
#property (readonly, nonatomic) NSArray *myArray;
- (void)addObjectToMyArray:(NSObject *)obj;
- (void)removeObjectFromMyArray:(NSObject *)obj;
#end
And in my .m:
#interface MyClass()
#property (strong, nonatomic) NSMutableArray *myPrivateArray;
#end
#implementation MyClass
- (NSArray *)myArray {
return (NSArray *)[self.myPrivateArray copy];
}
- (void) addObjectToMyArray:(NSObject *)obj {
[self willChangeValueForKey:#"myArray"];
[self.myPrivateArray addObject:obj];
[self didChangeValueForKey:#"myArray"];
}
- (void) removeObjectToMyArray:(NSObject *)obj {
[self willChangeValueForKey:#"myArray"];
[self.myPrivateArray removeObject:obj];
[self didChangeValueForKey:#"myArray"];
}
#end
In my tests, I am seeing an exception thrown when I call didChangeValueForKey:. Is this the correct way to do this?
I recommend that you don't use a separate property for the mutable array. Instead, have the array property backed by a mutable array variable. Then, implement the indexed collection mutating accessors and make all changes to the array through those. KVO knows to hook into those accessors and emit change notifications. In fact, it can emit better, more specific change notifications that can allow observers to be more efficient in how they respond.
#interface MyClass : NSObject
#property (readonly, copy, nonatomic) NSArray *myArray;
- (void)addObjectToMyArray:(NSObject *)obj;
- (void)removeObjectFromMyArray:(NSObject *)obj;
#end
#interface MyClass()
// Optional, if you want to be able to do self.myArray = <whatever> in your implementation
#property (readwrite, copy, nonatomic) NSArray *myArray;
#end
#implementation MyClass
{
NSMutableArray *_myArray;
}
#synthesize myArray = _myArray;
// If you optionally re-declared the property read-write internally, above
- (void) setMyArray:(NSArray*)array {
if (array != _myArray) {
_myArray = [array mutableCopy];
}
}
- (void) insertObject:(id)anObject inMyArrayAtIndex:(NSUInteger)index {
[_myArray insertObject:anObject atIndex:index];
}
- (void) removeObjectFromMyArrayAtIndex:(NSUInteger)index {
[_myArray removeObjectAtIndex:index];
}
- (void) addObjectToMyArray:(NSObject *)obj {
[self insertObject:obj inMyArrayAtIndex:_myArray.count];
}
- (void) removeObjectToMyArray:(NSObject *)obj {
NSUInteger index = [_myArray indexOfObject:obj];
if (index != NSNotFound)
[self removeObjectFromMyArrayAtIndex:index];
}
#end
According to the KVO docs, https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOCompliance.html#//apple_ref/doc/uid/20002178-BAJEAIEE, you need to implement automaticallyNotifiesObserversForKey, something like
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:#"myArray"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
I have not tested this code, so apologies if I am on the wrong track.
This is fragile, and - (NSArray *)myArray keeps returning different values for the same array which KVO doe not like.
You'd be better of to define a private mutable array and a public read-only array. The when you make changes to the mutable array:
self.myPublicReadOnlyArray=self.myMutableArray.copy;
That way you can avoid all the will/has changed notifications because self.myPublicReadOnlyArray is KVC/KVO compliant.
I don't have terribly much experience with this, but I post this answer in hopes that it will either solve your issue or lead you to a solution. In the past I've used this:
-(void)viewDidLoad{
[self addObserver:self forKeyPath:kYoutubeObserverKey options:NSKeyValueObservingOptionNew context:nil];
}
-(void) addLocatedYoutubeURLToList:(NSString *)youtubeURL{
// -- KVO Update of Youtube Links -- //
[self willChangeValueForKey:kYoutubeObserverKey
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:[NSSet setWithObject:youtubeURL]];
[self.youtubeLinksSet addObject:youtubeURL];
[self didChangeValueForKey:kYoutubeObserverKey
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:[NSSet setWithObject:youtubeURL]];
}
kYoutubeObserverKey corresponds to:
static NSString * const kYoutubeObserverKey = #"youtubeLinksSet";
and I use a property of the same name in this class, hence the keyvalue name:
#property (strong, nonatomic) NSMutableSet * youtubeLinksSet;
I would add an observer for your key and specify what change your interested in observing. Additionally, I'd keep your key naming consistent, meaning that if you're updating the private key, then observe that private key, not the public one. When the observer detects a change in the private key, then have your public key update as a result of that. For example:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSNumber * keyValueChangeType = change[#"kind"];
if ([keyValueChangeType integerValue] == NSKeyValueChangeInsertion) {
if ([keyPath isEqualToString:kYoutubeObserverKey] ) {
//code and such...
}
}
}
Trying to override init method (to create instance with already initialized tag) in class and getting exception. Code sample:
#interface DiagnosticsReport : NSObject {
}
#property NSString *tag;
- (void) initWithTag:(NSString*) tag;
#end
#implementation DiagnosticsReport
- (id) initWithTag:(NSString*) tag {
if (self = [self init]) {
_tag = tag;
}
return self;
}
- (id) init {
if (self = [super init]) {
// default init here
}
return self;
}
Your declaration of method returns void, while definition returns id. Change both to return instancetype and you're good to go.
I am developing a digital magazine reader app and it requires to download magazines first.
While downloading them i want to pass download progress data between viewcontrollers.
That's why i am using singleton design pattern.
I also use NSNotification to update progressBar percentage while its downloading. But i do not think it is quite efficient to send notificiation in every milisecond. So i decided to use delegate design pattern but i don't know to implement a custom delagete method. Any help on custom delegate? And is it the best way to use delegate?
// Header
#interface ZXCSingleton : NSObject
+ (id)sharedInstance;
- (BOOL)isDownloadingProduct:(NSString *)productID;
- (void)addToDownloadListWithProductID:(NSString *)productID;
- (void)removeFromDownloadListWithProductID:(NSString *)productID;
- (NSArray *)getDownloadList;
- (void)setDownloadProgress:(float)progress
withProductID:(NSString *)productID;
- (float)getDownloadProgressWithProductID:(NSString *)productID;
#end
// M
#import "ZXCSingleton.h"
#implementation ZXCSingleton{
NSMutableArray *downloadList;
NSMutableDictionary *downloadProgress;
}
+ (id)sharedInstance
{
static ZXCSingleton *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance = [[ZXCSingleton alloc] init];
});
return sharedInstance;
}
- (id)init
{
self = [super init];
if (self) {
downloadList = [[NSMutableArray alloc] init];
downloadProgress = [[NSMutableDictionary alloc] init];
}
return self;
}
- (BOOL)isDownloadingProduct:(NSString *)productID
{
for (int i = 0; i < downloadList.count; i++) {
if ([[downloadList objectAtIndex:i] isEqualToString:productID]) return YES;
}
return NO;
}
- (void)addToDownloadListWithProductID:(NSString *)productID
{
[downloadList addObject:productID];
}
- (void)removeFromDownloadListWithProductID:(NSString *)productID
{
[downloadList removeObject:productID];
}
- (NSArray *)getDownloadList
{
return downloadList;
}
- (void)setDownloadProgress:(float)progress
withProductID:(NSString *)productID
{
if (progress != [[downloadProgress objectForKey:productID] floatValue]) [[NSNotificationCenter defaultCenter] postNotificationName:#"downloading" object:nil];
[downloadProgress setObject:[NSString stringWithFormat:#"%0.2f", progress] forKey:productID];
}
- (float)getDownloadProgressWithProductID:(NSString *)productID
{
return [[downloadProgress objectForKey:productID] floatValue];
}
Define a delegate by creating a protocol first for defining the interface required by the delegate :
#protocol ZXCSingletonProtocol
- (void)downloadProgessUpdates:(float)progress;
#end
Create a property for the delegate in your singleton :
#property (nonatomic, weak) id<ZXCSingletonProtocol> delegate;
Let your view controller conform to that protocol :
#interface MyViewController : UIViewController <ZXCSingletonProtocol>
and implement the downloadProgessUpdates in the view controller as required (e.g. update the UI element with the given progress number). REMEMBER to set the delegate when you initialize the view controller (viewDidLoad will do):
[ZXCSingleton sharedInstance].delegate = self; // self is the view controller instance
In your singleton update the setDownloadProgress method to update the delegate as well :
[_delegate downloadProgessUpdates:progress];
I have a shared singleton classNSMutableArray [ICGlobals sharedApplianceCount](first time using this pattern so bear with me if ive done something really silly here)
.h
#import <Foundation/Foundation.h>
#interface ICGlobals : NSObject
{
NSMutableArray* applianceCount;
}
#property (nonatomic, retain) NSString *applianceCount;
+ (ICGlobals *)sharedApplianceCount;
#end
.m
#import "ICGlobals.h"
#implementation ICGlobals
static ICGlobals *sharedApplianceCount = nil;
+ (ICGlobals *)sharedUser {
if(sharedApplianceCount == nil){
sharedApplianceCount = [[super allocWithZone:NULL] init];
}
return sharedApplianceCount;
}
+ (id)allocWithZone:(NSZone *)zone {
return [self sharedApplianceCount];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
#end
In "another view controller" im trying to add the row count of my table view (changeable amount of rows) = self.circuits.count
Having tried this
[[ICGlobals sharedApplianceCount] addObject: self.circuits.count,nil]];
and
[[ICGlobals sharedApplianceCount] = [[NSMutableArray alloc] init];
[[ICGlobals sharedApplianceCount] addObject: self.circuits.count,Nil]];
I get no visible #interface error saying my singleton class declares the selector
same with
NSNumber* numberOfRows = [NSNumber numberWithInteger:self.circuits.count];
[[ICGlobals sharedApplianceCount]addObject:[NSMutableArray arrayWithObjects:numberOfRows, nil]];
and with
[ICGlobals sharedApplianceCount] = self.circuits.count;
I get expression assignable. Singleton class has been imported.
You have an inconsistency in your interface declaration. You declare ivar of type NSMutableArray and then a NSString property. Firstable, you don't need to declare ivar, declaring a property does it for you. So your interface should look like:
#interface ICGlobals : NSObject
#property (nonatomic, retain) NSMutableArray *applianceCount;
+ (ICGlobals *)sharedApplianceCount;
#end
Furthermore, you have a naming glitch. You should not use name applianceCount for an array. In general, naming convention of Cocoa suggests that count should be a number (int or NSUInteger). I would change this property name to applianceCounts.
Then, when you initialize your singletone, you can also initialize the array:
+ (ICGlobals *)sharedUser
{
if(sharedApplianceCount == nil)
{
sharedApplianceCount = [[super allocWithZone:NULL] init];
sharedApplianceCount.applianceCounts = [[NSMutableArray alloc] init];
}
return sharedApplianceCount;
}
Finally, here is how to add data to your singletone's applianceCounts array from view controller.
NSNumber* numberOfRows = [NSNumber numberWithInteger:self.circuits.count];
[[ICGlobals sharedApplianceCount].applianceCounts addObject:numberOfRows];
This should point you to right direction.
I don't fully get what you are trying to achieve like I don't understand why you want to have an array there, so if you need further help please let me know in the comments.
I fully recommend you reading about naming conventions. A good start is this article:
Introduction to Coding Guidelines for Cocoa.
I would recommend some refactoring to your class.
First you make the interface like this:
#interface ICGlobals : NSObject
// add the app count but make it private, because you will provide methods to access it
#property (nonatomic, readonly) NSString *applianceCount;
// return ICGlobals instance
+ (ICGlobals)sharedCounter;
- (NSInteger)count;
- (void)addObject:(id)object;
now in .m file
#implementation ICGlobals
static ICGlobals *sharedApplianceCount = nil;
// this is your method, just changed the name
+ (ICGlobals *)sharedCounter {
if(sharedApplianceCount == nil){
sharedApplianceCount = [[super allocWithZone:NULL] init];
}
return sharedApplianceCount;
}
// instance methods goes here
- (NSInteger)count
{
return _applicationCount.count;
}
- (void)addObject:(id)object
{
[_applicationCount addObject:object];
}
Now call [[ICGlobals sharedCount]addObject:object] from any viewController
I want to override an NSString property declared in a superclass. When I try to do it using the default ivar, which uses the the same name as the property but with an underscore, it's not recognised as a variable name. It looks something like this...
The interface of the superclass(I don't implement the getter or setter in this class):
//Animal.h
#interface Animal : NSObject
#property (strong, nonatomic) NSString *species;
#end
The implementation in the subclass:
//Human.m
#implementation
- (NSString *)species
{
//This is what I want to work but it doesn't and I don't know why
if(!_species) _species = #"Homo sapiens";
return _species;
}
#end
Only the superclass has access to the ivar _species. Your subclass should look like this:
- (NSString *)species {
NSString *value = [super species];
if (!value) {
self.species = #"Homo sapiens";
}
return [super species];
}
That sets the value to a default if it isn't currently set at all. Another option would be:
- (NSString *)species {
NSString *result = [super species];
if (!result) {
result = #"Home sapiens";
}
return result;
}
This doesn't update the value if there is no value. It simply returns a default as needed.
to access the superclass variables, they must be marked as #protected, access to such variables will be only inside the class and its heirs
#interface ObjectA : NSObject
{
#protected NSObject *_myProperty;
}
#property (nonatomic, strong, readonly) NSObject *myProperty;
#end
#interface ObjectB : ObjectA
#end
#implementation ObjectA
#synthesize myProperty = _myProperty;
#end
#implementation ObjectB
- (id)init
{
self = [super init];
if (self){
_myProperty = [NSObject new];
}
return self;
}
#end