Действительно динамично. На нем даже не выполняется статическая проверка типов (если вы не объявляете список протоколов для переменной). Он определяется просто как указатель на объект. Например, вы можете сделать это:
Class classes[3];
classes[0] = [NSMutableString class];
classes[1] = [NSMutableArray class];
classes[2] = [NSMutableData class];
srandom(time(NULL));
id foo = [[classes[random % 3] alloc] init];
NSLog(@"It is a %@", [foo class]);