Улучшение производительности сценариев кода моста
Сценарии кода Моста отличаются от большей части кода Objective C, в котором это включает два процесса — Ваш процесс и scriptable приложение — и использует события Apple в качестве пути к тем процессам для передачи. Из-за этой архитектуры производительность может быть проблемой. Однако существует несколько вещей, которые можно сделать для оптимизации производительности.
Оценка ссылок
Как AppleScript, Пишущий сценарий Моста использует события Apple, чтобы отправить и получить информацию от других приложений. Однако, потому что отправка событий Apple может быть дорогой, Писание сценарий Моста избегает отправлять события Apple, пока это не абсолютно необходимо или пока Вы не запрашиваете его.
Отложенные вычисления
Как описано в Объектных Спецификаторах и Оценке, пишущие сценарий объекты являются фактически объектными спецификаторами, которые являются ссылками, определяющими местоположение объекта в целевом приложении. То, когда Вы просите объект из приложения, что Вы фактически возвращаете, является ссылкой на него в терминах определения сценариев; оценка ссылки отправляет событие Apple в приложение, возвращающее более точную, «каноническую» ссылку. Сценарии Моста улучшают производительность посредством консервативной оценки ссылок. Обычно, это не оценит ссылку, пока Вам не будут нужны некоторые конкретные данные от него, которые всегда являются значением свойства объекта. Это вызывают отложенными вычислениями.
Например, Сценарии Моста не отправят событие Apple, когда Вы попросите первый диск Средства поиска, но это отправит событие, когда Вы просите имя первого диска Средства поиска. Перечисление 3-1 иллюстрирует это поведение.
Перечисление 3-1 пример отложенных вычислений
- (IBAction)doTest:(id)sender { |
FinderApplication *finder = [SBApplication applicationWithBundleIdentifier:@"com.apple.finder"]; |
SBElementArray *disks = [finder disks]; |
FinderDisk *disk = [disks objectAtIndex:0]; |
NSString *name = [disk name]; // lazy evaluation occurs |
NSLog(@"Name of first disk is %@", name); |
} |
Большую часть времени эти отложенные вычисления ссылок не будут иметь большого значения для Вас. Однако иногда необходимо стараться гарантировать, чтобы Вы получили поведение, которое Вы ожидаете. Рассмотрите код в Перечислении 3-2.
Эффект перечисления 3-2 прошедшего времени на отложенных вычислениях
- (IBAction)doTest:(id)sender { |
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"]; |
iTunesTrack *savedTrack = [iTunes currentTrack]; |
sleep(600); |
NSLog(@"Current track is %@", [savedTrack name]); |
} |
На первый взгляд могло бы казаться, что эти журналы кода, которые имя любой дорожки играло 10 минут назад, когда это добирается до сути строка. Вместо этого журналы кода имя любой дорожки в настоящее время играют. Почему это так? Вспомните, что Сценарии Моста имеют дело просто со ссылками на объекты, пока Вам фактически не нужны некоторые конкретные данные от них.И что же? savedTrack
хранилища являются ссылкой на любую дорожку, в настоящее время играет. Эта ссылка оценена, и данные возвращаются только, когда Вы вызываете name
метод, происходящий 10 минут спустя.
Принуждение оценки
Но что, если это не то, что Вы хотите? Что, если Вы хотите текущую дорожку “теперь"? Для этого необходимо вынудить ссылку текущей дорожки быть оцененной, как только она создается.
Для принуждения оценки, Вы используете get
метод, объявляющийся SBObject
. В действительности, get
метод говорит, что Мост Сценариев «прекращает быть ленивым — я хочу, чтобы Вы оценили этот объект теперь». Следующий код немного пересматривает код в Перечислении 3-2 для использования get
и таким образом измените получающееся имя дорожки:
- (IBAction)doTest:(id)sender { |
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"]; |
iTunesTrack *savedTrack = [[iTunes currentTrack] get]; |
sleep(600); |
NSLog(@"Current track is %@", [savedTrack name]); |
} |
Поскольку этот код использует get
метод, savedTrack
всегда содержит ссылку на дорожку, игравшую когда get
метод выполнился.
В полной мере пользование ленью
Несмотря на то, что Вы не можете хотеть Писать сценарий Моста, чтобы быть ленивыми в особой ситуации, ее отложенные вычисления ссылок существенно сокращают количество событий Apple, которые должны быть отправлены. Следовательно, Ваше приложение может работать значительно быстрее, чем оно было бы иначе. Однако, если Вы не осторожны относительно того, как Вы пишете свой код, Вы могли бы напрасно обойти лень Моста Сценариев, таким образом вынудив его отправить больше событий Apple, чем необходимый. Следующее является двумя распространенными ошибками:
Вызов
get
метод, когда Вы не должны. Сценарии Моста превосходны при определении, когда это должно отправить событие Apple для получения конкретных данных, Вы хотите. Когда Вы пишете[someObject name]
, например, Сценарии Моста автоматически отправляют событие Apple для выборки имени объекта. Если Вы вместо этого пишете[[someObject get] name]
, Вы вынуждаете Мост Сценариев отправить два события Apple вместо одного.Отправка того же сообщения многократно. Каждый раз Вы просите у объекта значение свойства — такого как его имя — Вы форсируете событие Apple, которое будет отправлено. Таким образом, в следующем примере, до трех событий Apple могли бы быть отправлены:
- (IBAction)doTest: {
FinderApplication *finder = [SBApplication applicationWithBundleIdentifier:@"com.apple.finder"];
SBElementArray *disks = [finder disks];
if ([[[disks objectAtIndex:0] name] isEqualToString:@"Macintosh HD"]) { // 1st AE sent
NSLog(@"The first disk's name is Macintosh HD");
} else if ([[[disks objectAtIndex:0] name] isEqualToString:@"Disk 1"]) {
// If execution reaches here, second Apple event sent
NSLog(@"The first disk's name is Disk 1");
} else if ([[[disks objectAtIndex:0] name] isEqualToString:@"My Disk"]) {
// If execution reaches here, third Apple event sent
NSLog(@"The first disk's name is My Disk");
}
}
Чтобы гарантировать, что только единственное событие Apple отправляется, вызовите
name
метод только один раз и хранилище значение в переменной.- (IBAction)doTest: {
FinderApplication *finder = [SBApplication applicationWithBundleIdentifier:@"com.apple.finder"];
SBElementArray *disks = [finder disks];
NSString *name = [disks objectAtIndex:0];
if ([name isEqualToString:@"Macintosh HD"]) {
NSLog(@"The first disk's name is Macintosh HD");
} else if ([name isEqualToString:@"Disk 1"]) {
NSLog(@"The first disk's name is Disk 1");
} else if ([name isEqualToString:@"My Disk"]) {
NSLog(@"The first disk's name is My Disk");
}
}
Эффективно перечисление и фильтрация массивов
Сценарии Моста оптимизируют SBElementArray
объекты, а также SBObject
объекты путем создания их ленивыми когда дело доходит до событий Apple. Они не отправляют событий Apple до абсолютно необходимый. Когда Вы вручную выполняете итерации через SBElementArray
объект, однако, Вы вынуждаете Мост Сценариев отправить столько же событий Apple, сколько существуют элементы в Вашем массиве. Возьмите следующий пример, составляющий список имени каждого диска в Средстве поиска:
- (IBAction)doTest:(id)sender { |
FinderApplication *finder = [SBApplication applicationWithBundleIdentifier:@"com.apple.finder"]; |
SBElementArray *disks = [finder disks]; |
NSMutableArray *nameArray = [NSMutableArray arrayWithCapacity:[disks count]]; |
for (FinderDisk *currentDisk in disks) { |
[nameArray addObject:[currentDisk name]]; |
} |
Этот код чрезвычайно неэффективен. Это требует, чтобы Мост Сценариев отправил событие Apple для получения числа дисков. Затем каждый раз через цикл быстрого перечисления, это отправляет дополнительное событие Apple для получения имени текущего диска.
Как обсуждено в Использовании Массивов Элемента, каждый раз, когда возможный необходимо всегда использовать один из методов массива «пакетной обработки» вместо того, чтобы перечислить массив. Эти методы избегают неэффективности перечисления, потому что они отправляют единственное событие Apple, а не одно событие Apple на элемент в массиве. Методы для использования makeObjectsPerformSelector:withObject:
и filteredArrayUsingPredicate:
из NSArray
, и arrayByApplyingSelector:
и arrayByApplyingSelector:withObject:
из SBElementArray
.
Например, Вы могли переписать метод выше как показано в Перечислении 3-3.
Перечисление 3-3 , Обрабатывающее массив с arrayByApplyingSelector:
- (IBAction)doTest:(id)sender { |
FinderApplication *finder = [SBApplication applicationWithBundleIdentifier:@"com.apple.finder"]; |
SBElementArray *disks = [finder disks]; |
NSArray *nameArray = [disks arrayByApplyingSelector:@selector(name)]; |
// or you could use valueForKey: (NSArray), e.g. |
// NSArray *nameArray = [disks valueForKey:@"name"]; |
} |
Этот код отправляет только единственное событие Apple.
Тестирование на запущенные приложения
Если приложение не выполняется, когда Сценарии Моста пытаются отправить его, событие Apple, Пишущий сценарий Моста может автоматически запустить его. Запуск другого приложения может стать неожиданностью для Ваших пользователей, вместе с фактом, что Вы, выполнение приложения блокируется, в то время как запускается другое приложение.
Из-за этих потенциальных неожиданностей это часто - хорошая идея проверить, работает ли целевое приложение, прежде чем Вы попытаетесь связаться с сообщением Моста Сценариев. Предположим, что Вы хотите получить имя текущей дорожки, но только если работает iTunes. Вы могли выполнить первым тестированием на выполнение приложений с isRunning
из SBApplication
, как показано в Перечислении 3-4.
Тестирование перечисления 3-4 на выполнение приложений
- (NSString *) nameOfCurrentTrack |
{ |
// "iTunes" is an instance variable |
if ([iTunes isRunning]) { |
return [[iTunes currentTrack] name]; |
} |
return nil; |
} |