Unexpected result of property `copy` attribute - objective-c

I've implemented NSCopying for own class and when I use this class as a property with copy attribute I'm expecting that it should use [... copy]/[... copyWithZone:] method. But it returns a reference to the same object.
But if I use copy attribute for NSString it works or when I directly call copy method.
My question why copy attribute is not working for own class that supports NSCopying protocol?
#interface A: NSObject<NSCopying>
#property(nonatomic, strong) NSNumber *num;
#end
#implementation A
- (instancetype) init {
if(self = [super init]) {
_num = #0;
}
return self;
}
- (instancetype)copyWithZone:(NSZone *)zone {
A *newA = [A new];
newA.num = [self.num copyWithZone:zone];
return newA;
}
#end
#interface B: NSObject
#property(nonatomic, copy) NSString *str;
#property(nonatomic, copy) A *objA;
#end
#implementation B
- (instancetype) init {
if(self = [super init]) {
_objA = [A new];
_str = #"0";
}
return self;
}
#end
int main(int argc, const char * argv[]) {
B *objB = [B new];
A *newA1 = objB.objA.copy;
newA1.num = #1;
NSLog(#"%# %#", newA1.num, objB.objA.num);
A *newA = objB.objA;
NSString *newStr = objB.str;
newA.num = #1;
newStr = #"1";
NSLog(#"%# %#", newA.num, objB.objA.num);
NSLog(#"%# %#", newStr, objB.str);
return 0;
}
Output:
1 0
1 1
1 0
Expected output:
1 0
1 0
1 0

Related

Is there any way to emulate user's input in XCTest function?

I have method which expects user's input
#implementation TeamFormation
- (void)run {
NSFileHandle *kbd = [NSFileHandle fileHandleWithStandardInput];
NSData *inputData = [kbd availableData];
NSString *option = [[[NSString alloc] initWithData:inputData
encoding:NSUTF8StringEncoding] substringToIndex:1];
NSLog(#"%#",option);
}
#end
Then I would like to cover this method by a test case
#interface TeamFormationTests : XCTestCase
#end
#implementation TeamFormationTests
- (void)testTeamFormation {
TeamFormation *teamFormation = [TeamFormation new];
[teamFormation run];
// emulate user's input here
}
#end
So, how to emulate user's input in test case function?
You have many options how to achieve this. Two obvious below.
Change run to accept an argument
- (void)run to - (void)runWithFileHandle:(NSFileHandle *)handle
your app code can pass stdin filehandle
your test code can pass handle to a file with desired input
Mock it with protocol
Create DataProvider protocol:
#protocol DataProvider
#property(readonly, copy) NSData *availableData;
#end
Make NSFileHandle to conform to this protocol:
#interface NSFileHandle (AvailableDataProvider) <DataProvider>
#end
Store an object implementing this protocol on TeamFormation:
#interface TeamFormation : NSObject
#property (nonatomic, nonnull, strong) id<DataProvider> dataProvider;
- (NSString *)run;
#end
By default, use stdin file handle:
#implementation TeamFormation
- (instancetype)init {
if ((self = [super init]) == nil) {
return nil;
}
_dataProvider = [NSFileHandle fileHandleWithStandardInput];
return self;
}
- (NSString *)run {
NSData *inputData = [self.dataProvider availableData];
return [[[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding] substringToIndex:1];
}
#end
Create TestDataProvider in your test:
#interface TestDataProvider: NSObject<DataProvider>
#property (nonatomic, strong, nonnull) NSData *dataToProvide;
#end
#implementation TestDataProvider
- (instancetype)init {
if ((self = [super init]) == nil) {
return nil;
}
_dataToProvide = [NSData new];
return self;
}
- (NSData *)availableData {
return _dataToProvide;
}
#end
And use it in TestFormationTests:
#implementation TeamFormationTests
- (void)testFormationRun {
TestDataProvider *dataProvider = [TestDataProvider new];
TeamFormation *formation = [TeamFormation new];
formation.dataProvider = dataProvider;
XCTAssertThrows([formation run]);
dataProvider.dataToProvide = [#"foo" dataUsingEncoding:NSUTF8StringEncoding];
XCTAssertEqualObjects([formation run], #"f");
dataProvider.dataToProvide = [#"bar" dataUsingEncoding:NSUTF8StringEncoding];
XCTAssertEqualObjects([formation run], #"b");
}
#end

Why weak property of associated object is not nilled out if I call its getter?

Though it's kind of stupid in 2020 that I'm still asking question about ObjC, please be patient and considerate...
I'm reading the source code of BloksKit and ran into a weird situation.
#import <objc/runtime.h>
#interface _WeakAssociatedObjectWrapper : NSObject
#property (nonatomic, weak) id object;
#end
#implementation _WeakAssociatedObjectWrapper
#end
#interface NSObject (AddWeak)
#end
#implementation NSObject (AddWeak)
- (void)setWeakProp:(id)weakProp {
_WeakAssociatedObjectWrapper *wrapper = objc_getAssociatedObject(self, #selector(weakProp));
if (!wrapper) {
wrapper = [[_WeakAssociatedObjectWrapper alloc] init];
objc_setAssociatedObject(self, #selector(weakProp), wrapper, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
wrapper.object = weakProp;
}
- (id)weakProp {
id value = objc_getAssociatedObject(self, _cmd);
if ([value isKindOfClass:_WeakAssociatedObjectWrapper.class]) {
return [(_WeakAssociatedObjectWrapper *)value object];
}
return value;
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
{
NSObject *prop = [[NSObject alloc] init];
[obj setWeakProp:prop];
[obj weakProp]; // *Weird!!
}
NSLog(#"Now obj.weakProp = %#", [obj weakProp]);
}
return 0;
}
This code is adding a weak associated object for category.(BlocksKit does so)
Note the *Weird!! line. If this line is commented out, then it prints (null), which is reasonable since prop is deallocated outside the {} scope. On the other side, if not commented out, it prints <NSObject: 0xxxxx>, which indicates that prop is somehow retained by someone(Or any other reason?).
What is happening here??! (BlocksKit behaves the same!)
Environment: XCode 10.3
This is a feature. For the case (and any similar)
[obj weakProp];
by properties/accessors naming convention ARC returns autoreleased instance, so in your case #autoreleasepool holds it and testing as below can show this.
int main(int argc, const char * argv[]) {
NSObject *obj = [[NSObject alloc] init];
#autoreleasepool {
{
NSObject *prop = [[NSObject alloc] init];
[obj setWeakProp:prop];
[obj weakProp]; // *Weird!!
}
NSLog(#"Now obj.weakProp = %#", [obj weakProp]);
}
NSLog(#"After autoreleased >> obj.weakProp = %#", [obj weakProp]);
return 0;
}

Get NSMutableDictionary from Singleton?

I created a singleton class in order to share an object inside my program. Here's the code:
SelectedRow.h
#import <Foundation/Foundation.h>
#import "TableEntry.h"
#interface SelectedRow : NSObject {
TableEntry *rowValue;
}
#property (nonatomic, retain) TableEntry *rowValue;
+ (id)sharedManager;
- (void)setVariable:(TableEntry*)value;
#end
and SelectedRow.m
#import "SelectedRow.h"
#import "TableEntry.h"
#implementation SelectedRow
#synthesize rowValue;
+ (id)sharedManager {
static SelectedRow *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
- (id)init {
if (self = [super init]) {
rowValue = [[TableEntry alloc] init];
}
return self;
}
- (void)setVariable:(TableEntry*)value {
rowValue = value;
}
#end
while TableEntry.h
#import <Foundation/Foundation.h>
#interface TableEntry : NSObject {
#private
NSString *videoId;
NSString *videoCategory;
NSString *videoTitle;
NSString *videoDescription;
NSDate *videoDate;
NSMutableArray *videoRelatedVideos;
NSDictionary *videoAdditionalInformation;
NSString *videoAccessControl;
NSArray *videoFields;
NSMutableDictionary *days;
NSMutableDictionary *views;
NSMutableDictionary *watchtime;
NSMutableDictionary *subscribers;
NSMutableDictionary *shares;
}
#property (copy) NSString *videoId;
#property (copy) NSString *videoCategory;
#property (copy) NSString *videoTitle;
#property (copy) NSString *videoDescription;
#property (copy) NSMutableArray *videoRelatedVideos;
#property (copy) NSDictionary *videoAdditionalInformation;
#property (copy) NSArray *videoFields;
#property (copy) NSString *videoAccessControl;
#property (copy) NSDate *videoDate;
#property (copy) NSMutableDictionary *days;
#property (copy) NSMutableDictionary *views;
#property (copy) NSMutableDictionary *subscribers;
#property (copy) NSMutableDictionary *shares;
#property (copy) NSMutableDictionary *watchtime;
- (id)setId:(NSString*)Id setCategory:(NSString*)Category setDate:(NSDate*)date setTitle:(NSString*)title setDescription:(NSString*)description setRelatedVideos:(NSMutableArray*)relatedVideos setAdditionalInformation:(NSDictionary*)additionalInformation setAccessControl:(NSString*)accessControl setFields:(NSArray*)fields setDays:(NSMutableDictionary*)days setViews:(NSMutableDictionary*)views setSubscribers:(NSMutableDictionary*)subscribers setShares:(NSMutableDictionary*)shares setWatchtime:(NSMutableDictionary*)watchtime;
- (NSString*)extractId;
- (NSString*)extractCategory;
- (NSString*)extractTitle;
- (NSString*)extractDescription;
- (NSMutableArray*)extractRelatedVideos;
- (NSDictionary*)extractAdditionalInformationVideos;
- (NSDictionary*)extractAccessControlVideos;
- (NSArray*)extractFields;
- (NSMutableDictionary*)extractDays;
- (NSMutableDictionary*)extractViews;
- (NSMutableDictionary*)extractSubscribers;
- (NSMutableDictionary*)extractShares;
- (NSMutableDictionary*)extractWatchtime;
#end
and TableEntry.m
- (id)init {
self = [super init];
if (self) {
videoId = #"9bZkp7q19f0";
videoCategory = #"Music";
videoTitle = #"Demo Title";
videoDescription = #"Demo description";
videoDate = [NSDate date];
videoAdditionalInformation = [NSDictionary alloc];
videoRelatedVideos = [NSMutableArray alloc];
videoAccessControl = #"demo accesControl";
videoFields = [NSArray alloc];
days = [NSMutableDictionary alloc];
views = [NSMutableDictionary alloc];
shares = [NSMutableDictionary alloc];
subscribers = [NSMutableDictionary alloc];
watchtime = [NSMutableDictionary alloc];
}
return self;
}
- (id)setId:(NSString*)Id setCategory:(NSString*)Category setDate:(NSDate*)date setTitle:(NSString*)title setDescription:(NSString*)description setRelatedVideos:(NSMutableArray*)relatedVideos setAdditionalInformation:(NSDictionary*)additionalInformation setAccessControl:(NSString*)accessControl setFields:(NSArray*)fields setDays:(NSMutableDictionary*)Days setViews:(NSMutableDictionary*)Views setSubscribers:(NSMutableDictionary*)Subscribers setShares:(NSMutableDictionary*)Shares setWatchtime:(NSMutableDictionary*)Watchtime {
videoId = Id;
videoCategory = Category;
videoDate = date;
videoTitle = title;
videoDescription = description;
videoRelatedVideos = relatedVideos;
videoAccessControl = accessControl;
videoAdditionalInformation = additionalInformation;
videoFields = fields;
days = Days;
views = Views;
subscribers = Subscribers;
watchtime = Watchtime;
shares = Shares;
return self;
}
- (NSString*)extractId {
return self.videoId;
}
- (NSString*)extractCategory{
return self.videoCategory;
}
- (NSString*)extractTitle{
return self.videoTitle;
}
- (NSString*)extractDescription{
return self.videoDescription;
}
- (NSMutableArray*)extractRelatedVideos{
return self.videoRelatedVideos;
}
- (NSString*)extractAccessControlVideos{
return self.videoAccessControl;
}
- (NSDictionary*)extractAdditionalInformationVideos{
return self.videoAdditionalInformation;
}
- (NSArray*)extractFields{
return self.videoFields;
}
- (NSMutableDictionary*)extractDays{
return self.days;
}
- (NSMutableDictionary*)extractSubscribers{
return self.subscribers;
}
- (NSMutableDictionary*)extractWatchtime{
return self.watchtime;
}
- (NSMutableDictionary*)extractShares{
return self.shares;
}
- (NSMutableDictionary*)extractViews{
return self.views;
}
#end
I can extract any values from the singleton with:
SelectedRow *selectedRow = [SelectedRow sharedManager];
NSString *videoID = [selectedRow.rowValue extractId];
the problem arises with any NSMutableDictionary. If I try:
SelectedRow *selectedRow = [SelectedRow sharedManager];
NSMutableDictionary *days = [selectedRow.rowValue extractDays];
or with any other NSMutableDictionary I get this error:
[NSMutableDictionary count]: method sent to an uninitialized mutable dictionary object
what I'm I doing wrong? Thanks
The [NSMutableDictionary alloc] call allocates the space for NSMutableDictionary, but it does not initialize it.
Replace it with [NSMutableDictionary dictionary] to fix the problem. Same goes for your NSArray and NSMutableArray objects (replace them with [NSMutable array] and [NSMutableArray array]).
The videoAdditionalInformation of type NSDictionary should be initialized to nil, though, because NSDictionary objects are immutable. If you are planning to set it to some dictionary later on, you might as well keep it nil on initialization.
In addition, you should reconsider the use of copy: it makes sense for NSString objects, but it hardly makes sense on NSMutableDictionary objects.

Calling a class method from a View gives: "Unrecognized selector sent to class"

I'm trying to call a class method that is defined in an imported header file.
When I run the code below, I get this error in the View on the "double *result = ..." line:
+[CalculatorBrain runProgram:usingVariableValues:]: unrecognized selector sent to class 0x6908
** CalculatorViewController.m **
#import "CalculatorViewController.h"
#import "CalculatorBrain.h"
#interface CalculatorViewController()
#property (nonatomic, strong) CalculatorBrain *brain;
#property (nonatomic, strong) NSMutableDictionary *variableValues;
#end
#implementation CalculatorViewController
#synthesize brain = _brain;
#synthesize variableValues = _variableValues;
- (CalculatorBrain *)brain {
if (!_brain) _brain = [[CalculatorBrain alloc] init];
return _brain;
}
- (NSMutableDictionary *)variableValues {
if (!_variableValues) {
_variableValues = [[NSMutableDictionary alloc] init];
}
return _variableValues;
}
- (IBAction)enterPressed {
double *result = [CalculatorBrain runProgram:[self.brain program] usingVariableValues:[self variableValues]];
}
** CalculatorBrain.h **
#import <UIKit/UIKit.h>
#interface CalculatorBrain : NSObject
+ (double *)runProgram:(id)program usingVariableValues:(NSDictionary *)variableValues;
#property (readonly) id program;
#end
** CalculatorBrain.m **
#import "CalculatorBrain.h"
#interface CalculatorBrain()
#property (nonatomic, strong) NSMutableArray *programStack;
#end
#implementation CalculatorBrain
#synthesize programStack = _programStack;
... other code ...
+ (double)runProgram:(id)program :(NSDictionary *) usingVariableValues
{
NSLog(#"variableValues is %#", usingVariableValues);
NSMutableArray *stack;
if ([program isKindOfClass:[NSArray class]]) {
stack = [program mutableCopy];
NSLog(#"runProgram");
// if vars are passed in
if ([usingVariableValues isKindOfClass:[NSDictionary class]]) {
NSLog(#"vars are passed in: %#", usingVariableValues);
id obj;
int index = 0;
NSEnumerator *enumerator = [program objectEnumerator];
// for every obj in programStack
while ((obj = [enumerator nextObject])) {
id varVal = [usingVariableValues objectForKey:(obj)];
// test
NSLog(#"usingVariableValues objectForKey:(obj) is %#", varVal);
// if the obj is a variable key
if (!varVal) {
varVal = 0;
NSLog(#"varVal is false");
}
NSLog(#"Replacing object at index %# of stack with var %#", index, varVal);
// replace the variable with value from usingVariableValues OR 0
[stack replaceObjectAtIndex:(index) withObject:varVal];
index += 1;
}
}
}
return [self popOperandOffStack:stack];
}
+ (double *)runProgram:(id)program usingVariableValues:(NSDictionary *)variableValues;
Is defined as a class method, but you call it as an object method
double *result = [self.brain runProgram:[self.brain program] usingVariableValues:[self variableValues]];
To call it on the class do:
double *result = [[self.brain class] runProgram:[self.brain program] usingVariableValues:[self variableValues]];
Or
double *result = [CalculatorBrain runProgram:[self.brain program] usingVariableValues:[self variableValues]];
You changed your code, indicating, that the method is still not found. Did you implement it?
If it is implemented, then you might have to add the implementation file (aka .m) to the target in Xcode.
By the way,: probably you want your method to return a double not a double*, a pointer to a double.
your header has a signature:
+ (double *)runProgram:(id)program usingVariableValues:(NSDictionary *)variableValues;
While your implementation has
+ (double)runProgram:(id)program :(NSDictionary *) usingVariableValues
They are not identical:
The header promisses a pointer to a double to be returned. You don't want that.
They don't even have the same name
+runProgram:usingVariableValues: vs +runProgram::

Set readonly attribute in ObjC

Is there a way to set a value to readonly attribute in Objective-C?
I actually don't care how nasty the code is unless it isn't stable anymore.
Never mind my comment, here's the two ways you do it:
#interface Grimley : NSObject
#property (readonly, copy) NSString * blabber;
#property (readonly, copy) NSString * narwhal;
- (id) initWithBlabber:(NSString *)newBlabber;
#end
#implementation Grimley
#synthesize blabber;
#synthesize narwhal = unicorn;
- (id) initWithBlabber:(NSString *)newBlabber {
self = [super init];
if( !self ) return nil;
// Any object can of course set its own ivar regardless
// of how the property it backs is declared.
blabber = [newBlabber copy];
// Refer to the _ivar_, not the property.
unicorn = #"One horn";
return self;
}
#end
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Grimley * g = [[Grimley alloc] initWithBlabber:#"Excelsior"];
// This is how you get around the property.
[g setValue:#"Nimitz" forKey:#"blabber"];
// Again, use the name of the variable, not the property
[g setValue:#"Pearly horn" forKey:#"unicorn"];
NSLog(#"%#", [g blabber]);
NSLog(#"%#", [g narwhal]);
[g release];
[pool drain];
return 0;
}