Objective C的键路径获取iTunes中所有艺术家

9

我正在使用键值编码从iTunes获取所有艺术家:

[self.iTunes valueForKeyPath:@"sources.@distinctUnionOfArrays.playlists.@distinctUnionOfArrays.tracks.artist"];

现在,这个很好运行。这是非常高效的。我也想用相同的方法处理相册。

[self.iTunes valueForKeyPath:@"sources.@distinctUnionOfArrays.playlists.@distinctUnionOfArrays.tracks.album"];

这里的问题是,有多个相同名称的专辑,但不一定是同一位艺术家的作品。有没有办法获取每个专辑中的一首歌曲,以便我可以找出它的艺术家,并获得其封面?我知道有NSPredicate,但这很慢。
具体代码并不重要,我只需要键值编码部分。
谢谢!
2个回答

4

这不是一个完整的答案。

为了像我这样最初不知道这个文档在哪里的人受益:您必须在安装了iTunes的Mac上运行命令。

sdef /Applications/iTunes.app | sdp -fh --basename iTunes

这将在您的当前工作目录中神奇地创建iTunes.h。然后,您可以浏览iTunes.h以查看API的外观。它似乎没有在Mac Developer Library或其他任何地方得到官方文档记录。

每个iTunesTrack除了album属性外,还有一个artist属性,因此我认为您真正想做的就是返回唯一元组数组(album,artist)

好的,那么我们如何从单个KVC查询中获取元组数组?我们希望编写类似于sources.@distinctUnionOfArrays.playlists.@distinctUnionOfArrays.tracks.albumAndArtist的内容,并将其返回像NSArray of NSDictionary这样的东西,例如@[ @{ @"Help!", @"The Beatles" }, @{ @"No!", @"They Might Be Giants" }, ... ]

编辑 ughoavgfhw在评论中提出了下一步建议:您可以使用类别来定义albumAndArtist,如下所示:

@interface iTunesTrack (albumAndArtist)
@property (readonly) NSDictionary *albumAndArtist;
@end

@implementation iTunesTrack (albumAndArtist)
-(NSDictionary *) albumAndArtist
{
    return @{ @"album":self.album, @"artist":self.artist };
}
@end

现在,您可以编写我们试图编写的代码行:

[self.iTunes valueForKeyPath:@"sources.@distinctUnionOfArrays.playlists.@distinctUnionOfArrays.tracks.albumAndArtist"];

这将为您提供一个NSArrayNSDictionary,基本上就是您想要的。请注意,我没有测试过这段代码或其他任何东西!


2
你可以使用一个分类来定义一个返回字典的 albumAndArtist 属性,然后使用 KVC 来获取所有不同的键值对。 - ughoavgfhw
你们真是天才!我只需要更改一下类别类就可以了。iTunesTrack不是一个真正的类,我必须使用SBObject来完成它。但是其他部分都非常完美,非常感谢你们! - IluTov
抱歉,我还有一个问题。它可以工作,但是不幸的是它仍然比使用内置属性慢得多。你能告诉我为什么吗? - IluTov
我尝试仅返回艺术家字符串,但仍然非常非常慢...-(NSString *) albumAndArtist { return [(ITunesFileTrack *)self album]; } - IluTov
1
关于“慢于内置属性”的问题:相对于返回现有NSString,构造新的NSDictionary速度较慢。关于“比仅返回艺术家更慢”的问题:我预计[self album]要比@{...}快很多,但它确实还在执行额外的方法调用。我天真地期望albumAndArtist { return [self album]; }比album慢两倍,因为有两倍的方法调用。如果您真的需要它运行得非常快,也许可以看一下http://www.mikeash.com/pyblog/performance-comparisons-of-common-operations.html。 - Quuxplusone
我仍然不明白为什么属性相册可以非常快,而一个简单的albumAndArtist { return [self album]; } 属性却可以非常非常慢,但我决定放手 :) - IluTov

1
@Ilija:如果你发帖说你已经放下了,那么你其实还没有真正放下。 ;) 让我看看我能否澄清我的评论
-(NSString *) album
{
    return self->album;
}

-(NSDictionary *) albumAndArtist
{
    return @{ @"album":self.album, @"artist":self.artist };
}

上面的album方法是Objective-C编译器会自动生成的。而albumAndArtist方法则是我在回答你原始问题时建议的。现在,如果你让Clang将这两个方法转换成C语言(clang -rewrite-objc test.m -o test.cc),你会得到类似下面的代码:
static NSDictionary * _I_iTunesTrack_albumAndArtist_albumAndArtist(iTunesTrack * self, SEL _cmd) {
    return ((NSDictionary *(*)(id, SEL, const id *, const id *, NSUInteger))(void *)
    objc_msgSend)(objc_getClass("NSDictionary"), sel_registerName("dictionaryWithObjects:forKeys:count:"),
    (const id *)__NSContainer_literal(2U, ((NSString *(*)(id, SEL))(void *)objc_msgSend)
    ((id)self, sel_registerName("album")), ((NSString *(*)(id, SEL))(void *)objc_msgSend)
    ((id)self, sel_registerName("artist"))).arr, (const id *)__NSContainer_literal(2U,
    (NSString *)&__NSConstantStringImpl_test_m_0, (NSString *)&__NSConstantStringImpl_test_m_1).arr, 2U);
}

或者用人类的语言来说,

-(NSDictionary *) albumAndArtist
{
    id album = objc_msgSend(self, sel_registerName("album"));
    id artist = objc_msgSend(self, sel_registerName("artist"));
    id *values = calloc(2, sizeof(id)); values[0] = album; values[1] = artist;
    id *keys = calloc(2, sizeof(id)); keys[0] = @"album"; keys[1] = @"artist";
    Class dict_class = objc_getClass("NSDictionary");
    id result = objc_msgSend(dict_class, sel_registerName("dictionaryWithObjects:forKeys:count:"), values, keys, 2);
    free(values); free(keys);
    return result;
}

看看这个:三个sel_registerName,一个objc_getClass,三个objc_msgSend,两个calloc和两个free。与编译器生成的album方法相比,这相当低效。

Technically, the compiler-generated album method looks like this:

-(NSString *) album
{
    return objc_getProperty(self, _cmd,
        __OFFSETOFIVAR__(struct iTunesTrack, album), /*atomic*/ YES);
}

but that's only because it wasn't originally declared as nonatomic. For the meaning of objc_getProperty, see here; but basically, it's faster than an objc_msgSend would have been.)

很明显,albumAndArtistalbum 要慢得多,因为它要做更多的额外工作。但是——你会问——如果我们摆脱所有这些工作,只返回 self.album 呢?那么,生成的代码仍然比编译器为 album getter 生成的代码更复杂:

-(NSString *) albumAndArtist_stripped_down
{
    // return self.album;
    return objc_msgSend(self, sel_registerName("album"));
}

当你的程序调用myTrack.album时,它会调用objc_msgSend一次来确定它应该调用album方法,然后在album内部调用objc_getProperty。这是两个调用。(如果您计算selector_registerName("album"),则为三个调用。)
当您的程序调用myTrack.albumAndArtist_stripped_down时,它会调用objc_msgSend一次来确定它应该调用albumAndArtist_stripped_down方法,然后那个调用objc_msgSend第二次,然后那个调用objc_getProperty。这是三个调用。(如果您计算selector_registerName,则为五个调用。)
因此,对我来说,albumAndArtist_stripped_down大约应该比单独的album慢两倍(或5/3倍)。
至于原始的albumAndArtist,仅通过计算函数调用次数,我预计它的速度将比album慢五倍左右...但当然它会比那慢得多,因为它至少进行了三次内存分配,而album没有进行任何内存分配。内存分配和清理非常昂贵,因为malloc本身就是一个复杂的算法。
希望这能为您解决问题。 :)

哇,非常感谢您的解释!我没想到它会有这么大的区别。为我澄清了事情 :) - IluTov
有了一些编程经验后,我意识到当时实际上发生了什么。实际上,这与非原子方法没有多大关系,而是与AppleScript有关。脚本桥在必要时会自动调用AppleScript,这非常昂贵。SBElementArray中有一些方法可以包装这些调用,因此如果您想要库中的每个曲目,它将一次性提供所有曲目。但是,如果您自己遍历数组,则必须为每个单独的对象进行调用。以防万一您想知道。 - IluTov

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