Saturday, July 13, 2013

Apple, why are you toying with me via -subviews? (mutability vs immutability)

You want to get a view's subviews and enumerate them.

Maybe you also want to remove some of them from their superview while you do that, why not?

But you have a slight misgiving because what if that would constitute mutation during enumeration?

So let's look at the API for -subviews:


@property(nonatomic,readonly,copy) NSArray *subviews;

Hey, this looks pretty good! It's an NSArray, so we know it's immutable. Therefore, no mutation. Very nice! 

And Apple even tells us how we should act in a similar scenario in our own code (my highlighting):

Consider a scenario where all objects are capable of being mutated. In your application you invoke a method and are handed back a reference to an object representing a string. You use this string in your user interface to identify a particular piece of data. Now another subsystem in your application gets its own reference to that same string and decides to mutate it. Suddenly your label has changed out from under you. Things can become even more dire if, for instance, you get a reference to an array that you use to populate a table view. The user selects a row corresponding to an object in the array that has been removed by some code elsewhere in the program, and problems ensue. Immutability is a guarantee that an object won’t unexpectedly change in value while you’re using it.

Sounds great to me, Apple! So let's do this:

    for (UIView *subview in parentView.subviews) {
        if (subview.tag == 37) {
            [subview removeFromSuperview];
        }
    }

Totally safe! But oh no!!

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__nsarraym: 0x63f9a0=""> was mutated while being enumerated.'

How could this be??? Let's find out:

UIView *parentView = [[UIView alloc] init];
    
NSLog(@"subviews: %p class: %@ count: %d", parentView, [parentView.subviews class], [parentView.subviews count]);

logged result (unimportant stuff edited): 
2013-07-13 23:54:19.168 subviews: 0x71a2ea0 class: __NSArrayI count: 0

OK, this looks fine. __NSArrayI means an immutable array and yep it has zero items. Then what's up? I'll take the next step to see:

UIView *parentView = [[UIView alloc] init];
    
NSLog(@"subviews: %p class: %@ count: %d", parentView, [parentView.subviews class], [parentView.subviews count]);
    
UIView *childView1 = [[UIView alloc] init];
UIView *childView2 = [[UIView alloc] init];
    
[parentView addSubview:childView1];

NSLog(@"subviews: %p class: %@ count: %d", parentView, [parentView.subviews class], [parentView.subviews count]);


[parentView addSubview:childView2];
    
NSLog(@"subviews: %p class: %@ count: %d", parentView, [parentView.subviews class], [parentView.subviews count]);

This code results in this log:
2013-07-14 00:37:16.646 subviews: 0x712a170 class: __NSArrayI count: 0
2013-07-14 00:37:16.648 subviews: 0x712a170 class: __NSArrayM count: 1
2013-07-14 00:37:16.648 subviews: 0x712a170 class: __NSArrayM count: 2

Wait, what!? My nice immutable __NSArrayI has transformed into a dangerous, mutable __NSArrayM! Who asked for THIS? (and check it out, the immutable array and the mutable ones are the SAME OBJECT!) Why would Apple do this to me? Let's go look at that page some more, maybe we missed something! Sure enough, we did:

Although in theory immutability guarantees that an object’s value is stable, in practice this guarantee isn’t always assured. A method may choose to hand out a mutable object under the return type of its immutable variant; later, it may decide to mutate the object, possibly violating assumptions and choices the recipient has made based on the earlier value.

So OK, Apple. You say something totally reasonable in that first paragraph, then you throw it out the window in this paragraph?? Why?? Just how much advantage is there for you to send out an NSMutableArray sometimes instead of always sending out a nice safe NSArray? Would it kill you to just throw a -copy on your internal mutable -subviews array before you let me have it?

Apple says some other things on that page:

When the mutability of objects is an issue, it’s best to adopt some defensive programming practices.

Oh yeah, you don't say?

Rely on the return type for indications of mutability.

Yeah, that sounds like a GREAT idea! I think I will do that (if I want to crash).

If you have any doubts about whether an object is, or should be, mutable, go with immutable.
Exactly what I think you should be doing too, so why aren't you, Apple?

If you have a mutable collection that is frequently changed and that you frequently hand out to clients (that is, you return it directly in a getter accessor method), you run the risk of mutating something that your clients might have a reference to. If this risk is probable, the instance variable should be immutable.

Fantastic idea, Apple! I wish I had thought of it! I wish you had implemented it that way!

If the value of the instance variable frequently changes but you rarely return it to clients in getter methods, you can make the instance variable mutable but return an immutable copy of it in your accessor method

Yeah, this sounds great too! How about you start doing that? (even though -subviews doesn't qualify as "rarely", I don't care). This page is just full of great ideas that someone at Apple had, and someone else at Apple ignored!



One sophisticated approach for handling mutable collections that are returned to clients is to maintain a flag that records whether the object is currently mutable or immutable. If there is a change, make the object mutable and apply the change. When handing out the collection, make the object immutable (if necessary) before returning it.
Another gem! This is perfectly reasonable!! Why aren't you doing that? But then things really get weird on that page:

  • You ask NSView for its subviews (with the subviews method) and it returns an object that is declared to be an NSArray but which could be an NSMutableArray internally. Then you pass that array to some other code that, through introspection, determines it to be mutable and changes it. By changing this array, the code is mutating internal data structures of the NSView class.
So don’t make an assumption about object mutability based on what introspection tells you about an object. Treat objects as mutable or not based on what you are handed at the API boundaries (that is, based on the return type). 
I'm not making an assumption based on introspection (calling -isKindOfClass on it). I AM relying on what the API says! But you are kind of screwing me over, and even worse, you keep changing your mind about if I should be expecting it. Look at the split personalities of those two paragraphs.

It is you, Apple, who is mutating the array (supposedly an NSArray according to the API) that you pass to me, your beloved client. I didn't check it via introspection, just like you said, I checked the API return type!

So, devs, be sure to make your own copy of -subviews all over your code and don't forget either! All because Apple didn't want to return [_subviews copy] for some reason.

Or is it just a bug I should file?

7/18/2013 Postscript: Call -copy on an NSMutableURLRequest and see what kind of thing you get back!

Tuesday, March 19, 2013

How slow is CGRectMake? Or: Paul times stupid things so you don't have to!

I just love the CGGeometry stuff. Apple seems to think using them is a good idea. Other people don't like them as much. I feel like developers who really want to be programming iOS or MacOS embrace them. Developers who would really rather be programming something else avoid them. That's just my opinion about it.

OK, having said that, how slow is CGRectMake?

In case you have never looked, here is what CGRectMake actually does:


CG_INLINE CGRect
CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
{
  CGRect rect;
  rect.origin.x = x; rect.origin.y = y;
  rect.size.width = width; rect.size.height = height;
  return rect;
}

Now I wonder why they didn't just: return (CGRect){{x,y},{width,height}}; Does anyone know? Also, I love that Apple puts that opening { on its own line. That was my pattern until I was beaten into submission at the NYTimes by Matt and Brittany.


Here is my test code:


CGFloat x = 1.0;
CGFloat y = 1.0;
CGFloat width = 1.0;
CGFloat height = 1.0;

NSDate *startTime = [NSDate date];
for (NSInteger loop = 0; loop < 1000000000; loop++) {
    CGRect rect = CGRectMake(x, y, width, height);
}
NSLog(@"elapsed time of CGRectMake: %f", -[startTime timeIntervalSinceNow]);
    
startTime = [NSDate date];
for (NSInteger loop = 0; loop < 1000000000; loop++) {
    CGRect rect = {{x,y},{width,height}};
}
NSLog(@"elapsed time of bracket rect make: %f", -[startTime timeIntervalSinceNow]);

Here are the results:

2013-03-19 22:46:02.391 CGRectMakeTest[38559:c07] elapsed time of CGRectMake: 14.657779
2013-03-19 22:46:05.212 CGRectMakeTest[38559:c07] elapsed time of bracket rect make: 2.818589

The results show that CGRectMake takes 5.2 times as long as creating the struct using brackets. Wow, that's a long time! But it only took 0.00000001465778 seconds per call (admittedly on the iPhone Simulator on my MacBook Air. I was just too lazy to go get my cable to plug in the phone).

I'll keep using CGRectMake, thanks.

Postscript: I made my own version of CGRectMake to test the time required to just return the bracket rect, like so:


CG_INLINE CGRect
MYCGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
{
    return (CGRect){{x,y},{width,height}};
}

startTime = [NSDate date];
for (NSInteger loop = 0; loop < 1000000000; loop++) {
    CGRect rect = MYCGRectMake(x, y, width, height);
}
NSLog(@"elapsed time of MYCGRectmake: %f", -[startTime timeIntervalSinceNow]);


Here are those results:


2013-03-19 23:28:39.452 CGRectMakeTest[44092:c07] elapsed time of CGRectMake: 14.612408
2013-03-19 23:28:42.331 CGRectMakeTest[44092:c07] elapsed time of bracket rect make: 2.877312
2013-03-19 23:28:56.775 CGRectMakeTest[44092:c07] elapsed time of MYCGRectmake: 14.443198


I guess that's why Apple didn't bother with that more difficult-to-read code--it was no faster.

Sunday, February 10, 2013

Can I stop typing "f" now?

Can I please stop worrying about "f" now? I have to admit I've never worried about it too much, but let's see if that was foolish on my part.

First test is simple assignment:





Let's see how the compiler deals with these two lines:








This is great! The compiler knew that I wanted floats, and that's the literals that it made. Thanks compiler! We don't have to type an f!

Now let's try to make things more interesting. What if I make the result a float, and divide an int by a literal? And I'll put it in a billion-cycle loop just for fun:








Here is a diff of the compiled assembly:









On the left is where I didn't put an f after the 3.2 literal. The compiler converted the integer i into a double. Then it did a double division. Then because result is a float, it had to convert the result from a double to a float (single).

On the right is where I did put an f after the 3.2 literal. The compiler had to convert the integer i into a float (single) this time instead of a double, but then no additional conversion was required because the result of the division was already a float to put into the result variable.

So unlike the first example of a simple literal assignment, there is an extra runtime conversion in this example. That is going to cost us, but how much? You saw I did a billion of them, and I ran it on an iPhone 5. Here is a typical result:




That's 0.00000001549739 seconds per conversion. It's going to be hard for me to worry about f too much unless I'm doing something a billion or OK, maybe a million times.

Or really now that I think about it, a million times isn't enough times to worry about it:




Friday, June 10, 2011

Computer chiropracty

I have decided to start a chiropractic computer repair business.

I have determined that since the power cord is the source of all computing power, without which, computers could not function at all, that the power cord must be the cause of all computer problems.

If you are experiencing a computer problem, just call me and I will come right over to evaluate, diagnose, and adjust your power cord to solve your problem.

I will focus primarily on cord kinks, which cause most problems. Next I will evaluate the curves of your code to maximize power flow, and finally, I will adjust your cord's "aura" not by touching directly, which would affect the aura, disturbing my analysis, but rather using "near-field" adjustments.

One important fact is that although the first visit is likely to improve your computer's performance, it can take several visits before your cord's years of bad adjustment are completely resolved.

And finally, and most important, you should call me for "maintenance adjustments" of your cord even if your computer appears to be running well. Only with these maintenance visits can the continued healthy operation of your computer be ensured. This applies to brand new computers because no doubt the cord arrived from the factory in a horribly twisted condition. Just like your chiropractor would love to get his hands on your children, I would love to get my hands on your brand new computer's power cord.

Tuesday, April 14, 2009

Portuguese Water Dogs, the next shelter casualty

What a lost opportunity.

Instead of adopting a homeless dog from a shelter being the next cool thing to do, buying a Portuguese Water Dog from a breeder will be the next cool thing.

This will draw thousands of disgusting dog breeders and puppy mills into breeding Portuguese Water Dogs which in turn will result in the deaths of thousands of unsold dogs or puppies who don't measure up to the "standard."

This will also lure thousands of existing owners of Portuguese Water Dogs to breed their dogs, adding to the number of surplus dogs. A commentator on ABC's This Week program stated that you don't find Portuguese Water Dogs in shelters. Get ready for that to change.

All of these activities will increase the number of all dog breeds that go unadopted at the nation's dog shelters resulting of course in increased killing of unwanted, yet perfectly adoptable dogs.

Also, I wonder if all these new Portuguese Water Dog breeders will be on the lookout for the following problems: hip dysplasia, various eye problems, GM-1 storage disease, and Hypoadrenocorticism.

To summarize: if you breed any dog breeds, you are disgusting. If you purchase a Portuguese Water Dog instead of getting a dog from a shelter, you are also disgusting. If you decide to breed your existing Portuguese Water Dog pet, then you are very disgusting. If you decide to start breeding Portuguese Water Dogs now that one is the "first dog", you are the most disgusting.

Labels: , , ,