__unsafe_unretained NSString struct var

2020-02-29 10:30发布

问题:

I am trying to create a structure that has several different variable of different types in it.

several of the types are of NSString, but trying to do this was causing an error

ARC forbids Objective-C objects in structs or unions

so having read about the error I see its sensible to add

__unsafe_unretained

before the NSString declaration, However I have no idea what the ramifications of this will be, I have had a quick read around and found this detailed post about the differences of

  • __strong
  • __weak
  • __unsafe_unretained

however it was still abit vague about whats going on with a NSString thats in a struct with __unsafe_unretained infront of it and was hoping someone can tell me whats going on and what I need to think about in the future regarding memory and stopping any leaks.

any help would be appreciated.

回答1:

Suppose, under ARC, you could declare a struct like this:

typedef struct {
    __strong NSObject *someObject;
    int someInteger;
} MyStruct;

Then you might write code like this:

MyStruct *thing = malloc(sizeof(MyStruct));

Problem: malloc doesn't zero-fill the memory it returns. So thing->someObject is some random value - not necessarily NULL. Then you assign a value to it like this:

thing->someObject = [[NSObject alloc] init];

Under the covers, ARC will turn that into code like this:

NSObject *temporary = [[NSObject alloc] init];
[thing->someObject release];
thing->someObject = temporary;

The problem here is that your program just sent release to some random value! Your app will probably crash at this point.

You might say that ARC should recognize the call to malloc and take care of setting someObject to NULL to prevent this. The problem is that malloc might be wrapped in some other function, like this:

void *myAllocate(size_t size) {
    void *p = malloc(size);
    if (!p) {
        // malloc failed.  Try to free up some memory.
        clearCaches();
        p = malloc(size);
    }
    return p;
}

OK, now ARC has to know about your myAllocate function too... and that might be inside some static library that you got as a binary.

Your app might even have its own memory allocators that recycle old allocations without using free and malloc every time. So even changing malloc to zero-fill the memory before returning it would not work. ARC would have to know about any custom allocators in your program.

It would be very, very hard to make this work reliably. So instead, the creators of ARC just gave up and said “Forget it. We're not going to let you put __strong and __weak references in structs.”

That's why you can only put an object pointer into a struct if you use __unsafe_unretained to tell ARC “Don't try to manage ownership of the object that this references.”

You can try to use a struct containing __unsafe_unretained object references, perhaps using CFRetain and CFRelease to manually retain and release them. Then you can create an array of such structs. This is error-prone, so you should only do it if the profiler tells you that it's critical for performance.

Instead, just create a new Objective-C class instead of a struct. Give the class an @property for each field you would have put in the struct. The use an NSMutableArray to manage an array of instances of this new class.



回答2:

Just more food for thought...

If you really need ARC pointers in non-objective-c code, you can use them natively in C++.

In fact, you can store ARC pointers in all the standard template containers, and they still retain (ha ha) their proper semantics.

struct Foo {
    NSDictionary *dictionary;
    NSString *string;
    __weak UIViewController *viewController;
};

std::list<UIView*> views;

Since the compiler treats ARC pointers as objects with non-trivial constructors, they can have their normal semantics.

All their ARC glory will be magically handled.



回答3:

ARC just loves to complain! Actually, we have to look at this historically. In the old-style Manual Reference Counted environment from way back, the compiler didn't complain because it knew everything memory related was going to be your job, and your job alone. But that changed when Apple introduced Automatic Reference Counting, because the compiler needed to get substantially more anal about what type an object was and what it was contained in, so that it knew how to properly manage the memory for said object efficiently. When you place an Objective-C object into a C-struct, you're sort of sticking out your tongue at the compiler because a struct implies that you will own and manage the memory of the items inside of it yourself (that and ARC doesn't touch malloc and free). That's where __unsafe_unretained comes in. With it, we tell the compiler that any and all memory operations will be your responsibility, just like in MRC. ARC literally "can't guarantee the safety" of the object's pointer being nil after deallocation, so it makes you explicitly declare it as such.

If you want to avoid all of this nonsense, just make your struct into a lightweight class and declare your objects normally. After all, classes in Objective-C are just C-structs-(ish) with a lot of Apple magic thrown in.



回答4:

Dont put objective C objects in structs. Thats why Obj C supports classes. You are coding C.