【问题标题】:Objective C - NSCollection w/NSArrayController refresh not consistentObjective C - NSCollection w/NSArrayController 刷新不一致
【发布时间】:2014-08-09 15:59:02
【问题描述】:

我编写了一个程序来播放歌曲队列,这些歌曲在后台任务中使用委托进行处理。我将回复发送回AppController 以移动NSSlider 显示正在播放的歌曲的进度并删除刚刚播放的NSCollection View 实体(歌曲)。大多数时候效果很好,但在两三首歌之后;实体消失但其余对象不会重新排列(在屏幕上向上移动),直到我使用鼠标或键盘使主屏幕成为焦点。我对其他类似问题使用了几个建议,并且通常得到相同的响应。我现在使用的代码:

-(void) songPlayer:(SongPlayer *)player removeEntity:(DesktopEntity *)entity {
    int i = 0;
    NSIndexSet *indexes;
    indexes = [NSIndexSet indexSetWithIndex:i];
    NSLog(@"about to try");
    @try {

    //    [_arrayController removeObjectAtArrangedObjectIndex:0];
        [_seqFile removeObjectAtIndex:0];
        [_songs removeObjectAtIndex:0];

        [_myCollectionView setContent:_songs];
        [_arrayController rearrangeObjects];
        [_myCollectionView setNeedsDisplay:YES];
    //    [_arrayController rearrangeObjects];
    //    [_window makeKeyAndOrderFront:self];
    //    [_myCollectionView setNeedsDisplay:YES];
    //    [self.seqFile reloadData];

    }
    @catch (NSException *exception) {
        NSLog(@"Ugly Ending Caught");

    }
    @finally {
        NSLog(@"Each Time");
        }
  }

我留下了注释代码,以显示我尝试过的其他事情以获得相同的结果。我相信它必须与刷新 NSView 相关,但不确定如何。

我的线程设置如下:

songPlayer.h

@protocol SongPlayerDelegate;
@interface SongPlayer : NSObject <SliderDelegate> {
    id<SongPlayerDelegate> __unsafe_unretained delegate;
}
@property (nonatomic, assign) id<SongPlayerDelegate> delegate;
…
@end
@protocol SongPlayerDelegate <NSObject>
- (void)songPlayer:(SongPlayer *)player removeEntity:(id)enity;
@end

歌曲播放器.M

#import "SongPlayer.h"
@implementation SongPlayer
@synthesize delegate;
-(id) init {
    if (self = [super init]) {
    }
    ...
    return self;
}

-(id) playMyQueue:(NSMutableArray *)q {
    //playsong stuff
    …
    [self.delegate songPlayer:self removeEntity:entity];
}

appController.h

#import "SongPlayer.h"
@interface AppController : NSObject <SongPlayerDelegate>
@property (weak) IBOutlet NSCollectionView *myCollectionView;
@property IBOutlet NSArrayController *arrayController;
@property (strong) NSMutableArray *songs;

@property SongPlayer *myPlayer;

- (void)songPlayer:(SongPlayer *)player removeEntity:(id)enity;

appController.m

#import "AppController.h"
@implementation AppController
@synthesize songs = _songs;
-(void) awakeFromNib {
    _songs = [[NSMutableArray alloc] init];
    [_arrayController addObject:s];  
    …
    _myPlayer = [[SongPlayer alloc] init];
…
}
-(IBAction)previewSong:(id)sender {
    if ([_myPlayer qActive]) {
        [(SongPlayer *)_myPlayer stopQueue];
        [_songLabel setStringValue:@"playback has been stopped"];
    } else {
        if (![_myPlayer isDone]) {
            [(SongPlayer *)_myPlayer stopSong];
            [_songLabel setStringValue:@"playback has been stopped"];
    } else {
            [_queue removeAllObjects];
            _myPlayer.delegate = self;
            NSMutableArray *activeQueue =
                [[NSMutableArray alloc] initWithArray:_seqFile];
            [_myPlayer performSelectorInBackground:@selector(playMyQueue:)
                withObject:activeQueue];
            }
      }
}

-(void) songPlayer:(SongPlayer *)player removeEntity:(DesktopEntity *)entity {
    int i = 0;
    NSIndexSet *indexes;
    indexes = [NSIndexSet indexSetWithIndex:i];
    NSLog(@"about to try");
    @try {
        [_arrayController removeObjectAtArrangedObjectIndex:0];
        [_seqFile removeObjectAtIndex:0];
    }
    @catch (NSException *exception) {
        NSLog(@"Ugly Ending Caught");
    }
    @finally {
        NSLog(@"Each Time");
    }
}

【问题讨论】:

  • 您不得尝试从后台线程更新 GUI——包括修改绑定到 GUI 的任何模型或控制器属性。所有这些修改都必须在主线程上完成。
  • 感谢您的回复。这就是我使用代表的原因。 removeEntity 方法在主线程的 AppController 中。当后台程序通知歌曲完成时执行。
  • 委托不解决任何线程问题。 “AppController”不是主线程“的”。对象并不存在于特定线程上。如果您有在后台线程上运行的代码,并且它正在调用方法而不使用特定于线程的 API,例如 -performSelectorOnMainThread:... 或带有 dispatch_get_main_queue() 的 GCD 专门在主线程上执行此操作,那么该方法调用发生在后台线程也是。
  • Ken,对术语感到抱歉,我对 Objective C Cocoa 没有我应有的熟悉。我在做performSelectorOnMainThread... 代码设置如下:
  • 您使用的是-performSelector InBackground :,而不是OnMainThread。这与我所说的相反。您在后台运行一些工作,只要它不与 GUI 交互就可以了。如果有一些东西需要更新 GUI,那么你需要将它分流到主线程。

标签: objective-c macos delegates nscollectionview


【解决方案1】:

在您的 -previewSong: 方法中,您可以:

        [_myPlayer performSelectorInBackground:@selector(playMyQueue:)
            withObject:activeQueue];

所以,-playMyQueue: 在后台线程上运行。然后它会:

[self.delegate songPlayer:self removeEntity:entity];

由于您没有将这项工作分流到另一个线程,因此它与-playMyQueue: 的其余部分发生在同一个后台线程上。同样,在-songPlayer:removeEntity: 中完成的数组控制器操作是在后台线程上完成的,并且由于 GUI 绑定到该数组控制器,因此从后台线程更新 GUI,这将无法可靠地工作。

您可以通过以下方式解决此问题:

dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate songPlayer:self removeEntity:entity];
});

除了从后台线程更新 GUI 的问题之外,您还有一般线程安全的问题。您必须同步对来自多个线程的共享数据结构的访问。例如,如果任何线程可以改变一个可变数组的内容,那么它必须在其改变的持续时间内拥有对该数组的独占访问权。您甚至不能让其他线程读取该数组,因为它们可能会在它被修改的过程中处于不确定状态时访问它。让多个线程同时从数组中读取是安全的,只要它们都不会改变它。

这适用于数组控制器的内容数组和您的 _seqFile 数组。

【讨论】:

  • 肯,非常感谢;我不知道有多可怕。我更改了滑块和集合视图的命令,两者都在进行我的初始测试。我已经开始在 Apple.com 上阅读有关 OSX 线程的信息,以便更好地掌握。再次感谢您的帮助。
猜你喜欢
  • 2011-03-24
  • 2016-01-28
  • 1970-01-01
  • 2018-12-30
  • 1970-01-01
  • 2023-04-02
  • 2021-03-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多