Objetivo C keypath para obter todos os artistas do iTunes

9

Estou usando a codificação de valores-chave para obter todos os artistas do iTunes:

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

Agora, isso funciona bem. Isso é muito eficiente. Eu também gostaria de fazer o mesmo com o álbum.

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

O problema aqui é que existem vários álbuns com o mesmo nome, mas não necessariamente do mesmo artista. Existe uma maneira de obter uma música de cada álbum, então eu posso descobrir quais são os artistas, e também obter a capa dele? Eu sei que há o NSPredicate, mas isso é muito lento.

O código específico não é importante, eu só preciso da parte de codificação do valor-chave.

Obrigado!

    
por NSAddict 10.09.2012 в 23:18
fonte

2 respostas

4

Esta não é uma resposta completa.

Para o benefício de pessoas como eu que originalmente não sabiam onde isso foi documentado: Você precisa estar em um Mac com o iTunes instalado e, em seguida, executar o comando

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

que criará magicamente iTunes.h em seu diretório de trabalho atual. Então você pode percorrer iTunes.h para ver como a API é exibida. Não parece ser oficialmente documentado na Biblioteca de Desenvolvedores Mac ou qualquer coisa.

Cada iTunesTrack tem uma propriedade artist além da propriedade album , então eu acho que tudo que você realmente quer fazer é retornar uma matriz de tuplas únicas (album, artist) .

Ok, então como podemos obter uma matriz de tuplas de uma única consulta do KVC? Gostaríamos de escrever algo como sources.@distinctUnionOfArrays.playlists.@distinctUnionOfArrays.tracks.albumAndArtist e ter algo como NSArray de NSDictionary , por exemplo @[ @{ @"Help!", @"The Beatles" }, @{ @"No!", @"They Might Be Giants" }, ... ]

EDIT ughoavgfhw sugeriu o próximo passo em um comentário: Você poderia usar uma categoria para definir albumAndArtist da seguinte forma:

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

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

E agora você pode escrever a linha que estávamos tentando escrever:

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

Isso deve dar a você NSArray de NSDictionary , que é basicamente o que você queria. Lembre-se, eu não testei este código nem nada!

    
por Quuxplusone 19.09.2012 / 21:01
fonte
1
@Ilija: Se você postar para dizer que você o deixou ir, você ainda não o deixou ir. ;) Deixe-me ver se consigo esclarecer meu comentário :

-(NSString *) album
{
    return self->album;
}

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

O método album acima é o que o compilador Objective-C irá gerar para você automaticamente. * O método albumAndArtist é o que eu sugeri na minha resposta à sua pergunta original. Agora, se você pedir a Clang para abaixar esses dois métodos para C ( clang -rewrite-objc test.m -o test.cc ), você terá algo assim:

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);
}

ou, em termos humanos,

-(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;
}

Verifique: três sel_registerName s, um objc_getClass , três objc_msgSend s, dois calloc s e dois free s. Isso é bastante ineficiente, comparado ao método album gerado pelo compilador.

  

Tecnicamente, o método album gerado pelo compilador se parece com isto:

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

mas é só porque não foi originalmente declarado como nonatomic . Para o significado de objc_getProperty , consulte aqui ; mas basicamente, é mais rápido que um objc_msgSend teria sido.)

Então, claramente albumAndArtist será muito mais lento que album , por causa de todo o trabalho extra que está fazendo. Mas - você pergunta - e se nos livrarmos de todo esse trabalho e apenas retornarmos self.album ? Bem, o código gerado ainda é mais do que o compilador gerado para o album getter:

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

Quando o programa chama myTrack.album , invoca objc_msgSend uma vez para descobrir que deve chamar o método album e, em seguida, dentro de album , chama objc_getProperty . São duas chamadas. (Três, se você contar selector_registerName("album") .)

Quando o programa chama myTrack.albumAndArtist_stripped_down , invoca objc_msgSend uma vez para descobrir que deve chamar o método albumAndArtist_stripped_down e, em seguida, esse chama objc_msgSend uma segunda vez e, em seguida isso chama objc_getProperty . São três chamadas. (Cinco, se você contar selector_registerName .)

Então, faz sentido para mim que albumAndArtist_stripped_down deva ser duas vezes mais lento (ou 5/3 como lento) do que album por si só.

E quanto ao original albumAndArtist , apenas contando as chamadas de função, eu esperaria que ele fosse cerca de cinco vezes mais lento que album ... mas é claro que será muito mais lento do que isso, porque ele está fazendo pelo menos três alocações de memória, enquanto album não está fazendo nada. A alocação e a limpeza de memória são muito caras, porque malloc é um algoritmo complicado por si só.

Espero que isso elimine o problema para você. :)

    
por Quuxplusone 11.10.2012 / 20:35
fonte