Can anyone give me clear picture about dynamic property and its usage? y not use the usual #property everywhere?
Dynamic properties are used when you don't provide an implementation at compile time, but ensure that one exists at runtime. Being a dynamic language, Objective-C can respond to messages at runtime, even if the class doesn't have an implementation at compile time.
Here's a contrived example: Let's say you have a Book class, backed by an NSMutableDictionary that contains the keys title and author. However, you want Book to respond to title and author as well, and have them as properties; title and author will grab the appropriate value from the dictionary, and setTitle: and setAuthor: will change the value stored in the dictionary. You could do so with this code:
#import <Foundation/Foundation.h>
#interface Book : NSObject
{
NSMutableDictionary *data;
}
#property (retain) NSString *title;
#property (retain) NSString *author;
#end
#implementation Book
#dynamic title, author;
- (id)init
{
if ((self = [super init])) {
data = [[NSMutableDictionary alloc] init];
[data setObject:#"Tom Sawyer" forKey:#"title"];
[data setObject:#"Mark Twain" forKey:#"author"];
}
return self;
}
- (void)dealloc
{
[data release];
[super dealloc];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
NSString *sel = NSStringFromSelector(selector);
if ([sel rangeOfString:#"set"].location == 0) {
return [NSMethodSignature signatureWithObjCTypes:"v#:#"];
} else {
return [NSMethodSignature signatureWithObjCTypes:"##:"];
}
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
NSString *key = NSStringFromSelector([invocation selector]);
if ([key rangeOfString:#"set"].location == 0) {
key = [[key substringWithRange:NSMakeRange(3, [key length]-4)] lowercaseString];
NSString *obj;
[invocation getArgument:&obj atIndex:2];
[data setObject:obj forKey:key];
} else {
NSString *obj = [data objectForKey:key];
[invocation setReturnValue:&obj];
}
}
#end
int main(int argc, char **argv)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Book *book = [[Book alloc] init];
printf("%s is written by %s\n", [book.title UTF8String], [book.author UTF8String]);
book.title = #"1984";
book.author = #"George Orwell";
printf("%s is written by %s\n", [book.title UTF8String], [book.author UTF8String]);
[book release];
[pool release];
return 0;
}
Note the the methods are "created" at runtime via forwardInvocation:; hence, title and author are dynamic properties.
(This isn't the best example, but I think it gets the point across.)
#dynamic thing; is merely a way to inform the system not to generate getters/setters for the thing, that you (or someone else) will provide them for you—As in, they'll be there at runtime.
This is in contrast to #synthesize which tells the compiler to generate the getter/setter (as appropriate) for you.
#dynamic is (in my experience) used primarily in conjunction with Core Data and subclasses of NSManagedObject. To quote Marcus Zarra's Core Data,
By declaring them
[attributes/relationships], we are
telling the compiler to ignore any
warnings associated with there
properties because we "promise" to
generate them at runtime. Naturally,
if the turn up missing at runtime,
then our application is going to
crash.
Related
I have a form with information first name and last name and some other information. I use a person class to store this information. on submit click I archiving it in a file person.txt using NSCoding implemented in person class. if I add multiple persons in the file person.txt, how can I get all the person objects stored in the file. decoding the person class just gives me the last added person.
If you want all of the person objects serialized, then you need the NSArray or whatever other collection class in which they are stored to be the root object for the NSKeyedArchiver. e.g.: (assumes ARC)
#import <Foundation/Foundation.h>
#interface Person:NSObject <NSCoding>
#property (nonatomic, copy) NSString *lastName;
#property (nonatomic, copy) NSString *firstName;
// etc.
#end
#implementation Person
#synthesize lastName = _lastName;
#synthesize firstName = _firstName;
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.lastName forKey:#"ln"];
[aCoder encodeObject:self.firstName forKey:#"fn"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if( !self ) { return nil; }
_lastName = [aDecoder decodeObjectForKey:#"ln"];
_firstName = [aDecoder decodeObjectForKey:#"fn"];
return self;
}
#end
int main(int argc, char *argv[]) {
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
Person *me = [Person new];
me.lastName = #"Kitten";
me.firstName = #"Mittens";
Person *you = [Person new];
you.lastName = #"Youe";
you.firstName = #"JoJo";
NSArray *people = [NSArray arrayWithObjects:me,you,nil];
NSData *serializedData = [NSKeyedArchiver archivedDataWithRootObject:people];
// write your serializedData to file, etc.
[p release];
}
Why the .txt extension on your archive, though? It just binary data, right?
How do I build up a global like integer array?
I tried variations of the following:
#interface
int *iArray; //this space will vary depending upon need in the implementation
#implementation
...
int iArrayInit[4] = {1,2,3,4};
iArray = iArrayInit;
-bottom line: I need to keep index values in array that I can access easily, and use of [NSArray intValue] maybe to slow.
thanks
If it needs to be static you can declare an NSMutableArray as static in the implementation file and expose static methods to access it. When using an NSArray the values need to be of type id which NSNumber can do. Here is an example which currently is not thread safe.
//.h file
#interface Foo : NSObject
{
}
+(NSArray*)iArray;
+(void)addiArrayValue:(NSNumber*)value;
#end
//.m file
#implementation Foo
static NSMutableArray *_iArray;
+(void)initialize
{
if([Foo class] == self)
{
_iArray = [[NSMutableArray alloc] init];
}
}
+(NSArray*)iArray
{
return [[_iArray copy] autorelease];
}
+(void)addiArrayValue:(NSNumber*)value
{
[_iArray addObject:value];
}
#end
//Use
[Foo addiArrayValue:[NSNumber numberWithInt:10]];
[Foo addiArrayValue:[NSNumber numberWithInt:12]];
NSLog(#"%#", [Foo iArray]);
I'm trying to send some NSData over Bluetooth through GameKit.
While I've got GameKit set up and are able to send small messages across, I now would like to expand and send across whole files.
I've been reading that you have to split large files up into packets before sending them across individually.
So I decided to create a struct to make it easier to decode the packets when they're received at the other end:
typedef struct {
const char *fileName;
NSData *contents;
int fileType;
int packetnumber;
int totalpackets;
} file_packet;
However, for small files (8KB and less) I thought one packet will be enough.
So for one packet, I thought I would be able to create a file_packet, set its properties, and send it via -sendDataToAllPeers:withDataMode:error:
NSData *fileData;
file_packet *packet = (file_packet *)malloc(sizeof(file_packet));
packet->fileName = [filename cStringUsingEncoding:NSASCIIStringEncoding];
packet->contents = [NSData dataWithContentsOfFile:selectedFilePath];
packet->packetnumber = 1;
packet->totalpackets = 1;
packet->fileType = 56; //txt document
fileData = [NSData dataWithBytes:(const void *)packet length:sizeof(file_packet)];
free(packet);
NSError *error = nil;
[self.connectionSession sendDataToAllPeers:fileData withDataMode:GKSendDataReliable error:&error];
if (error) {
NSLog(#"An error occurred: %#", [error localizedDescription]);
}
However, I don't think something's right setting fileData - and error displays nothing.
When a file's received, I do the following:
file_packet *recievedPacket = (file_packet *)malloc(sizeof(file_packet));
recievedPacket = (file_packet *)[data bytes];
NSLog(#"packetNumber = %d", recievedPacket->packetnumber);
...
However, the output on the console is packetNumber = 0, even when I set packetNumber to 1.
Am I missing the obvious?
I don't know much about NSData or GameKit.
So my question is - Can I add a file_packet in NSData, and if so, How do I do it successfully - and How do you split files up into multiple packets?
To add on:
What you ought to do here is make an NSObject subclass to represent your packet, and then adopt NSCoding to serialize it to an NSData in the way that you want. Doing this with a struct isn't buying you anything, and makes things even harder. It's also fragile, since packing a struct into an NSData doesn't account for things like endian-ness, etc.
The tricky part of the packetizing process using NSCoding is that you don't really know what the overhead of the coding process is, so being as big as possible, but still under the max packet size is tricky...
I present this without testing, or warranty, but if you want a decent start on that approach, this may be it. Be warned, I didn't check to see if my arbitrary 100 bytes for overhead was realistic. You'll have to play with the numbers a little bit.
Packet.h:
#interface Packet : NSObject <NSCoding>
{
NSString* fileName;
NSInteger fileType;
NSUInteger totalPackets;
NSUInteger packetIndex;
NSData* packetContents;
}
#property (readonly, copy) NSString* fileName;
#property (readonly, assign) NSInteger fileType;
#property (readonly, assign) NSUInteger totalPackets;
#property (readonly, assign) NSUInteger packetIndex;
#property (readonly, retain) NSData* packetContents;
+ (NSArray*)packetsForFile: (NSString*)name ofType: (NSInteger)type withData: (NSData*)fileContents;
#end
Packet.m:
#import "Packet.h"
#interface Packet ()
#property (readwrite, assign) NSUInteger totalPackets;
#property (readwrite, retain) NSData* packetContents;
#end
#implementation Packet
- (id)initWithFileName: (NSString*)pFileName ofType: (NSInteger)pFileType index: (NSUInteger)pPacketIndex
{
if (self = [super init])
{
fileName = [pFileName copy];
fileType = pFileType;
packetIndex = pPacketIndex;
totalPackets = NSUIntegerMax;
packetContents = [[NSData alloc] init];
}
return self;
}
- (void)dealloc
{
[fileName release];
[packetContents release];
[super dealloc];
}
#synthesize fileName;
#synthesize fileType;
#synthesize totalPackets;
#synthesize packetIndex;
#synthesize packetContents;
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject: self.fileName forKey: #"fileName"];
[aCoder encodeInt64: self.fileType forKey:#"fileType"];
[aCoder encodeInt64: self.totalPackets forKey:#"totalPackets"];
[aCoder encodeInt64: self.packetIndex forKey:#"packetIndex"];
[aCoder encodeObject: self.packetContents forKey:#"totalPackets"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init])
{
fileName = [[aDecoder decodeObjectForKey: #"fileName"] copy];
fileType = [aDecoder decodeInt64ForKey:#"fileType"];
totalPackets = [aDecoder decodeInt64ForKey:#"totalPackets"];
packetIndex = [aDecoder decodeInt64ForKey:#"packetIndex"];
packetContents = [[aDecoder decodeObjectForKey:#"totalPackets"] retain];
}
return self;
}
+ (NSArray*)packetsForFile: (NSString*)name ofType: (NSInteger)type withData: (NSData*)fileContents
{
const NSUInteger quanta = 8192;
Packet* first = [[[Packet alloc] initWithFileName:name ofType:type index: 0] autorelease];
// Find out how big the NON-packet payload is...
NSMutableData* data = [NSMutableData data];
NSKeyedArchiver* coder = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
[first encodeWithCoder: coder];
[coder finishEncoding];
const NSUInteger nonPayloadSize = [data length];
NSMutableArray* packets = [NSMutableArray array];
NSUInteger bytesArchived = 0;
while (bytesArchived < [fileContents length])
{
Packet* nextPacket = [[[Packet alloc] initWithFileName: name ofType: type index: packets.count] autorelease];
NSRange subRange = NSMakeRange(bytesArchived, MIN(quanta - nonPayloadSize - 100, fileContents.length - bytesArchived));
NSData* payload = [fileContents subdataWithRange: subRange];
nextPacket.packetContents = payload;
bytesArchived += [payload length];
[packets addObject: nextPacket];
}
for (Packet* packet in packets)
{
packet.totalPackets = packets.count;
}
return packets;
}
- (NSData*)dataForSending
{
NSMutableData* data = [NSMutableData data];
NSKeyedArchiver* coder = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
[self encodeWithCoder: coder];
[coder finishEncoding];
return [NSData dataWithData:data];
}
+ (Packet*)packetObjectFromRxdData:(NSData*)data
{
NSKeyedUnarchiver* decoder = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
return [[[Packet alloc] initWithCoder:decoder] autorelease];
}
#end
The reassemblage of the original file from these packets can be done using much the same approach as splitting it up... Iterate over the packets, copying from the individual packet payload NSDatas into a big NSMutableData.
In closing, I feel compelled to say that when you find yourself doing something like this, that boils down to implementing a primitive TCP stack, it's usually time to stop yourself and ask if there aren't better ways to do this. Put differently, if GameKit were the best way to transfer files between devices over bluetooth, one would expect that the API would have a method for doing just that, but instead it has this 8K limit.
I'm not being intentionally cryptic -- I don't know what the right API would be for your situation, but the exercise of cooking up this Packet class left me thinking, "there's gotta be a better way."
Hope this helps.
You create the NSData with size sizeof(packet), which is only the pointer's size. Change it to sizeof(file_packet).
BTW, you're not really sending the filename and the contents. Only the pointers to them.
Background: I use a ton of NSDictionary objects in my iPhone and iPad code. I'm sick of the verbose way of getting/setting keys to these state dictionaries.
So a little bit of an experiment: I just created a class I call Remap.
Remap will take any arbitrary set[VariableName]:(NSObject *) obj selector and forward that message to a function that will insert obj into an internal NSMutableDictionary under the key [vairableName].
Remap will also take any (zero argument) arbitrary [variableName] selector and return the NSObject mapped in the NSMutableDictionary under the key [variableName].
e.g.
Remap * remap = [[Remap alloc] init];
NSNumber * testNumber = [NSNumber numberWithInt:46];
[remap setTestNumber:testNumber];
testNumber = [remap testNumber];
[remap setTestString:#"test string"];
NSString * testString = [remap testString];
NSMutableDictionary * testDict = [NSMutableDictionary dictionaryWithObject:testNumber forKey:#"testNumber"];
[remap setTestDict:testDict];
testDict = [remap testDict];
where none of the properties testNumber, testString, or testDict are actually defined in Remap.
The crazy thing? It works... My only question is how can I disable the "may not respond to " warnings for JUST accesses to Remap?
P.S. : I'll probably end up scrapping this and going with macros since message forwarding is quite inefficient... but aside from that does anyone see other problems with Remap?
Here's Remap's .m for those who are curious:
#import "Remap.h"
#interface Remap ()
#property (nonatomic, retain) NSMutableDictionary * _data;
#end
#implementation Remap
#synthesize _data;
- (void) dealloc
{
relnil(_data);
[super dealloc];
}
- (id) init
{
self = [super init];
if (self != nil) {
NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
[self set_data:dict];
relnil(dict);
}
return self;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSString * selectorName = [NSString stringWithUTF8String: sel_getName([anInvocation selector])];
NSRange range = [selectorName rangeOfString:#"set"];
NSInteger numArguments = [[anInvocation methodSignature] numberOfArguments];
if (range.location == 0 && numArguments == 4)
{
//setter
[anInvocation setSelector:#selector(setData:withKey:)];
[anInvocation setArgument:&selectorName atIndex:3];
[anInvocation invokeWithTarget:self];
}
else if (numArguments == 3)
{
[anInvocation setSelector:#selector(getDataWithKey:)];
[anInvocation setArgument:&selectorName atIndex:2];
[anInvocation invokeWithTarget:self];
}
}
- (NSMethodSignature *) methodSignatureForSelector:(SEL) aSelector
{
NSString * selectorName = [NSString stringWithUTF8String: sel_getName(aSelector)];
NSMethodSignature * sig = [super methodSignatureForSelector:aSelector];
if (sig == nil)
{
NSRange range = [selectorName rangeOfString:#"set"];
if (range.location == 0)
{
sig = [self methodSignatureForSelector:#selector(setData:withKey:)];
}
else
{
sig = [self methodSignatureForSelector:#selector(getDataWithKey:)];
}
}
return sig;
}
- (NSObject *) getDataWithKey: (NSString *) key
{
NSObject * returnValue = [[self _data] objectForKey:key];
return returnValue;
}
- (void) setData: (NSObject *) data withKey:(NSString *)key
{
if (key && [key length] >= 5 && data)
{
NSRange range;
range.length = 1;
range.location = 3;
NSString * firstChar = [key substringWithRange:range];
firstChar = [firstChar lowercaseString];
range.length = [key length] - 5; // the 4 we have processed plus the training :
range.location = 4;
NSString * adjustedKey = [NSString stringWithFormat:#"%#%#", firstChar, [key substringWithRange:range]];
[[self _data] setObject:data forKey:adjustedKey];
}
else
{
//assert?
}
}
#end
Cool class. I like it.
I can't think of a way to suppress all your warnings, but we can get it down to one line per property. Add this to your Remap.h:
#define RemapProperty(PROP) \
#interface Remap(PROP) \
#property (nonatomic, retain) id PROP; \
#end \
#implementation Remap(PROP) \
#dynamic PROP; \
#end
Now you can suppress all remap warnings for a given property by putting this at the top of the file that's giving you a warning:
RemapProperty(propertyName);
This requires some extra work, but it gives you dot-syntax as a reward.
remap.propertyName = #"Foo";
With a little more work, you could define a similar macro that adds properties directly to NSDictionary, thereby making the Remap class unnecessary.
If I'm reading your code right, I think a potential problem with this approach might be that you can't have key names such as hash (or other methods from NSObject, assuming your Remap inherits from NSObject). You will end up with the Remap instance's hash value rather than letting Remap look up a key called hash within _data, because [remap hash] will not invoke forwardIncovation:, as far as I can tell.
As an alternative for making dictionaries more like general purpose objects, what I have done is make categories of NSDictionary that wrap getters and setters for specific keys. Just use very descriptive names since you are filling a global namespace.
#interface NSDictionary (MyGetters)
#property (nonatomic,readonly) id something;
-(id) something;
#end
#interface NSDictionary (MyGetters)
-(id) something { return [self objectForKey:#"something"]; }
#end
#interface NSMutableDictionary (MySetters)
-(void) setSomething:(id)inValue;
#end
#interface NSDictionary (MySetters)
-(void) setSomething:(id)inValue { return [self setObject:inValue forKey:#"something"]; }
#end
The only problem with setter properties is that you must define the getter in both categories to avoid a warning.
You still have to declare everything, but you are going to have to do that anyway to get rid of warnings for Remap.
I hope to add objects to a NSMutableArray "myArray", The NSMutableArray is the array for FileObj which has a NSString property "fileName"
#import <UIKit/UIKit.h>
#interface FileObj : NSObject {
NSString *fileName;
}
-(void) setfileName:(NSString *)s ;
-(NSString *) getfileName ;
#end
//
// File.m//
#import "File.h"
#implementation FileObj
-(void) setfileName:(NSString *)s ;
{
fileName=s;
}
-(NSString *) getfileName ;
{
return fileName;
}
#end
I initialize the myArray here:
NSMutableArray *temarray;
temarray=[[NSMutableArray alloc] init];
self.myArray=temarray;
[temarray release];
the codes to add object to myArray
FileObj *newobj=[[FileObj alloc]init ];
NSString *fieldValue2 = [[NSString alloc] initWithUTF8String:#"aaaa"];
[newobj setfileName:fieldValue2];
[myArray addObject:newobj];
[fieldValue2 release]; //**if I enabled the line, it will cause crash**
//**if I disable the line, it will cause memory leak**
[newobj release];
Welcome any comment
Thanks
interdev
First you should look into ObjC naming conventions. There is no -get methods in ObjC. It's also a good idea to prefix your classes with your own 2 letters (like NS).
Your setter value assignment is invalid and the NSString initialization unnecessary.
I would strongly recommend introductory material to you!
#interface MYFileObject : NSObject {
NSString *_fileName;
}
- (void)setFileName:(NSString *)theString;
- (NSString *)fileName;
#end
and the implementation
#implementation MYFileObject
- (void)setFileName:(NSString *)theString {
[_fileName release];
_fileName = [theString copy];
}
- (NSString *)fileName {
return [[_fileName copy] autorelease];
}
- (void)dealloc {
[_fileName release];
[super dealloc];
}
#end
You would add an object like this...
NSMutableArray *myAry = [[NSMutableArray alloc] init];
MYFileObject *obj = [[MYFileObject alloc] init];
[obj setFileName:#"thefilename.txt"];
[myAry addObject:obj];
[obj release];
I would recommend using properties instead of defining your own getters/setters.
You could also use the NSMutableArrays' designated initializers for fast array creation.
Look here for how to use properties: http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/ObjectiveC/Articles/ocProperties.html
Why bother with getters and setters? Use declared property already!
#interface FileObj : NSObject {
NSString *fileName;
}
#property(retain,nonatomic) NSString* fileName; // <---
#end
...
#implementation FileObj
#synthesize fileName; /// <---
-(void)dealloc {
[fileName release]; // Remember to release the object on dealloc.
[super dealloc];
}
#end
...
FileObj *newobj=[[FileObj alloc] init];
NSString *fieldValue2 = [[NSString alloc] initWithUTF8String:#"aaaa"];
newobj.fileName = fieldValue2; /// <----
[myArray addObject:newobj];
[fieldValue2 release];
[newobj release];
The crash occurs because the NSString instance is not retained anymore.
A common pattern is to retain NSString properties, either declaratively with #property or by hand.
You should modify the setter like this:
-(void) setfileName:(NSString *)s ;
{
[s retain]; // <- Retain new value
[filename release]; // <- Release old value
fileName=s;
}