madsmtm / objc2

Bindings to Apple's frameworks in Rust
https://docs.rs/objc2/
MIT License
281 stars 35 forks source link

Converting a msg_send_id received NSArray to Vec #595

Closed josephjohnston closed 1 month ago

josephjohnston commented 1 month ago

When I model some Objective-C object type Value I'm confused on the best way to receive an NSArray of these objects and convert toVec<Id<Value>>.

Here I model some fictional Objective-C type Value as

#[derive(Debug)]
#[repr(C)]
pub struct Value(AnyObject);
unsafe impl RefEncode for Value {
    const ENCODING_REF: Encoding = AnyObject::ENCODING_REF;
}
unsafe impl Message for Value {}
impl ::std::ops::Deref for Value {
    type Target = AnyObject;
    #[inline]
    fn deref(&self) -> &AnyObject {
        &self.0
    }
}

Then calling method values with self some instance of struct Value I receive an NSArray of these values

let mut v: Vec<Id<Value>> = Vec::new();
let values: Id<NSArray> = msg_send_id![self, values];
values.iter_retained().map(|x| v.push(x));

The issue is x is of type Id<AnyObject> rather than Id<Value> because I typed values as Id<NSArray>. I can't type values as Id<NSArray<Value>> because Value doesn't implement the ClassType trait. I model many types as above and never have to deal with classes, and I hope to keep it that way. The following fails for the same reason

NSArray::to_vec_retained(msg_send_id![self, values])

In 0.3.0-beta.5 the following worked fine

NSArray::into_vec(msg_send_id![self, values])

I see you took away into_vec for lack of safety because the array is IsIdCloneable so may be cloned even when containing mutable elements. When I try the into_vec code you used, that is

array
    .to_vec()
    .into_iter()
    .map(|obj| unsafe {
        Id::retain(obj as *const T as *mut T).unwrap_unchecked()
    })
    .collect()

where T=Value here, this fails because to_vec converts to a vector of &AnyObject and then casting &AnyObject to *const T is an invalid cast. So I'm really not sure at this point how to properly convert an array to a vector when the type of objects in the array don't implement ClassType.

madsmtm commented 1 month ago

I assume you closed this because you figured out the problem by yourself, but for future reference, I'll give my two cents on this: What you're doing should be possible as just values.to_vec_retained(), and will (probably) eventually be fixed by https://github.com/madsmtm/objc2/issues/563.

But while it's not, the easiest fix is probably to use the code from to_vec_retained directly, with Value plugged in as the T:

let values: Id<NSArray<Value>> = msg_send_id![self, values];
let v: Vec<Id<Value>> = values.to_vec().into_iter().map(|obj| unsafe {
    Id::retain(obj as *const Value as *mut Value).unwrap_unchecked()
}).collect();
josephjohnston commented 1 month ago

Thanks, yes I realized I just needed to parameterize NSArray with Value