Pull to refresh

Исследуем iOS SDK и используем недокументированные API

Reading time 19 min
Views 33K
Из этой главы, да и из всей этой книги понятно, что самые лакомые куски программирования под iOS включены в публичные фреймворки, но не в SDK. Неофициальная политика Apple насчет этого проста: вы можете всё это использовать, но только на свой страх и риск. Ваш код может сломаться при следующем обновлении прошивки. Вам самим придётся искать компромисс между риском и прибылью.

Erica Sadun, The iPhone Developer's CookBook
Оригинал
As you’ve seen in this chapter, and throughout this book, some of the nicest bits of iPhone programming are included in the public iPhone frameworks but not in the SDK. Apple’s unofficial policy on this is clear:You can use these items in your programs, but you do so at your own risk.Your code may break at each firmware release. Striking the balance between risk and reward is up to you.

Дисклеймеры


  • Приведенные здесь куски кода работают на обычных iPhone (включая 4S) и iPad (включая new iPad) и не требуют jailbreak.
  • Все решения написаны и протестированы на iOS 5. Все решения также протестированы на совместимость с iOS 4.3, т.е. работают с iOS 4, если не сказано обратное. Основная часть статьи была написана до выхода iOS 6, так что приведенные решения не тестировались на совместимость с iOS 6.
  • Использование недокументированных API может привести к тому, что ваше приложение не допустят в AppStore. А может и не привести :-)
    Для тех, кому интересно, как Apple опрделяет использование приваетных API:«Как Apple узнаёт, что ты используешь приватные API?»
  • Apple может изменить реализацию вместе со следующим релизом iOS, и в вашем коде что-то сломается. Впрочем, это решаемо, и ничем принципиально не отличается от реализации обратной совместимости для документированных API. Ниже я рассмотрел эту проблему чуть подробнее.
  • Я не могу гарантировать, что у найденных мной API нет побочных эффектов. Используйте на свой страх и риск.
  • Лицензионное соглашение Apple Developer Program запрещает реверс-инжинеринг iOS.
  • Статья в процессе доработки. Конструктивная критика приветствуется!

Краткая инструкция по поиску в SDK


Допустим, вам нужно сделать что-то, выходящее за рамки официальной документации. Например, изменить уровень подсветки экрана (до iOS 5 этого не было в документированной части SDK). Известно, что программисты Apple как правило дают функциям и переменным осмысленные и выразительные названия, этим мы и воспользуемся для поиска в SDK. Для этого выберем несколько слов, относящихся к теме, например, brightness, level, screen. Запустим скрипт LookSDKForSymbol.sh (это моя обертка над nm; об этом скрипте и других используемых инструментах написано далее, в разделе «Инструменты») с ключевыми словами в качестве параметров. Скрипт выдаёт найденные в объектном файле символы (т.е. названия классов, функций, переменных). Пример выдачи:
$ LookSDKForSymbol.sh light level
U _UIBacklightLevelChangedNotification
Found in ./System/Library/CoreServices/SpringBoard.app/SpringBoard

001b43c4 t -[UIApplication backlightLevel]
001b4360 t -[UIApplication setBacklightLevel:]
0025ce54 t -[UIDevice _backlightLevel]
0025ce40 t -[UIDevice _setBacklightLevel:]
… и ещё несколько десятков символов

Большую часть результатов можно сразу отбросить, например -[UIApplication backlightLevel] возвращает значение подсветки, а не устанавливает его.

Оставшиеся, если их не более нескольких десятков, можно попытаться скормить гуглу. Случается, что кто-то уже занимался исследованием API, связанных с найденными символами, и в этом случае задача, считай, решена. В более сложных случаях приходится заниматься реверс-инжинерингом, то есть выяснять, как работают найденные функции, как использовать найденных оповещения и тому подобное.

Символьные строки, выдаваемые утилитой, делятся на следующие категории:
  1. Objective-C и С++ функции, классы, структуры и так далее. Всё что относится к Objective-C содержит квадратные скобки([]) либо знаки доллара ($). C++ функции как правило содержатся в каком-нибудь namespace'е, и поэтому в их названии содержиться символ разрешения пространства имён, два двоеточия (::).
  2. Objective-C блоки. Они имеют следующий общий вид:
    	___{число}{вызывающая данный блок функция}_block_invoke_{число2}
    

    Например:
    	___22-[AXSystemServer init]_block_invoke_0
    
  3. Pure C функции.
  4. Objective-C оповещения. Заканчиваются на Notification, например _SBMenuButtonPressedNotification.
  5. Ключи/константы. Обычно начинаются на k, например: _kCFUserNotificationAlternateButtonTitleKey.

Дальнейшие действия зависят от категории символа.
  1. Генерируем заголовочный файл для данного фреймворка:
    class-dump-z Foundation > $/iOS_private_headers/Foundation.h

    В большинстве случаев, сгенерированного заголовчного файла достаточно: в нем должны быть довольно хорошо описаны иерархии наследования классов, структуры, методы и т.д, чтобы потратив немного времени можно разобраться с API и использовать его в своём предложении.

    К сожалению, иногда информации содержащейся в заголовочном файле недостаточно, чтобы заставить код работать, и тогда приходится анализировать ассемблерный код, сгенерированный otool.

    Hint по дизассемблированию Objective-C кода: почти наверняка вы столкнетесь с вызовами функций типа objc_msgSend (отправка сообщения объекту). В качестве первого параметра всегда идет указатель на объект, а вторым — указатель на селектор (selector), т.е. указатель на строку, являющуюся названием метода (остальные «обычные» аргументы идут третьим, четвертым и т.д. аргументами). Определить, что за сообщение отправляется в данном случае, поможет hexdump.
  2. Про это можно сразу забыть. Блоки (обычно) локальны, их нельзя вызвать из своего кода.
  3. Самый сложный вариант. В самых простых случаях можно подобрать сигнатуру для функции, в остальных — только дизассемблирование. Больше об этом можно узнать в разделе «Как узнать сигнатуру неизвестной функции?».
  4. Начнем с того, что попытаемся отловить оповещения в одном из трёх основных центров оповещений (это Local, Darwin и CoreTelephony). Если оповещения такого типа не приходят, дело может быть в одной из двух вещей:
    — Оповещения такого типа приходитя в отдельный, специальный центр оповещений. Cледует поискать следы такого центра оповещений в том же фреймворке, к каторому принадлежит найденное оповещение.
    — Доставка оповещений отключена. Попытаться найти механизм включения доставки оповещений такого типа.
  5. В этом случае, скорее всего существует либо функция, которая принимает данную константу в виде параметра, либо словарь, в котором данная константа является ключом. В любом случае, следует искать функцию или метод, название которых начинаются с того же слова (например: константа kLockdownDeviceColorKey -> функция lockdown_copy_value(...);

Как узнать сигнатуру неизвестной функции?


1. Найти в интернете, как это не банально. Мне довольно часто попадались китайский сайты, были корейский и японский сайты с очень полезной информацией. Обычно самого кода уже достаточно, чтобы понять что происходит и как используется данная функция, данный класс и т.д. Спасибо многословности и выразительности Objective-C!
2. Для многих простых функций, можно попытаться угадать сигнатуру. Внимание, это может быть довольно опасно.
Использование некоторые простые функции, таких как GSEventSetBackLightLevel, самоочевидно.
void GSEventSetBackLightLevel(float level);

Для многих других я использовал следующий трюк (на примере функции SBGetRingerSwitchState):

	SInt32 ret = 5, out1 = 1, out2 = 2, out3 = 3, out4 = 4;

	void *libHandle = dlopen(SPRINGBOARD_SERVICES_PATH, RTLD_LAZY);
	SInt32 (*SBGetRingerSwitchState)(SInt32*,SInt32*,SInt32*,SInt32*) = dlsym(libHandle, "SBGetRingerSwitchState");
	ret = SBGetRingerSwitchState(&out1, &out2, &out3, &out4);
	NSLog(@"%x %x %x %x %x", ret, out1, out2, out3, out4);



В результате работы этого кода выяснилось, что
1) функция возвращала значение 0x10000003, не зависящее от реального положения переключателя.
2) Переменная out2 изменила свое значение на self. Возвращаемое значение также не зависит от переключателя.
3) Остальные переменные не изменили свое значение.

Из 1) я сделал вывод функция возвращает значене типа kern_return_t, так как 0x10000003 соответствует системной ошибке MACH_SEND_INVALID_DEST. По видимому, ошибка указывала на неправильный порт [в данном случае порт — это абстракция ядра mach (mach kernel), характеризующая права и приоритет процесса]. Как правило, если в вызове функции используется номер порта, то он идет первым аргументом. Из 2) следует, что через второй аргумент функция возвращает некое значение по ссылке.

В результате этих нехитрых действий получается следующая сигнатура:
	kern_return_t SBGetRingerSwitchState(mach_port_t port, SInt32 *state);


Кстати, если в названии функции присутствует слово get, то согласно naming conventions Objective-C эта функция должна возвращать значение по ссылке. Это также видно из приведенного примера.

3. Дизассемблирование. На примере все той же SBGetRingerSwitchState. Используем otool:

$ otool -p _SBGetRingerSwitchState -tV -arch armv6 SpringBoardServices | less
000038cc b5f0 push {r4, r5, r6, r7, lr}
000038ce af03 add r7, sp, #12
000038d0 b092 sub sp, #72
000038d2 aa06 add r2, sp, #24 // значение регистра r2 затирается
000038d4 9205 str r2, [sp, #20]
000038d6 ac08 add r4, sp, #32 // … как и регистра r4
000038d8 ab0f add r3, sp, #60 // … и r3
000038da 9304 str r3, [sp, #16]
000038dc 9103 str r1, [sp, #12] // значение r1 сохраняется в стеке
000038de 4925 ldr r1, [pc, #148] (0x3974)
000038e0 6011 str r1, [r2, #0]
000038e2 6020 str r0, [r4, #0] // значение r0 также сохраняется в стеке



Из этого кода, используя даже поверхностные знания arm-ассемблера, можно предположить, что функция принимает два аргумента типа «слово» (word)
Выходит, что у функции два аргумента. Идем дальше, в самый конец.


00003964 9e04 ldr r6, [sp, #16]
00003966 6836 ldr r6, [r6, #0]
00003968 9903 ldr r1, [sp, #12]
0000396a 600e str r6, [r1, #0]
// примерно соответствует (в терминах языка си): *r1 = r6; т.е. по адресу, хранящемуся в r1 записывается значение из r6;
// Это значит, что функция возвращает значение по ссылке
0000396c 462e mov r6, r5
0000396e 4630 mov r0, r6
// результат выполнения функции помещается в r0
00003970 b012 add sp, #72
00003972 bdf0 pop {r4, r5, r6, r7, pc}



В сухом остатке получаем:
int SBGetRingerSwitchState(int arg1, int* arg2);


Продолжая анализировать этот асcемблерный код, уточняем типы и приходим к окончательному варианту:
kern_return_t SBGetRingerSwitchState(mach_port_t port, SInt32 *state);


Разные прошивки и разные устройства: что может сломаться и как это исправить?

Понятно, что недокументированные API вовсе не обязательно работают на всех устройствах одинаково. По моему опыту, чаще всего ничего не меняется, и API работает одинаково на всех устройствах и всех прошивках. Так, например, все функции расширения UIDevice-IOKitExtensions (кроме определения IMEI) работают одинаково хорошо на всех устройствах и всех прошивках. Какие изменения могут произойти при обновлении iOS?
Вот несколько практических вариантов.
  • Может появиться официально документированный программный интерфейс, при этом недокументированный интерфейс, как правило, продолжает работать. Пример:
    void GSEventSetBacklightLevel(float level); // работает во всех версиях iOS
    -[UIDevice setBrightness: (CGFloat)brightness]; // появилось в iOS 5.0
    

  • Программные интерфейсы переносятся в другой фреймворк. Apple может объединить несколько фреймворков в один, переименовать или удалить фреймворк. Например, функции для работы с WiFi (Apple80211Open, Apple80211Close и так далее) были перенесены из Aeropuerto.dylib в IPConfiguration.dylib.
  • API могут просто удалить, особенно если она связана с уязвимостью.


Для того чтобы избежать проблем совместимости, соблюдайте простые правила: проверяйте наличие функций (например, с помощью -[NSObject respondsToSelector:]), классов (NSClassFromString(@"SomeClass") вернет nil в случае отсутствия класса SomeClass) и т.д., а также заранее подумайте, что должна делать программа в случае, если API отсутствует. При использовании динамической линковки библотек следует также всегда проверять возвращаемые значения dlsym(...) и dlopen(...) на равенство NULL.

Примеры
Пример 1:
Определение положения бокового переключателя вибро (a.k.a. Ring/Silent switch, Mute switch)

Одной из задач, которые стояли передо мной, было определение положения бокового переключателя, который в оригинале называется ring/silent switch. Этот переключатель используется для переключения между «тихим» и обычном/«громким» режимами в айфоне и айпаде. Поиск по StackOverflow дал решение:

#import <AudioToolbox/AudioToolbox.h>
...
/*
Возвращаемое значение:
	0: тихий режим
	1: обычный режим  
*/
int switchState() 
{
	// ...
	// Инициализируем и активируем аудиосессию, устанавливаем 
	// категорию в значение kAudioSessionCategoryAmbient
	// ...
	UInt32 size = sizeof(CFStringRef));
	CFStringRef route;
	AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &size, &route); 
	// Получаем описание текущего аудиовыхода
	CFIndex len = CFStringGetLength(route);
	return (len > 0); // Если результат - пустая строка, значит телефон находится в "тихом" режиме
}



Которое, впрочем, не работает в iOS 5. Не сработало и использование более нового API (kAudioSessionProperty_AudioRouteDescription) которое дает расширенную информацию об аудиовходах и -выходах. (AUDIOROUTE)

Мои дальнейшие поиски по StackOverflow вывели меня на этот пост. В нем описывается библиотечная функция AudioServicesAddSystemSoundCompletion(), чьё нестандартное поведение рассматривалось разработчиками как баг.

#import <AudioToolbox/AudioToolbox.h>
...
void playSound()
{
	AudioServicesAddSystemSoundCompletion(MySoundId, NULL, NULL, MyAudioServicesSystemSoundCompletionProc, 
	AudioServicesPlaySystemSound(MySoundId);
}

void MyAudioServicesSystemSoundCompletionProc (SystemSoundID  ssID, void *clientData)
{
	// Проигрывание звука завершено
	NSLog(@"Playback has been finished");
}


Нестандартное поведение заключается в том, что вызов колбэка MyAudioServicesSystemSoundCompletionProc состоится в конце проигрывания звука в обычном режиме, но сразу после вызова AudioServicesPlaySystemSound в «тихом» режиме. Это создает лазейку для определения текущего состояния переключателя. Если, например, длина аудиофайла что мы проигрываем равна 1 с, то разница во времени вызова MyAudioServicesSystemSoundCompletionProc() в «тихом» и громком режиме составляет 1 c. На этом я построил свое второе, асинхронное решение для определения положения бокового переключателя. Вот оно:

#import <AudioToolbox/AudioToolbox.h>
#import "MuteSwitchTet.h"
...
enum MuteSwitchStates {
	kMuteSwitchUndefined = -1,
	kSoundless = 0,
	kSound = 1
};
 
@implementation MuteSwitchTest
...
void MyAudioServicesSystemSoundCompletionProc (SystemSoundID  ssID, void *clientData)
{
	// "тихий" режим
	MuteSwitchTest *self = (MuteSwitchTest*)clientData;
	[NSObject cancelPreviousPerformRequestsWithTarget:self];
	self.muteSwitchState = kSoundless;
}

- (void) cancelSystemSoundCompletion
{
	// "громкий" режим
	AudioServicesRemoveSystemSoundCompletion(SoundID);
	self.muteSwitchState = kSound;
}

- (void) startPlayback
{
	AudioServicesAddSystemSoundCompletion(SoundID, NULL, NULL, MyAudioServicesSystemSoundCompletionProc, self);
	AudioServicesPlaySystemSound(SoundID);
	[self performSelector:@selector(cancelSystemSoundCompletion) withObject:nil afterDelay:0.1];
}
...
@end


Хотя это новое решение и было рабочим, оно не устраивало меня по нескольким причинам. Во-первых, оно было асинхронным и работало с ощутимой задержкой (около 1/10 секунды). Снижение задержки вело к ложным срабатываниям. Во-вторых, был побочный эффект — сам проигрываемый звук, который звучал достаточно громко чтобы смутить пользователя. Позже я искусственно выкрутил громкость в ноль в аудиоредакторе. В-третьих, это был уже слишком похоже на грязный хак, хотя это, например, не помешало создателям VSSilentSwitch продавать свое решение, по всей видимости основанное на том же эффекте.

Примерно через месяц я вернулся к этой проблеме. Я начал использовать команду nm для поиска символов в объектных файлах, на её основе я написал простейший shell-скрипт, листинг которого можно найти ниже (В разделе «Инструменты»). Скрипт запускается с одним, двумя или тремя параметрами, каждый из которых представляет ключевое слово.

$ sh ~/Documents/LookSDKForSymbol.sh RingerSwitch
# часть результатов опущена
0000d738 S _kGSRingerSwitchCapability
Found in ./System/Library/PrivateFrameworks/GraphicsServices.framework/GraphicsServices
000038cc T _SBGetRingerSwitchState
0000370c T _SBGetRingerSwitchState
Found in ./System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices


Функция с названием SBGetRingerSwitchState выглядела многообещающе.

Для получения нужного порта использовалась функция:
mach_port_t SBSSpringBoardServerPort();

из того же фреймворка.

Вот что получилось в итоге:
@implementation MuteSwitchTest
...
- (int) switchState
{
	// Функции SBSSpringBoardServerPort и SBGetRingerSwitchState 
	// загружается при инициализации объекта MuteSwitchTest

	mach_port_t port = SBSSpringBoardServerPort();
	SInt32 state;
	SBGetRingerSwitchState(port, &state);
	return (int)state;
}


Пример 2:
Определение IMEI

IMEI (International Mobile Equipment Identity) — уникальный идентификационный
код, присваиваемый каждому телефону, своего рода MAC-адрес телефона (хотя MAC-адрес у телефона также есть)

Я уже и не помню, как я вышел проект Эрики Садун uidevice-extension, но по мере того, как я с ним разбирался он всё больше казался мне этакой программистской «золотой жилой».

Одна из категорий, UIDeviсe(IOKit_Extensions) содержит функции для определения IMEI. Я протестировал эти функции на iPhone 4 c iOS 5.1 и iPad c iOS 4.3, всё работало и я перешел к другим задачам. Но в ходе бета-тестирования выяснилось, что функция для определения IMEI не работает на новых устройствах: iPad 2, the new iPad и iPhone 4S. Для выяснения причин я отправился на StackOverflow, где мои опасения подтвердились. Поиски привели меня тогда к фреймворку под названием CoreTelephony.

$ nm -g ./CoreTelephony | grep -i imei
U _kCFAbsoluteTimeIntervalSince1970
00053b28 S _kCTMobileEquipmentInfoIMEI
00053ad4 S _kCTPostponementInfoIMEI
00053ac4 S _kCTPostponementStatusErrorDefaultIMEI
$ nm -g ./CoreTelephony | grep MobileEquipment
000260e4 T __CTServerConnectionCopyMobileEquipmentInfo
00053b34 S _kCTMobileEquipmentInfo1xIMSI
00053b20 S _kCTMobileEquipmentInfoCurrentMobileId
00053b24 S _kCTMobileEquipmentInfoCurrentSubscriberId
00053b40 S _kCTMobileEquipmentInfoERIVersion
00053b2c S _kCTMobileEquipmentInfoICCID
00053b28 S _kCTMobileEquipmentInfoIMEI
00053b30 S _kCTMobileEquipmentInfoIMSI
00053b38 S _kCTMobileEquipmentInfoMEID
00053b44 S _kCTMobileEquipmentInfoMIN
00053b3c S _kCTMobileEquipmentInfoPRLVersion

Можно предположить что функция (_CTServerConnectionCopyMobileEquipmentInfo(...)) возвращает словарь(CFDictionaryRef) c ключами вида kCTMobileEquipmentInfo* и соответствующими им значениями. К счастью, на этот раз мне не пришлось восстанавливать сигнатуру. Поиск в гугле по запросу _CTServerConnectionCopyMobileEquipmentInfo привел меня на эту страничку, и вскоре функция для определения IMEI была готова.


// Заголовочный файл с декларациями недокументированных функций и констант	
#include "CoreTelephony.h" 
...

NSString* CTGetIMEI
{
	struct CTResult it;
	NSMutableDictionary *dict;
	CTServerConnectionRef conn;
	
	conn = _CTServerConnectionCreate(kCFAllocatorDefault, ConnectionCallback, NULL);
	
	_CTServerConnectionCopyMobileEquipmentInfo(&it, conn, &(CFMutableDictionaryRef)dict);
	CFRelease(conn);
	[dict autorelease];
	return [dict objectForKey: kCTMobileEquipmentInfoIMEI];
}

Этот метод определения IMEI работает на всех устройствах.
Позже я нашел еще один метод определения IMEI (через lockdownd).

Пример 3:
Использование недокументированных оповещений: нажатия кнопок громкости.

Изначально я наивно полагал, что любая символьная константа, заканчивающаяся на «Notification» является названием системного оповещения и её можно использовать, просто зарегистрировав наблюдателя (observer) с помощью [NSNotificationCenter defaultCenter].
$ sh ~/Documents/LookSDKForSymbol.sh notification$ volume change
001dbe60 S _MPAVControllerVolumeDidChangeNotification
001dbe64 S _MPAVControllerVolumeMutedDidChangeNotification
001dc4f8 S _MPMusicPlayerControllerVolumeDidChangeNotification
001dc314 S _MPVolumeViewRouteButtonChangedNotification
001dc310 S _MPVolumeViewVisibilityChangedNotification
Found in ./System/Library/Frameworks/MediaPlayer.framework/MediaPlayer

000d6d24 D _AVController_EffectiveVolumeDidChangeNotification
000d6d60 D _AVController_VolumeDidChangeNotification
000d6fec D _AVSystemController_CurrentRouteHasVolumeControlDidChangeNotification
000d6ffc D _AVSystemController_EffectiveVolumeDidChangeNotification
000d6fdc D _AVSystemController_SystemVolumeDidChangeNotification
Found in ./System/Library/PrivateFrameworks/Celestial.framework/Celestial
… и еще около десятка из других фреймворков

Написав тестовую программу, я принялся проверять, какие оповещения приходили в ответ на нажатия клавиш громкости.
Из составленного мной довольно большого списка опопвещений приходили только вот эти 2:

AVController_EffectiveVolumeDidChangeNotification
AVController_VolumeDidChangeNotification

Недостаток этих оповещений в том, что
1) Нельзя напрямую определить, какая из двух кнопок была нажата
2) Нельзя отследить, когда нажата и когда отпущена каждая из кнопок

Ищу по другим ключевым словам:
$ sh ~/Documents/LookSDKForSymbol.sh volume button
001b221c t -[UIApplication setWantsVolumeButtonEvents:]
003cce5c t _SBSetWantsVolumeButtonEvents$shim
0054478c S __UIApplicationVolumeDownButtonDownNotification
00544790 S __UIApplicationVolumeDownButtonUpNotification
00544784 S __UIApplicationVolumeUpButtonDownNotification
00544788 S __UIApplicationVolumeUpButtonUpNotification
Found in ./System/Library/Frameworks/UIKit.framework/UIKit
… и еще несколько десятков из разных фреймворкрв

Четыре оповещения из UIKit сработали не сразу: необходимо было подать связанную с ними команду.
	[[UIApplication sharedApplication] setWantsVolumeButtonEvents: YES];

После этого стали приходить оповещения о нажатиях соответствующих кнопок.
Побочный эффект: вызов данной функции приводит к тому, что кнопки громкости больше не регулируют громкость, так что по завершении работы с кнопками следует вызвать
	[[UIApplication sharedApplication] setWantsVolumeButtonEvents: NO];

Пример 4:
Использование недокументированных оповещений: отслеживание статуса SIM-карты

Работаем по проверенной схеме:
$ sh ~/Documents/LookSDKForSymbol.sh notification$ SIM

00052560 S _kCTSIMSupportSIMInsertionNotification
00052564 S _kCTSIMSupportSIMStatusChangeNotification

000525bc S _kCTSIMSupportSIMTrayStatusNotification

Found in ./System/Library/Frameworks/CoreTelephony.framework/CoreTelephony

Found in ./System/Library/PrivateFrameworks/FTServices.framework/FTServices
$


Наиболее подходящими мне показались оповещения под названиями:
1) kCTSIMSupportSIMInsertionNotification
2) kCTSIMSupportSIMStatusChangeNotification
3) kCTSIMSupportSIMTrayStatusNotification

Простейшая тестовая программа показала, что оповещения под названием (1) приходили только в момент вставки сим-карты (я мог бы догадаться и раньше по названию), (2) приходили именно тогда когда мне нужно (при вставке и вынимании), оповещения (3) не приходили вообще. Позже я узнал, что оповещения (3) относятся к специальному центру оповещений под названием CTTelephonyCenter. Об использовании CTTelephonyCenter можно прочитать здесь.

Оповещения о статусе SIM:

#include "CoreTelephony.h" 

- (void) notificationCallback:(NSNotification)notification
{
	...
}
- (void) startUpdateSIMStatus
{
	[[NSNotificationCenter defaultCenter]
		 addObserver:self
		 selector:@selector(notificationCallback:)
		 name:kCTSIMSupportSIMStatusChangeNotification
		 object:nil
	];
}
- (void) stopUpdateSIMStatus 
{
	[[NSNotificationCenter defaultCenter] 
	 removeObserver:self 
	 name:kCTSIMSupportSIMStatusChangeNotification 
	 object:nil];
}


Дополнения

1. Используем системные звуки в своем приложении

www.iphonedevwiki.net/index.php/AudioServices — здесь описаны недокументированные константы типа SystemSoundID для проигрывания стандартных коротких (< 30сек) звуков, таких как нажатие кнопок. Можно и самому их все найти, просто перебирая значения от 1000 в цикле.

Еще можно проигрывать стандартные рингтоны:

- (void) playDefaultRingTone
{
	NSURL *defaultRingTone = [NSURL URLWithString:@"/System/Library/CoreServices/SpringBoard.app/ring.m4r"];
	AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:defaultRingTone error:nil];
	[player play];
}

2. Рекурсивный поиск по иерархии UIView

Как известно, у объектов класса UIView обычно есть родительский вид (superview) и могут быть дочерние виды (subviews). Верхушкой (как мы дальше увидим, верхушками) этой иерархии являются объект(ы) UIWindow. Что если пройти по всей иерархии? Тут есть тольк одна тонкость: как это не странно, в программе может быть больше одного объекта типа UIWindow.
Для получения __всех__ окон я использовал недокументированную функцию

	+ [UIWindow allWindowsIncludingInternalWindows: onlyVisibleWindows:]


При помощи неё мне удалось обнаружить что в самом обычном приложении может быть до четырех окон! (UIWindow)
1) обычное окно программы
2) окно для статус-бара (его также можно получить при помощи — [UIApplication statusBarWindow] )
3) окно для UIAlertView (Родительский вид для объектов типа UIAlertView).
4) окно для экранной клавиатуры.

Какую пользу мы можем из этого извлечь?

Очевидно, что с объектами последних трех типов мы можем обращаться так же, как и с первым.
В частности, мы можем:
— изменять внешний вид UIAlertView, добавлять текстовые поля, переключатели и т.д.
— изменять внешний вид статус-бара, добавлять свои индикаторы и удалять стандартные.
— изменять внешний вид экранной клавиатуры: например, изменять вид кнопок, добавить свои кнопки, переключатели и т.д

Но это еще не все, есть также неожиданные побочные последствия. Часть графических элементов может быть напрямую связана с тем или иным более глубоким функционалом.
@interface UIStatusBarSignalStrengthItemView : UIStatusBarItemView {
@private
	int _signalStrengthRaw;
	int _signalStrengthBars;
	BOOL _enableRSSI;
	BOOL _showRSSI;
}
-(id)_stringForRSSI;
-(float)extraRightPadding;
-(void)touchesEnded:(id)ended withEvent:(id)event;
-(id)contentsImageForStyle:(int)style;
-(BOOL)updateForNewData:(id)newData actions:(int)actions;
@end


Так, например, получив доступ к UIStatusBarSignalStrengthItemView мы имеем возможность вполне легально получать значения RSSI (мощность принимаемого сигнала) сотовой сети, и отображать в любой удобной форме.

Полный листинг кода, рекурсивно описывающего все видимые объекты в программе. Вызывается через [UIView completeDescription]:

@interface UIView (RecursiveDescription)
- (void) recursiveDescription;
+ (void) completeDescription
@end

@implementation UIView (RecursiveDescription)
- (void) recursiveDescription
{
	NSLog(@"______________________________________");
	NSLog(@"%@", self);
	NSArray *subviews_ = [self subviews];
	if ([subviews_ count] > 0) {
		for (UIView *subview_ in subviews_) {
			[subview_ recursiveDescription];
		}
	}
}
- (void)completeDescription
{
	NSArray *windows = [UIWindow allWindowsIncludingInternalWindows:YES onlyVisibleWindows:NO];
	for (UIView *view in windows) {
		[view recursiveDescription];
	}
}
@end

3. Некоторые важные низкоуровневые подсистемы iOS


Подсистема MIG (MIG-subsystem, Mach Interface Generator) — интерфейс взаимодействия с ядром операционной системы (так называемый «Mach kernel»). Реализация MIG-subsystem для Mac OS X лежит где-то здесь: www.opensource.apple.com/source/xnu/xnu-1228.0.2/libsyscall/mach. Смотри также список известных низкоуровневых сообщений: www.iphonedevwiki.net/index.php/MIG_subsystem.

IORegistry, I/O registry — реестр ввода-вывода; древовидная структура, описывающая аппаратное обеспечение iPhone и взаимодействие с аппаратными компонентами. Примеры использования IORegistry можно найти в проектах Эрики Садун (см. UIDevice-IOKitExtensions).

Инструменты

nm — UNIX-утилита, выводящая таблицу символов объектнового файла.
На основе nm я написал простой (и довольно тупой) bash-скрипт, который ищет по всем библиотекам и объектным файлам внутри iOS SDK.

LookSDKForSymbol.sh:
	#!/bin/bash

	SDK=/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk
	PrivateFrameworks=$SDK/System/Library/PrivateFrameworks
	Frameworks=$SDK/System/Library/Frameworks 
	Lib=$SDK/usr/lib 
	
	cd $SDK
	for i in $(find -f .); do
		test -f $i && nm $i | c++filt | grep -i "$1" | grep -i "$2" | grep -i "$3" && echo "Found in $i
		";
	done

c++filt — восстановление (demangling) имён. Только для С++; имена objective-C и просто С идут сразу в человекочитаемом виде.
otool — стандартная утилита для анализа и дизассемблирования объектных файлов.
hexdump — дамп он дамп и есть :-)
class-dump-z — суперполезная утилита. Позволяет генерировать заголовочный файл из объектного файла. Описание всех структур, протоколов, классов, категорий, их методов, свойств и так далее.
Репозиторий на Google Code — здесь можно ознакомиться с более подробным описанием и скачать исходный код этого проекта.

Hex-Rays ARM Decompiler — плагин для HEX-Rays IDA, декомпилятор ARM кода. Официальный сайт.
Утилиты Эрики Садун — на сайте Эрики есть несколько полезных утилит, например, утилита для анализа оповещений.

Полезные ресурсы, источники

Wiki

www.iphonedevwiki.net
Единственная известная мне wiki, относящаяся к jailbreak разработке. Хотя информация и устарела
(iOS 3.x, iOS 4.x), но все равно это крайне полезный ресурс.

Персоны

1. Джей Фримен (Jay Freeman, saurik)
www.saurik.com

2. Эрика Садун (Erica Sadun) — автор книг «The iPhone Developer's CookBook» и «The iOS 5 Developer's Cookbook: Core Concepts and Essential Recipes for iOS Programmers», а также многих полезных утилит. В её книгах описываются некоторые недокументированные возможности публичных фреймворков.
ericasadun.com

Некоторые проекты Эрики Садун:
github.com/erica/iOS-5-Cookbook
github.com/erica/iphone-3.0-cookbook-

github.com/erica/uidevice-extension

3. KennyTM / networkpx — создатель class-dump-z, активный участник StackOverflow.com в вопросах, связанных с недокументированными API
networkpx.blogspot.com
code.google.com/p/networkpx
github.com/kennytm
stackoverflow.com/users/224671/kennytm

github.com/kennytm/iphone-private-frameworks
Информация сильно устарела (iOS 3).

Книги

«The iPhone Developer's CookBook» (также «The iOS 5 Developer's Cookbook: Core Concepts and Essential Recipes for iOS Programmers») — книга о разработке под iOS, с уклоном в недокументированные возможности iOS.
Русский перевод:
translated.by/you/iphone-developers-cookbook-development-native-applications-for-the-iphone/into-ru/trans
Tags:
Hubs:
+49
Comments 12
Comments Comments 12

Articles