非保留委托数组

43
在 Cocoa Touch 项目中,我需要一个特定的类不仅有单个委托对象,而且有多个委托对象。
看起来我应该为这些委托创建一个 NSArray; 问题是 NSArray 会保留所有这些委托,但它不应该这样做(按照惯例,对象不应该保留它们的委托)。
是否应该编写自己的数组类来防止保留或者是否有更简单的方法? 谢谢!

3
你几乎肯定不想这样做。在这种情况下,你的代码通常应该广播 NSNotification,让多个对象(问题中的代理对象)进行订阅。 - Mike Abdullah
4
Mike,恐怕我需要 - 出于控制和安全的原因。我想知道有哪些东西与我的委托人相关联。 - wh1t3cat1k
2
我建议不要与框架对抗,而是使用带有NSPointerFunctionsWeakMemory NSPointerFunctionOption的NSPointerArray。 - leviathan
完全有效的要求 - dave
10个回答

48

我一段时间前找到了这段代码(无法记得归功于谁)。

它非常巧妙地使用一个类别,通过使用CFArray和适当的回调来支持创建不进行保留/释放操作的可变数组。

@implementation NSMutableArray (WeakReferences)
    + (id)mutableArrayUsingWeakReferences {
    return [self mutableArrayUsingWeakReferencesWithCapacity:0];
    }

    + (id)mutableArrayUsingWeakReferencesWithCapacity:(NSUInteger)capacity {
    CFArrayCallBacks callbacks = {0, NULL, NULL, CFCopyDescription, CFEqual};
    // We create a weak reference array
    return (id)(CFArrayCreateMutable(0, capacity, &callbacks));
    }
@end

编辑 找到了原始文章:http://ofcodeandmen.poltras.com


3
就像使用临时的 NSValue 对象来存储弱引用一样,我认为这种解决方案不会存储真正的弱引用,如果目标对象被释放,它就会设置为 nil。虽然这只是我的猜测-如果我错了,请纠正我。最安全的方法可能仍然是定义自己的帮助对象,提供一个弱属性,并将该帮助对象存储在集合(数组、字典、集合)中,如在 NSArray 的用法注释中建议的那样。 - Thomas Tempelmann
事实上,我对一个类似的问题添加了一个答案,可以在这里看到:https://dev59.com/l2ox5IYBdhLWcg3wOx_9#13351665 - Thomas Tempelmann
刚在iOS 10上进行了测试。我不相信它还能工作,因为被添加的元素是一个字符串,如:"iphoneos10.1"。虽然非常奇怪。 - Paul Razvan Berg
或者,您可以在客户端代码中使用CFArrayCreateMutable(...),并像使用NSMutableArray一样使用它(例如[(NSMutableArray *)myCFArray addObject:aDelegate]或只是[(id)myCFArray addObject:aDelegate])。这是因为NSMutableArrayCFMutableArrayRef是“无桥接转换”的。更多信息请参见苹果开发者关于无桥接转换的文章 - Slipp D. Thompson

27

我在此提出了早期答案中一个重要的限制,以及解释和改进方法。

Johnmph建议使用[NSValue valueWithNonretainedObject:]

请注意,当您这样做时,您的引用在NSValue对象内部的行为不像 __weak ,而更像 __unsafe_unretained 。 更具体地说,如果对象在那个时间之前已被释放,当您尝试获取回您的引用(使用[myNSValue nonretainedObjectValue])时,您的应用程序将崩溃并显示EXC_BAD_ACCESS信号!

换句话说,在 NSValue 对象内部,弱引用不会自动设置为nil。这让我花费了很多时间来解决。我通过创建一个只有一个弱引用属性的简单类来解决了这个问题。

更美妙的是,通过使用 NSProxy ,我们可以完全将包装器对象视为其所包含的对象本身!

// WeakRef.h
@interface WeakRef : NSProxy

@property (weak) id ref;
- (id)initWithObject:(id)object;

@end


// WeakRef.m
@implementation WeakRef

- (id)initWithObject:(id)object
{
    self.ref = object;
    return self;
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    invocation.target = self.ref;
    [invocation invoke];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return [self.ref methodSignatureForSelector:sel];
}

@end

天啊...太棒了!谢谢!NSProxy看起来很有趣。 - walkingbrad
1
注意:为了使 NSProxy 的性能最佳,除了实现 -forwardInvocation: 之外,您还应该实现 - (id)forwardingTargetForSelector:(SEL)aSelector。 _(我知道,我知道,-forwardingTargetForSelector: 不在 NSProxyNSObject 协议声明中,但 Obj-C 运行时仍然会用于 NSProxy 子类)_。此外,运行时将首先检查 - (BOOL)respondsToSelector:(SEL)aSelector _(有时还包括 - (BOOL)conformsToProtocol:(Protocol *)aProtocol)_,因此您需要实现这些方法才能使用 -forwardingTargetForSelector。但速度差异是值得的! - Slipp D. Thompson

17

查看NSValue valueWithNonretainedObject方法的文档:

当将一个对象添加到集合对象(例如NSArray或NSDictionary的实例)时,此方法非常有用,可以防止该对象被保留。


27
请注意,当您这样做时,您的引用并不像__weak那样起作用,而是在NSValue对象内部像__unsafe_unretained一样。更具体地说,当您尝试获取引用(使用[myNSValue nonretainedObjectValue])时,如果对象在此之前已被释放,您的应用程序将崩溃,并发出EXC_BAD_ACCESS信号。换句话说,在NSValue对象内部,弱引用不会自动设置为nil。我花了好几个小时才理解这一点。我通过创建一个仅具有弱引用属性的简单类来解决了这个问题。将其添加到数组中,就完成了! - Timo
2
@Timo你的评论需要更多的关注度。请将其发布为答案。 - codrut
我已将我的评论发布为答案,并添加了一个性感的替代方案,利用了NSProxy。 - Timo

13

我建议不要与框架斗争,而是使用NSPointerArrayNSPointerFunctionsWeakMemory NSPointerFunctionOption,像这样:

NSPointerArray *weakReferencingArray = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsWeakMemory];

// NSPointerFunctionsWeakMemory - Uses weak read and write barriers 
// appropriate for ARC or GC. Using NSPointerFunctionsWeakMemory 
// object references will turn to NULL on last release.

在我需要设计一个委托数组并自动将引用设置为NULL的情况下,它在场景中为我服务得很好。


12

你不应该这么做!Cocoa Touch 提供了多种发送事件的概念,你应该针对每种情况使用适当的概念。

  1. 目标-动作(Target-action):用于UI控件,例如按钮按下。一个发送者,零个或多个接收者。
  2. 代理(Delegates):仅用于一个发送者和一个接收者。
  3. 通知(Notification):用于一个发送者和零个或多个接收者。
  4. KVO:比通知更细粒度。

你应该查看如何使用NSNotificationCenter类来发送通知,这是向多个接收者发送通知的正确方式。


虽然这不是原作者想要的答案,但我需要有人把它讲清楚,并加上一些感叹号以确保我听到了... :) - Oded Ben Dov
10
然而,有些情况下,一组未被保留的对象是有用的。 - matt
1
@matt - 对于在Mac OS X上使用NSHashMap的少数和罕见情况,或者可以在iOS和Mac OS X上都使用CFDictionary - PeyloW

4
这个来自NIMBUS的更为简单:
NSMutableArray* NICreateNonRetainingMutableArray(void) {
  return (NSMutableArray *)CFArrayCreateMutable(nil, 0, nil);
}

NSMutableDictionary* NICreateNonRetainingMutableDictionary(void) {
  return (NSMutableDictionary *)CFDictionaryCreateMutable(nil, 0, nil, nil);
}

NSMutableSet* NICreateNonRetainingMutableSet(void) {
  return (NSMutableSet *)CFSetCreateMutable(nil, 0, nil);
}

3

关键词:NSHashTable,在文档中进行搜索。


天啊,我不知道我怎么会不知道(忘了?)这个。更多需要说一下这个福音类的事情。根据苹果文档所述:哈希表是基于NSSet进行建模的,具有以下区别:1. 它可以保存其成员的弱引用。 2.其成员可以在输入时被复制,也可以使用指针标识相等和散列。3.它可以包含任意指针(其成员不受对象约束)。简而言之:这是一个“正确”的答案——使用由NeXT/Apple专门设计来解决您问题的内置NSFoundation类。 - Slipp D. Thompson

2

我在Three20项目中找到了一些关于这个主题的代码片段,希望这能帮到你...

NSMutableArray* TTCreateNonRetainingArray() {
  CFArrayCallBacks callbacks = kCFTypeArrayCallBacks;
  callbacks.retain = TTRetainNoOp;
  callbacks.release = TTReleaseNoOp;
  return (NSMutableArray*)CFArrayCreateMutable(nil, 0, &callbacks);
}


NSMutableDictionary* TTCreateNonRetainingDictionary() {
  CFDictionaryKeyCallBacks keyCallbacks = kCFTypeDictionaryKeyCallBacks;
  CFDictionaryValueCallBacks callbacks = kCFTypeDictionaryValueCallBacks;
  callbacks.retain = TTRetainNoOp;
  callbacks.release = TTReleaseNoOp;
  return (NSMutableDictionary*)CFDictionaryCreateMutable(nil, 0, &keyCallbacks, &callbacks);
}

0

将其存储在数组或字典中如何?

__weak typeof(pointer) weakPointer = pointer;

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接