I thought the new notation works like this:
someArray[5] turns into
someArray[5] will actually turn into [someArray objectAtIndexedSubscript:5]
However, in NSArray.h and NSOrderedSet.h I saw this:
- (id)objectAtIndexedSubscript:(NSUInteger)idx NS_AVAILABLE(10_8, 6_0);
So, objectAtIndexedSubscript is only available for IOS6.
I experimented making this simple code:
NSArray * someArray =#[#"hello",#"World",#"World"];
NSOrderedSet * someOS = [NSOrderedSet orderedSetWithArray:someArray];
PO(someArray);
PO(someOS);
PO(someArray[0]);
PO(someOS[0]); //Exception thrown here
The code break at someOS[0]
-[__NSOrderedSetI objectAtIndexedSubscript:]: unrecognized selector sent to instance 0x8b1fac0
in BOTH NSArray and NSOrderedSet, there is a text NS_AVAILABLE(10_8, 6_0);
Yet it doesn't break on NSArray yet break on NSOrderedSet. Why?
Bonus: How do I make it work for NSOrderedSet too with category (need to check that it's not already defined)
I have a better answer!
This code will dynamically patch NSOrderedSet for versions of iOS that don't support -objectAtIndexedSubscript:.
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
id PatchedObjectAtIndexedSubscript(id self_, SEL cmd_, NSUInteger index)
{
return [self_ objectAtIndex:index];
}
void PatchedSetObjectAtIndexedSubscript(id self_, SEL cmd_, id object, NSUInteger index)
{
return [self_ replaceObjectAtIndex:index withObject:object];
}
void SIPatchNSOrderedSet()
{
char types[6];
if (!class_getInstanceMethod([NSOrderedSet class], #selector(objectAtIndexedSubscript:))) {
sprintf(types, "##:%s", #encode(NSUInteger));
class_addMethod([NSOrderedSet class],
#selector(objectAtIndexedSubscript:),
(IMP)PatchedObjectAtIndexedSubscript,
types);
}
if (!class_getInstanceMethod([NSMutableOrderedSet class], #selector(setObject:atIndexedSubscript:))) {
sprintf(types, "v#:#%s", #encode(NSUInteger));
class_addMethod([NSMutableOrderedSet class],
#selector(setObject:atIndexedSubscript:),
(IMP)PatchedSetObjectAtIndexedSubscript,
types);
}
}
At the start of your application (-application:didFinishLaunchingWithOptions: maybe) call SIPatchNSOrderedSet().
In looking into this a little deeper and it looks like it NSOrderSet only has -objectAtIndexedSubscript: in iOS 6, but NSArray has -objectAtIndexedSubscript: in both iOS 5 and iOS 6.
My testing has shown the following.
- (void)testNSOrderedSetObjectAtIndexedSubscript
{
NSString *systemVersion = [UIDevice currentDevice].systemVersion;
NSString *message = #"NSOrderedSet for %# does not respond to -objectAtIndexedSubscript:";
NSOrderedSet *orderedSet = [NSOrderedSet orderedSet];
STAssertTrue([orderedSet respondsToSelector:#selector(objectAtIndexedSubscript:)], message, systemVersion);
}
- (void)testNSArrayObjectAtIndexedSubscript
{
NSString *systemVersion = [UIDevice currentDevice].systemVersion;
NSString *message = #"NSArray for %# does not respond to -objectAtIndexedSubscript:";
NSArray *array = [NSArray array];
STAssertTrue([array respondsToSelector:#selector(objectAtIndexedSubscript:)], message, systemVersion);
}
iOS 5.0 Simulator
Test Case '-[SIObjectTests testNSArrayObjectAtIndexedSubscript]' started.
Test Case '-[SIObjectTests testNSArrayObjectAtIndexedSubscript]' passed (0.000 seconds).
Test Case '-[SIObjectTests testNSOrderedSetObjectAtIndexedSubscript]' started.
/Users/jthomas/workspaces/si-catalog-order-ios/SICatalogOrderTests/SIObjectTests.m:20: error: -[SIObjectTests testNSOrderedSetObjectAtIndexedSubscript] : "[orderedSet respondsToSelector:#selector(objectAtIndexedSubscript:)]" should be true. NSOrderedSet for 5.0 does not respond to -objectAtIndexedSubscript:
Test Case '-[SIObjectTests testNSOrderedSetObjectAtIndexedSubscript]' failed (0.000 seconds).
iOS 6.0 Simulator
Test Case '-[SIObjectTests testNSArrayObjectAtIndexedSubscript]' started.
Test Case '-[SIObjectTests testNSArrayObjectAtIndexedSubscript]' passed (0.000 seconds).
Test Case '-[SIObjectTests testNSOrderedSetObjectAtIndexedSubscript]' started.
Test Case '-[SIObjectTests testNSOrderedSetObjectAtIndexedSubscript]' passed (0.000 seconds).
Jeffry answer is awesome and provide insight on how declaring your selector work.
For alternative, I simply do this:
self.businessDetailed.Phones.array[0];
Then I'll tell my future grandchildren to change that to
self.businessDetailed.Phones[0];
when iPhone 15 hit the shelf.
Much less work than Jeffry's solution at the cost of slightly increased marginal costs along the way.
Related
I would like to provide my own implementation for description method of NSArray class, so that I could use it simply like this:
NSLog(#"%#", #[]);
My idea was to provide a category for NSArray and simply override description method there. However it doesn't work because NSArray is a class cluster and its real class is __NSArrayI, so my category implementation is never called. Unfortunately I cannot provide a category for __NSArrayI because this class is unavailable.
Of course, I can just subclass NSArray and implement this method in my subclass, but again, since NSArray is a class cluster I have to provide an implementation for a bunch of different methods, like objectAtIndex: and I don't wanna do that because this is too much work for simply changing the way an array is printed into console.
Do you have any ideas, guys? Thanks
Do you have any ideas, guys? Thanks
Ideas we got. Solutions... you gotta decide.
From the documentation about format specifiers, you can't just worry about description. Specifically, from that document...
Objective-C object, printed as the string returned by
descriptionWithLocale: if available, or description otherwise. Also
works with CFTypeRef objects, returning the result of the
CFCopyDescription function.
We can't do much about CFTypeRef objects unless we want to hack with the linker and/or dynamic loader.
However, we can do something about description and descriptionWithLocale:, though that something is a bit gnarly.
You may also want to consider debugDescription as well.
This is one way to approach your goal, though I consider it "educational" and you should use your best judgement as to whether you want to go this route or not.
First, you need to determine what your replacement description implementation would look like. We will declare a simplistic replacement for description like so (ignoring the original implementation).
static NSString * swizzledDescription(id self, SEL _cmd)
{
NSUInteger count = [self count];
NSMutableString *result = [NSMutableString stringWithFormat:#"Array instance (%p) of type %# with %lu elements", (void*)self, NSStringFromClass([self class]), (unsigned long)count];
int fmtLen = snprintf(0,0,"%lu",count);
for (NSUInteger i = 0; i < count; ++i) {
[result appendFormat:#"\n%p: %*lu: %#", (void*)self, fmtLen, i, self[i]];
}
return result;
}
and an even more simplistic implementation of descriptionWithLocale: that completely ignores the locale.
static NSString * swizzledDescriptionWithLocale(id self, SEL _cmd, id locale) {
return swizzledDescription(self, _cmd);
}
Now, how do we make the NSArray implementations use this code? One approach is to find all subclasses of NSArray and replace their methods...
static void swizzleMethod(Class class, SEL selector, IMP newImp) {
Method method = class_getInstanceMethod(class, selector);
if (method) {
IMP origImp = method_getImplementation(method);
if (origImp != newImp) {
method_setImplementation(method, newImp);
}
}
}
static void swizzleArrayDescriptions() {
int numClasses = objc_getClassList(NULL, 0);
if (numClasses <= 0) return;
Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
Class target = [NSArray class];
for (int i = 0; i < numClasses; i++) {
for (Class c = classes[i]; c; c = class_getSuperclass(c)) {
if (c == target) {
c = classes[i];
swizzleMethod(c, #selector(description), (IMP)swizzledDescription);
swizzleMethod(c, #selector(descriptionWithLocale:), (IMP)swizzledDescriptionWithLocale);
break;
}
}
}
free(classes);
}
A reasonable place to call swizzleArrayDescriptions is in your app delegate's +initialize method.
#implementation AppDelegate
+ (void)initialize {
if (self == [AppDelegate class]) {
swizzleArrayDescriptions();
}
}
Now, you should be able to play with it and see how you get along.
As a very simple test...
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSArray *array = #[#"One", #"Two", #3, #"4", #"FIVE", #(6.0), #".7.", #8, #9, #10, #"Eleven" ];
NSLog(#"%#", array);
NSLog(#"%#", [array mutableCopy]);
}
yields this output...
2015-11-08 14:30:45.501 TestApp[72183:25861219] Array instance (0x6000000c5780) of type __NSArrayI with 11 elements
0x6000000c5780: 0: One
0x6000000c5780: 1: Two
0x6000000c5780: 2: 3
0x6000000c5780: 3: 4
0x6000000c5780: 4: FIVE
0x6000000c5780: 5: 6
0x6000000c5780: 6: .7.
0x6000000c5780: 7: 8
0x6000000c5780: 8: 9
0x6000000c5780: 9: 10
0x6000000c5780: 10: Eleven
2015-11-08 14:30:45.501 TestApp[72183:25861219] Array instance (0x600000045580) of type __NSArrayM with 11 elements
0x600000045580: 0: One
0x600000045580: 1: Two
0x600000045580: 2: 3
0x600000045580: 3: 4
0x600000045580: 4: FIVE
0x600000045580: 5: 6
0x600000045580: 6: .7.
0x600000045580: 7: 8
0x600000045580: 8: 9
0x600000045580: 9: 10
0x600000045580: 10: Eleven
Of course, you should do more testing than I did, because all I did was hack this up after church because it seemed somewhat interesting (it's raining so the picnic was cancelled).
If you need to invoke the original implementation, you will need to create a cache of the original implementations, keyed by class, and invoke them accordingly. However, for a case like this where you want to modify the returned string, you probably don't need to do that, and it should be straight forward in any event.
Also, please note the normal cautions about swizzling, and they are a bit more heightened when playing with class clusters.
Note, that you could also do something like this to create custom subclasses at runtime as well. You could even define your subclasses just as straight subclasses of NSArray in the normal way, then swizzle their types without impacting any of the classes that are not yours... or a bunch of different things... remember the ObjectiveC runtime is your friend.
let's suppose iam doing
for (UserCollection * userCollection in [smth fetchUserCollections]) {
}
is there a guarantee that [user fetchUserCollection] gets called just once at the beginning of the for each?
or i shouldn't count on it?
Well…
I don't know of any official Apple or clang documentation guaranteeing that it's only called once. There's no specification for the Objective-C language.
On the other hand…
You can count on it being called only once. That's the way it works today, it would be a performance regression to call it repeatedly, and it would probably break a lot of code that works today.
For example, you wouldn't be able to do this:
for (Card *card in [deck shuffledArrayOfCards]) {
...
}
because you'd get back a new random ordering on each call.
Under the covers, your program calls [smth fetchUserCollections] once at the start of the loop and saves the result. The result is an object that implements the NSFastEnumeration protocol. It then sends countByEnumeratingWithState:objects:count: to the NSFastEnumeration object repeatedly until the object stops returning elements. The loop sends multiple messages to the NSFastEnumeration object, but only one message to your smth object.
I agree with rob mayoff about NSFastEnumeration. I verified it myself with a quick code test below and I don't see any reason why the for...in structure would change for any other implementation of an enumerable collection.
EDIT: Good suggestions! I've updated my test to have a static counter and a significantly bigger array and it still shows the same result -- that the enumerated object is collected only once. It's worth noting in Apple's documentation for Fast Enumeration specifically where it says "The enumerator raises an exception if you modify the collection while enumerating." While this doesn't confirm exactly what the OP was asking, it does seem to imply that the collection is retrieved only once.
Also worth noting -- when I ran this test with size set to NSUIntegerMax, it raised a nice exception :)
capacity (18446744073709551615) is ridiculous
Code:
#import <Foundation/Foundation.h>
#interface FastEnumer : NSObject
- (NSArray *)enumeratedArrayOfSize:(NSUInteger)size;
#end
#implementation FastEnumer
- (NSArray *)enumeratedArrayOfSize:(NSUInteger)size
{
// counts how many times this method is called
static NSUInteger calledCount = 0;
NSLog(#"(Enumerated) Called: %ld\n", ++calledCount);
// populates an array of NSStrings of alphabet letters to enumerate
static NSMutableArray *testArray = nil;
if (!testArray) {
testArray = [[NSMutableArray alloc] initWithCapacity:size];
NSArray *sample = #[#"A", #"B", #"C", #"D", #"E", #"F", #"G", #"H",
#"I", #"J", #"K", #"L", #"M", #"N", #"O", #"P",
#"Q", #"R", #"S", #"T", #"U", #"V", #"W", #"X",
#"Y", #"Z"];
for (NSUInteger i = 0; i < size; ++i) {
[testArray addObject:sample[i % sample.count]];
}
}
return testArray;
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
// flag to note loop behavior
BOOL started = NO;
// testing class for the 'for...in' fast enumeration
FastEnumer *myTest = [[FastEnumer alloc] init];
// set number of items in array and use Fast Enumeration construct
NSUInteger size = 1000000000; // 1 billion
for (NSString *letter in [myTest enumeratedArrayOfSize:size]) {
// show loop has begun
if (!started) {
NSLog(#"Started 'for...in' loop with %#", letter);
started = YES;
}
}
}
return 0;
}
Output:
2014-09-26 14:16:45.826 Scratchy[8222:303] (Enumerated) Called: 1
2014-09-26 14:17:40.346 Scratchy[8222:303] Started 'for...in' loop with A
Program ended with exit code: 0
I am implementing key-value validation like so:
- (BOOL)validateValue:(__autoreleasing id *)value forKey:(NSString *)key error:(NSError *__autoreleasing *)error
{
NSAttributeDescription *attribute = [[self.entity attributesByName] objectForKey:key];
if (attribute && attribute.attributeType == NSStringAttributeType) {
*value = [NSString stringWithFormat:#"%# modified", *value];
return YES;
}
return [super validateValue:value forKey:key error:error];
}
When this runs, memory continually increases until the app receives a memory warning and crashes at the 250 MB mark.
Interestingly with the following variations the total memory footprint remains stable at around 5 MB.
*value = [NSString stringWithFormat:#"%#", *value];
*value = [NSString stringWithFormat:#"%# modified", NSStringFromClass([self class])];
With the first variation, I suspect +stringWithFormat is optimised to simply return *value unchanged.
The second variation is just me trying to make sure the compiler or objective c runtime isn't returning already existing objects without allocating anything new.
It is only when I create a new string object incorporating the passed in value that memory increases.
I've tried doing
NSString * __unsafe_unretained tmp = *value;
*value = [NSString stringWithFormat:#"%# modified", tmp];
CFStringRef tmp = (__bridge_retained CFStringRef)*value;
*value = [NSString stringWithFormat:#"%# modified", tmp];
CFRelease(tmp);
To no avail.
What am I doing wrong?
Incidentally, notice that you don't need to write (__autoreleasing id *); just writing (id *) is good enough. ARC assumes that pointers-to-object-pointers are __autoreleasing by default. In fact, the Apple documentation on Key-Value Validation has just (id *) for this parameter. But this definitely has nothing to do with your memory leak.
Can you post a complete, compilable example? (A Pastebin link might be the appropriate way to do this.)
Does the memory leak still reproduce if you take Cocoa out of the loop and just directly call your -validateValue:forKey:error: method a couple million times?
Does the memory leak still reproduce if you replace return [super ...] with return YES;?
With the first variation, I suspect +stringWithFormat is optimised to simply return *value unchanged.
This is trivial to check, you know? Just make it
id oldValue = *value;
*value = [NSString stringWithFormat:#"%#", *value];
assert(*value == oldValue); // TESTING -- is the value unchanged?
Indeed, Apple's interning (de-duplicating) of non-mutable NSString instances means that your guess is correct. Non-mutable NSStrings containing the same character data will generally compare pointer-equal. (But of course you shouldn't write code relying on this quirk!)
Personally, my money is on there being no leak at all, but just a lot of (recursive?) calls to -validateValue:forKey:error:. Look, every time you call that function, it takes whatever string used to be the value and extends it by strlen(" modified") bytes. So the first time you validate #"foo", you get #"foo modified"; when you validate that you get #"foo modified modified"; when you validate that you get #"foo modified modified modified"; and eventually your program runs out of memory and crashes.
Try this:
- (BOOL)validateValue:(id *)value forKey:(NSString *)key error:(NSError **)error
{
NSAttributeDescription *attribute = [[self.entity attributesByName] objectForKey:key];
if (attribute && attribute.attributeType == NSStringAttributeType) {
if ([*value hasSuffix: #" modified"]) {
NSLog(#"Uh-oh! I refuse to modify an already modified value!");
} else {
*value = [NSString stringWithFormat:#"%# modified", *value];
}
return YES;
}
return [super validateValue:value forKey:key error:error];
}
Does this solve your memory leak?
I have a project written for iOS 4.x. Recently i update it to iOS5 using XCode 4.3.2. It's strange that the app stop everytime with double free error when using Apple LLVM compiler. After i change back to LLVM GCC, it works fine. Is there any difference between this two?
The code is shown below:
- (NSArray *)readCourselist {
NSString *path = [[self currentUserPathString] stringByAppendingPathComponent:kUserCourseListFilename];
return [NSArray arrayWithContentsOfFile:path];
}
- (NSArray *)getCourselist {
NSArray *courseRawArray = [self readCourselist];
for (NSDictionary *courseDic in courseRawArray) {
CourseModel *courseModel = [[CourseModel alloc] init];
courseModel.courseID = [[courseDic objectForKey:kKeyID] intValue];
courseModel.courseNameString = [courseDic objectForKey:kKeyTitle];
NSArray *lectureArray = [courseDic objectForKey:kKeyLecture];
for (NSDictionary *lectureDic in lectureArray) {
LectureModel *lectureModel = [[LectureModel alloc] init];
NSString *startString = [lectureDic objectForKey:kKeyStart];
if ([startString isEqualToString:#"8:00"]) {
lectureModel.lectureNumber = 1;
}
else if ([startString isEqualToString:#"9:50"]) {
lectureModel.lectureNumber = 2;
}
lectureModel.location = [lectureDic objectForKey:kKeyWhere]; //#property of location is retain
[courseModel.lectureArray addObject:lectureModel];
[lectureModel release];
}
[courseArray addObject:courseModel];
[courseModel release];
}
}
With more tracing i found out that it's
lectureModel.location = [lectureDic objectForKey:kKeyWhere];
that really matters.
In my LectureModel, location is declared as follow
#property (nonatomic, retain) NSString *location;
#synthesize location;
- (id)init {
location = NSLocalizedString(#"未知", nil);
}
Delete NSLocalizedString and everything works all right.
Why?
Generally working with NSDictionary you want to use valueForKey: instead of objectForKey:, but I don't think that's the problem here. If you flip it back to LLVM, and run Instruments with "Zombies", it should point you to exactly where each free (well, release) is occurring.
I need your help. I have some problems with NSInvocation 'getReturnValue:' method. I want to create UIButton programmatically, and even more, I want to create it dynamically using NSInvocation and through passing value through the NSArray (that's why I wrapped UIButtonTypeRoundedRect).
Listing.
NSLog(#"Button 4 pushed\n");//this code executed when button pushed
Class cls = NSClassFromString(#"UIButton");//if exists {define class},else cls=nil
SEL msel = #selector(buttonWithType:);
//id pushButton5 = [cls performSelector:msel withObject:UIButtonTypeRoundedRect];//this code works correctly,but I want to do this by NSInvocation
//---------------------------
NSMethodSignature *msignatureTMP;
NSInvocation *anInvocationTMP;
msignatureTMP = [cls methodSignatureForSelector:msel];
anInvocationTMP = [NSInvocation invocationWithMethodSignature:msignatureTMP];
[anInvocationTMP setTarget:cls];
[anInvocationTMP setSelector:msel];
UIButtonType uibt_ = UIButtonTypeRoundedRect;
NSNumber *uibt = [NSNumber numberWithUnsignedInt:uibt_];
NSArray *paramsTMP;
paramsTMP= [NSArray arrayWithObjects:uibt,nil];
id currentValTMP = [paramsTMP objectAtIndex:0];//getParam from NSArray
NSInteger i=2;
void* bufferTMP;
//if kind of NSValue unwrapp it.
if ([currentValTMP isKindOfClass:[NSValue class]]) {
NSUInteger bufferSize = 0;
NSGetSizeAndAlignment([currentValTMP objCType], &bufferSize, NULL);
bufferTMP = malloc(bufferSize);
[currentValTMP getValue:bufferTMP];//copy currentVal to bufer
[anInvocationTMP setArgument:bufferTMP atIndex:i];// The +2 represents the (self) and (cmd) offsets
}else {
[anInvocationTMP setArgument:¤tValTMP atIndex:i];//Again,+2 represents (self) and (cmd) offsets
}
void* result = malloc([[cls methodSignatureForSelector:msel] methodReturnLength]);
[anInvocationTMP invoke];
[anInvocationTMP getReturnValue:result];
NSLog(#"sizeof(UIButton)=%i,sizeof(result)=%i,methodreturnlength = %i,sizeof(*result)=%i",class_getInstanceSize(NSClassFromString(#"UIButton")),sizeof(result),[[cls methodSignatureForSelector:msel] methodReturnLength],sizeof(*result));
id pushButton5;
pushButton5=result;
//---------------------------
NSLog output: sizeof(UIButton)=140,sizeof(result)=4,methodreturnlength = 4,sizeof(*result)=1
The problem is that value from NSInvocation is pointer of size 4 bytes. It should point to UIButton object,size of 140 bytes. But actually refers to 1 byte data. So what does happen with UIButton object,that should be initialized by 'buttonWithType:'?
Added after getting some answers:
To clarify: I want to get UIButton object, but after this code id pushButton5 = (id) result; ,when I try to work with pushButton5,it causes EXC_BAD_ACCESS. Can someone help me?
Can this happen because of this?
Class cls = NSClassFromString(#"UIButton");
...
[anInvocationTMP setTarget:cls];
It is correct, isn't it?
If you're using ARC, I would replace this:
void* result = malloc([[cls methodSignatureForSelector:msel] methodReturnLength]);
[anInvocationTMP invoke];
[anInvocationTMP getReturnValue:result];
With this:
CFTypeRef result;
[anInvocationTMP invoke];
[anInvocationTMP getReturnValue:&result];
if (result)
CFRetain(result);
UIButton *pushButton5 = (__bridge_transfer UIButton *)result;
The reason is that the invokation's return object is not retained, so it will go away, even if you immediately assign it to an object reference, unless you first retain it and then tell ARC to transfer ownership.
result has type void* and your sizeof(*result) expression is measuing sizeof(void), which apparently yields 1 in your compiler.
To check type of an Objective-C object, use isKindOfClass: method:
id resObj = (id)result;
if ([resObj isKindOfClass:[UIButton class]])
NSLog(#"mazel tov, it's a button");
Just be sure it's really an objective-c object first.
The return value is a UIButton* not a UIButton. Thus everything looks fine in your code.
It's not a problem.
First, getReturnValue: should come after the invocation. So,
[anInvocationTMP getReturnValue:result];
[anInvocationTMP invoke];
should be
[anInvocationTMP invoke];
[anInvocationTMP getReturnValue:result];
Next, you should forget about the size of UIButton itself. What buttonWithType returns is UIButton*, not UIButton. In Objective-C, you should never directly deal with the object itself. You should always work with a pointer to the object.
Finally, (Objective-)C's sizeof operator is purely compile-time operation. So, sizeof(*result) doesn't know at all what result points to at the run time. But it doesn't matter... you shouldn't care about the size of UIButton, as I already told you.
Really answer that I needed was... How do you think where ? Yes, in documentation.
Use the NSMethodSignature method methodReturnLength to determine the size needed for buffer :
NSUInteger length = [[myInvocation methodSignature] methodReturnLength];
buffer = (void *)malloc(length);
[invocation getReturnValue:buffer];
When the return value is an object, pass a pointer to the variable (or memory) into which the object should be placed :
id anObject;
NSArray *anArray;
[invocation1 getReturnValue:&anObject];
[invocation2 getReturnValue:&anArray];
So the problem solved. Thanks for your respond guys.