Objective-C and NSCoding primative data type just like the old C code - objective-c

I have some old C code that looks like this:
#include <stdio.h>
#include <strings.h>
main()
{
FILE *output;
struct socket_cpacket
{
char type; /* CP_SOCKET */
char version;
char udp_version; /* was pad2 */
char pad3;
unsigned socket;
};
struct socket_cpacket sockPack;
bzero(&sockPack,sizeof(sockPack));
sockPack.type = 27;
sockPack.version = 4;
sockPack.udp_version = 10;
sockPack.pad3 = 0;
sockPack.socket = 0;
output = fopen("/tmp/sockPack.bin", "wb");
fwrite(&sockPack, sizeof(sockPack), 1, output);
}
I'd like to duplicate this functionality in obj-c and I started down the path of using NSCoding protocol.
CP_Socket.h
#import <Foundation/Foundation.h>
#interface CP_Socket : NSObject <NSCoding>
{
#private
char type;
char version;
char udp_version;
char pad3;
unsigned int socket;
}
#property (readonly) char type;
#property (readonly) char version;
#property (readonly) char udp_version;
#property (readonly) char pad3;
#property unsigned int socket;
typedef enum {
mTYPE = 27,
mVERSION = 4,
mUDP_VERSION = 10,
} cpSocketEnum;
#end
And CP_Socket.m
#import "CP_Socket.h"
#implementation CP_Socket
#pragma mark ======== properties =========
#synthesize type;
#synthesize version;
#synthesize udp_version;
#synthesize pad3;
#synthesize socket;
- (id)init {
NSLog(#"init");
if( !( self = [super init] ) )
return nil;
type = mTYPE;
version = mVERSION;
udp_version = mUDP_VERSION;
pad3 = 0;
socket = 0;
return self;
}
#pragma mark ======== Archiving and unarchiving methods =========
//
// Archives and Serializations Programming Guide for Cocoa
// http://bit.ly/PAaRsV
//
// NSCoding Protocol Reference
// http://bit.ly/PAb1Rd
//
- (void)encodeWithCoder:(NSCoder *)coder
{
NSLog(#"encodeWithCoder");
[coder encodeBytes:[self type] length:1 forKey:#"type"];
//[coder encodeBytes:[self version] length:1 forKey:#"version"];
//[coder encodeBytes:[self udp_version] length:1 forKey:#"udp_version"];
//[coder encodeBytes:[self pad3] length:1 forKey:#"pad3"];
//[coder encodeInt:[self socket] forKey:#"socket"];
}
- (id)initWithCoder:(NSCoder *)coder
{
NSLog(#"initWithCoder");
}
#end
First problem, [coder encodeBytes:[self type] length:1 forKey:#"type"]; throws a warning. Incompatible integer to pointer conversion sending 'char' to parameter of type 'const uint8_t *.
How do I encode a char?
I tried [coder encodeInt:[self type] forKey:#"type"]; but char != int.
Going with the code to further understand how it work; the file the obj-c code generates is 280 bytes and looking inside the file I see what looks like name-mangled class identifiers.
I've tried NSKeyedArchiver and NSArchiver with the same results.
I don't know where to go from here. The C-code generates a 8 byte file. I'd like the obj-c code to do the same while using some of the OO stuff like the NSCoding protocol.
I feel like I'm going to have to extend the NSCoder object to make this work.
Any help would be appreciated.

The first argument to encodeBytes:length:forKey: is expected to be a pointer to the buffer you want to encode, so take the address of your ivar:
[coder encodeBytes:&type length:1 forKey:#"type"];
Or make a temp variable, put the result of the property access in that, and take its address.
Using encodeInt:forKey: should work too (with a cast), but it'll inflate the size of your data file.
If you really wanted to, you could certainly extend NSCoder with a category:
#implementation NSCoder (BTEncodeChar)
- (void)BTencodeChar: (char)c forKey: (NSString *)key
{
[self encodeBytes:&c length:1 forKey:key];
}
#end

I dont know much about NSCoding, but obj C interops with C just fine. Take your existing code put it in a function with params and call it.

Related

ObjectC-Why can't I get the properties correctly using the class_copyPropertyList function?

macOS 11.5.2
Xcode 13.2.1
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <iostream>
int main(int argc, const char * argv[]) {
#autoreleasepool {
Class clazz = NSClassFromString(#"NSString");
uint32_t count = 0;
objc_property_t* properties = class_copyPropertyList(clazz, &count);
for (uint32_t i = 0; i < count; i++){
const char* name = property_getName(properties[i]);
std::cout << name << std::endl;
}
free(properties);
}
return 0;
}
I will take some snippets of the output:
hash
superclass
description
debugDescription
hash
superclass
description
debugDescription
vertexID
sha224
NS_isSourceOver
hash
superclass
description
debugDescription
...
From the output, we can find that properties such as hash, description, superclass, etc. will appear repeatedly several times, while some properties (such as UTF8String) do not appear in the result list.
How should I get the list of properties correctly?
I would appreciate it.
The reason you're not seeing UTF8String come up as a property is that it's not declared as a property in the main declaration of NSString, but rather in a category. On macOS 12.2.1/Xcode 13.2.1, the declaration of NSString boils down to this:
#interface NSString : NSObject <NSCopying, NSMutableCopying, NSSecureCoding>
#property (readonly) NSUInteger length;
- (unichar)characterAtIndex:(NSUInteger)index;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
#end
All other properties and methods on NSString are declared in categories immediately afterwards:
#interface NSString (NSStringExtensionMethods)
#pragma mark *** Substrings ***
/* To avoid breaking up character sequences such as Emoji, you can do:
[str substringFromIndex:[str rangeOfComposedCharacterSequenceAtIndex:index].location]
[str substringToIndex:NSMaxRange([str rangeOfComposedCharacterSequenceAtIndex:index])]
[str substringWithRange:[str rangeOfComposedCharacterSequencesForRange:range]
*/
- (NSString *)substringFromIndex:(NSUInteger)from;
- (NSString *)substringToIndex:(NSUInteger)to;
// ...
#property (nullable, readonly) const char *UTF8String NS_RETURNS_INNER_POINTER; // Convenience to return null-terminated UTF8 representation
// ...
#end
When a property is declared in a category on a type like this, it doesn't get emitted as an actual Obj-C property because categories can only add methods to classes, and not instance variables. When a category declares a property on a type, it must be backed by a method and not a traditional property.
You can see this with a custom class, too — on my machine,
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface MyClass: NSObject
#property (nullable, readonly) const char *direct_UTF8String NS_RETURNS_INNER_POINTER;
#end
#interface MyClass (Extensions)
#property (nullable, readonly) const char *category_UTF8String NS_RETURNS_INNER_POINTER;
#end
#implementation MyClass
- (const char *)direct_UTF8String {
return "Hello, world!";
}
- (const char *)category_UTF8String {
return "Hi there!";
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
Class clazz = NSClassFromString(#"MyClass");
printf("%s properties:\n", class_getName(clazz));
uint32_t count = 0;
objc_property_t* properties = class_copyPropertyList(clazz, &count);
for (uint32_t i = 0; i < count; i++){
printf("%s\n", property_getName(properties[i]));
}
free(properties);
puts("-----------------------------------------------");
printf("%s methods:\n", class_getName(clazz));
Method *methods = class_copyMethodList(clazz, &count);
for (uint32_t i = 0; i < count; i++) {
SEL name = method_getName(methods[i]);
printf("%s\n", sel_getName(name));
}
free(methods);
}
return 0;
}
outputs
MyClass properties:
direct_UTF8String
-----------------------------------------------
MyClass methods:
direct_UTF8String
category_UTF8String
If you remove the actual implementations of the *UTF8String methods from the class, the property remains declared, but the category method disappears (because it doesn't actually have a synthesized implementation because of how categories work):
MyClass properties:
direct_UTF8String
-----------------------------------------------
MyClass methods:
direct_UTF8String
As for how to adjust to this: it depends on what purpose you're trying to fetch properties for, and why you might need UTF8String specifically.
NSString declares in its interface it implements methods, but it does not actually implement them, that is why when you print at runtime a list of the its methods it does not print what you expect.
The methods are implemented by other private classes, and when you initialize a new instance of NSString, instead of getting an instance of NSString you get an instance of that private class that have the actual implementation.
You can see that by printing the class type of a string, the following prints NSCFString or NSTaggedPointerString, not NSString:
NSString* aString = [NSString stringWithFormat: #"something"];
NSLog(#"%#", [aString class]);
And this prints __NSCFConstantString:
NSLog(#"%#", [#"a constant string" class]);
This pattern is called a class cluster pattern.
If you modify to dump the methods of the NSCFString you will get a "redactedDescription", it seems you are prevented to query these classes.

How to input new instance of Object with multiple arguments into NSMutablearray

I'm very new to Obj-C, been learning more Java and C++ lately.
I have two objects Friend and Foe which inherit the Character Object. Friend and Foe have slightly different attributes. I want all Friends and Foes to be in the same NSMutablearray. Can't figure out how to put these into the array. I get an error saying too many arguments, expected 1 have 4. For Foe its the same, but expected 1, have 5.
The Character Object
#import <foundation/foundation.h>
#interface Character : NSObject
#property NSString *name;
#property NSInteger strength;
#property NSInteger iff;
- (void) printDetails;
#end
#import <Foundation/Foundation.h>
#import "game_character.h"
#implementation Character
- (void) printDetails
{
NSLog (#"%# has strength %ld\n", self.name, self.strength);
}
#end
The Friend Object (The Foe object is similar with without intelligence and spell but has an alternate NSInteger attribute.
#interface Friend : Character
#property NSInteger intelligence;
#property NSString *spell;
- (void)printDetails;
#end
#import <Foundation/Foundation.h>
#import "game_character.h"
#import "friend.h"
#implementation Friend
-(void)printDetails
{
NSLog (#"%# has strength %ld\n", self.name, self.strength);
NSLog (#" ,Intelligence %ld, Spell %#\n", self.intelligence, self.spell);
}
#end
The Friend Input Method (I will have a similar method to input a Foe)
void input_friend()
{
#autoreleasepool
{
char str[30] = {0};
NSInteger strength;
NSInteger iff=1;
NSInteger intelligence;
NSLog(#"Enter character name\n");
scanf("%s", str);
NSString *name = [NSString stringWithUTF8String:str];
NSLog(#"Enter character strength\n");
scanf("%ld", &strength);
NSLog(#"Enter character intelligence");
scanf("%ld", &intelligence);
NSLog(#"Enter character spell\n");
scanf("%s", str);
NSString *spell = [NSString stringWithUTF8String:str];
My Error is here when I try to add the object to the array.
[characters addObject:name, strength, iff, intelligence, spell];
}
}
The Main so far. I intend to add a menu with option to add Friend or Foe to the array.
int main(int argc, const char * argv[])
{
#autoreleasepool
{
characters = [[NSMutableArray alloc] init];
void input_friend();
void input_foe();
}
return 0;
}
In this line, you are passing multiple arguments to add to your list of characters. However, this doesn't work since you want to add objects, which is why you got your error:
[characters addObject:name, strength, iff, intelligence, spell];
So you need to initialize a new Friend or Foe first, set its properties, and then add it to your array.
Friend *newFriend = [[Friend alloc] init];
newFriend.name = name;
newFriend.strength = strength;
// etc.
[characters addObject: newFriend];

Making pointer from integer in Objective C

After programming with C# and Java for many years I finally decided to learn Objective-C in order to start programming iOS Devices as well as Mac OS X, and I have to admit it is very different then most modern c-based programming languages. I am getting the following warning in my code:
warning: passing argument 1 of 'SetAge' makes pointer from integer without a cast
Here is my code:
Dog.h
#import <Cocoa/Cocoa.h>
#interface Dog : NSObject {
int ciAge;
NSString * csName;
}
- (void) Bark;
- (void) SetAge: (int *) liAge;
- (void) SetName: (NSString *) lsName;
#end
Dog.m
#import "Dog.h"
#implementation Dog
- (void) Bark
{
NSLog(#"The dog %# barks with age %d", csName, ciAge);
}
- (void) SetAge: (int *) liAge {
ciAge = (int)liAge;
}
- (void) SetName: (NSString *) lsName {
csName = lsName;
}
#end
HelloWorld.m
#import <Foundation/Foundation.h>
#import "Dog.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int liTemp = 75;
NSString * lsCity = #"Chicago";
NSDate * loDate = [NSDate date];
// insert code here...
NSLog(#"The temperature is %d currently in %#, on %#", liTemp, lsCity, loDate);
int liAge = 10;
// Call Dog class
Dog * Dog1 = [Dog new];
[Dog1 SetAge:(int)liAge]; // The Warning happens here
[Dog1 SetName:#"Fido"];
[Dog1 Bark];
[pool drain];
return 0;
}
My Questions Are:
How do I get rid of the warning above?
Instead of creating methods in the Dog Class for setting Age and Name, how could I have make Age and Name public class level variables that I could directly assign to?
Any help would be much appreciated!
Thanks,
Pete
Don't declare an int as a pointer. Change your code from:
- (void) SetAge: (int *) liAge
to
- (void) SetAge: (int) liAge
and
- (void) SetAge: (int *) liAge {
ciAge = (int)liAge;
}
to
- (void) SetAge: (int) liAge {
ciAge = liAge;
}
Consider making age and name a property. Change:
- (void) SetAge: (int *) liAge;
- (void) SetName: (NSString *) lsName;
to
#property (nonatomic, readwrite) NSInteger age; //Don't declare as pointer
#property (nonatomic, retain) NSString *name; //DO declare as pointer
Also, don't forget to synthesize them in your implementation file:
#synthesize age, name;
int is a C primitive scalar type so you don't need pointers. Don't use *.
Warning here
int * represents a pointer variable, a thing you're not used to see in C# or in Java.
int *p is a variable that will point to a memory address. To put data at this address you have to dereference the variable p before using it ex:*p = 3.
Objective-C is based on the C language and this is a C language problem. You should (must ?) read about C and pointers if you want to code in Objective-C.
And read also how Objective-C simplifies your life with pointers to objects particularly the fact that you don't have do explicitly dereference them to use them.

Request for member 'pData' with BOOL value TRUE is not a structure or union-Objective C

I could not use the pData[4096] to pass it to the other function from main.
data.m
------
#implementation data
static int msgID;
static char pData[4096]="\0";
+ (void)initialize
{
//some initialisations
msgID =123;
}
-(void)SwapEndian:(uint8_t*)pData withBOOLValue:(BOOL)bIsAlreadyLittleEndian
{
NSLog("%s %s",pData,bIsAlreadyLittleEndian);
}
#end
main.m
-------
[dat SwapEndian:dat.pData withBOOLValue:TRUE];
I am getting pData undeclared. As pData is declared as static inside the Data
implementation i tried with dat.pData to pass it from main.But when i do it i am getting
Request for member 'pData' with BOOL value TRUE is not a structure or union.
It is difficult to determine what the code is supposed to do, but here is how to create an Objective-C object that holds an integer identifier and a 4096-character array. Please note that this sort of thing is usually discouraged. Unless you have a really specific reason for using int and char[], the identifier should be NSInteger and the data should be an NSData or NSString object.
I have also used some of the "standard" naming conventions. If you are writing Cocoa code, it helps to drink a lot of the Kool-Aid.
Message.h:
#interface Message : NSObject
{
int identifier;
char data[4096];
}
#property (nonatomic, assign) int indentifier;
#property (nonatomic, readonly) char * data;
- (void)swapEndian:(BOOL)flag;
#end
Message.m:
#implementation Message
#synthesize identifier;
#synthesize data;
- (id)init
{
if ((self = [super init]) == nil) { return nil; }
identifier = 0;
data[0] = '\0';
return self;
}
- (void)swapEndian:(BOOL)flag
{
NSLog(#"%s %d", data, flag);
}
#end
main.m:
#import "Message.h"
...
Message * message = [[[Message alloc] init] autorelease];
message.identifier = 123;
[message swapEndian:YES];

accessor-Objective C

I am new to Objective C.
I need to know how to access the instance variable using the accessors.
I could able to access integer variables but not the character variable which i have declared here.
Please correct if there is any wrong in this code below.
#define SIZE = 4096
#interface data : NSObject
{
unsigned char id[SIZE];
}
#property(readwrite)unsigned char id[SIZE];
#end
#implementation data
#synthesize unsigned char id[SIZE];
#end
main.m
someClass* classPointer = [[someClass alloc]init];
data* dt = [[data alloc]init];
[classPointer createMessage:data.id];
Why not use an instance of NSString or NSData for the instance variable instead of an array of chars? For example:
#interface Foo2 : NSObject
{
NSString *_dataId;
}
#property (nonatomic, retain) NSString *dataId;
#end
#implementation Foo2
#synthesize dataId = _dataId;
#end
Otherwise you'd have to do something along these lines:
#define DATA_ID_SIZE 4096
#interface Foo : NSObject
{
char _dataID[DATA_ID_SIZE];
}
#property (nonatomic, assign) const char *dataID;
#end
#implementation Foo
// Returns a copy of the internal array of chars.
- (const char *)dataID
{
size_t length = strlen(_dataID);
// Dynamically allocate an array of chars to return.
char *buf = malloc(length);
// Copy the values from the internal array.
memcpy(buf, _dataID, length);
return buf;
}
- (void)setDataID:(const char *)dataID
{
// Copy provided chars into the internal array.
memcpy(_dataID, dataID, DATA_ID_SIZE);
// To be on the safe side, copy null character to the last element.
_dataID[DATA_ID_SIZE - 1] = '\0';
}
By the way, id is a data type in Objective-C, so it's best not to use it as a variable name.
You haven't set id anywhere, so it's just going to be nil.
Also on a side note, you must release the objects that you alloc init.
How do you want to manage the memory is the sample above? char c[size] is an array and it will take sizeof(char) * size bytes as the class member, but the property declared in the same manner will read/write pointer, not the data! I suggest you to use NSData* (or NSMutableData) instead of C-array, it's the preferred way for Obj-C.