From this chapter, and from this entire book, it is clear that the most tidbits of iOS programming are included in public frameworks, but not in the SDK. Apple’s unofficial policy about this is simple: you can use it all, but only at your own peril and risk. Your code may break with the next firmware update. You yourself have to find a compromise between risk and profit.
Erica Sadun, The iPhone Developer's CookBook
OriginalThis is a section of the iPhone for the iPhone. It’s clear that you’re unofficial. Striking the balance between risk and reward is up to you.
Disclaimers
- The pieces of code given here work on ordinary iPhones (including 4S) and iPads (including the new iPad) and do not require a jailbreak.
- All solutions are written and tested on iOS 5. All solutions are also tested for compatibility with iOS 4.3, i.e. work with iOS 4, if not stated otherwise. The main part of the article was written before the release of iOS 6, so the above solutions were not tested for compatibility with iOS 6.
- The use of undocumented APIs can lead to the fact that your application will not be allowed in the AppStore. Or maybe not bring :-)
For those who are interested in how Apple defines the use of privatory APIs: “How does Apple know that you are using private APIs?”
- Apple may change the implementation with the next release of iOS, and something will break in your code. However, this is solvable, and is not fundamentally different from the implementation of backward compatibility for documented APIs. Below I have covered this issue in more detail.
- I cannot guarantee that the APIs I found have no side effects. Use at your own risk.
- The Apple Developer Program license agreement prohibits iOS reverse engineering.
- The article is in the process of refinement. Constructive criticism is welcome!
SDK Quick Start Guide
Suppose you need to do something that goes beyond the official documentation.
For example, change the screen backlight level (before iOS 5 this was not in the documented part of the SDK). It is known that Apple programmers usually give meaningful and expressive names to functions and variables, which we will use to search the SDK. To do this, select a few words related to the subject, for example, brightness, level, screen. Run the LookSDKForSymbol.sh script (this is my wrapper over
nm
; this script and other used tools are described further in the
“Tools” section) with keywords as parameters. The script returns the characters found in the object file (ie, the names of classes, functions, variables). Sample issue:
$ 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:]
... and a few dozen characters
Most of the results can be immediately discarded, for example
-[UIApplication backlightLevel]
returns the value of the backlight, rather than setting it.
Remaining, if not more than a few dozen, you can try to feed Google. It happens that someone has already studied the API related to the characters found, and in this case, the problem is considered to be solved. In more complex cases, you have to deal with reverse engineering, that is, find out how the functions found work, how to use the found alerts and the like.
')
The character strings produced by the utility are divided into the following categories:
- Objective-C and C ++ functions, classes, structures, and so on. All that relates to Objective-C contains square brackets ([]) or dollar signs ($). C ++ functions are usually contained in some namespace, and therefore their name contains the namespace permission symbol, two colons (: :).
- Objective-C blocks. They have the following general form:
___{}{ }_block_invoke_{2}
For example:
___22-[AXSystemServer init]_block_invoke_0
- Pure C function.
- Objective-C alerts. Ends on Notification, for example
_SBMenuButtonPressedNotification
. - Keys / constants. Usually begin with k, for example: _
kCFUserNotificationAlternateButtonTitleKey
.
Further actions depend on the category of the symbol.
- We generate the header file for this framework:
class-dump-z Foundation> $ / iOS_private_headers / Foundation.h
In most cases, the generated header file is sufficient: the class inheritance hierarchies, structures, methods, etc. should be fairly well described, so that you can spend a bit of time working with the API and using it in your sentence.
Unfortunately, sometimes the information contained in the header file is not enough to make the code work, and then you have to analyze the assembler code generated by otool.
Hint on disassembling Objective-C code: almost certainly you will encounter calls to functions like objc_msgSend (sending a message to an object). The first parameter is always a pointer to the object, and the second is a pointer to the selector (selector), i.e. a pointer to a string that is the name of the method (the remaining "ordinary" arguments are the third, fourth, etc. arguments). To determine what message is sent in this case, help hexdump
.
- You can forget about it immediately. Blocks are (usually) local, they cannot be called from their code.
- The most difficult option. In the simplest cases, you can choose the signature for the function, in the rest - only disassembly. You can learn more about this in the section “How to find out the signature of an unknown function?” .
- Let's start by trying to catch alerts in one of the three main alert centers (these are Local, Darwin and CoreTelephony). If this type of alert does not come, the case may be one of two things:
- Alerts of this type coming to a separate, special alert center. It is necessary to look for traces of such an alert center in the same framework, to which the found alert belongs.
- Notification delivery is disabled. Try to find a mechanism to enable the delivery of alerts of this type.
- In this case, most likely there is either a function that takes a given constant as a parameter, or a dictionary in which the given constant is a key. In any case, you should look for a function or method whose name begins with the same word (for example: constant
kLockdownDeviceColorKey
-> lockdown_copy_value(...)
function;
How to find out the signature of an unknown function?
1. Find on the Internet, as it is not trivial. I often came across Chinese sites, there were Korean and Japanese sites with very useful information. Usually, the code itself is already enough to understand what is happening and how this function, this class, etc. are used. Thanks to the verbosity and expressiveness of Objective-C!
2. For many simple functions, you can try to guess the signature.
Attention , this can be quite dangerous.
Using some simple functions, such as GSEventSetBackLightLevel, is self-evident.
void GSEventSetBackLightLevel (float level);
For many others, I used the following trick (using the example of the SBGetRingerSwitchState function):
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);
As a result of the operation of this code, it turned out that
1) the function returned a value of
0x10000003
, independent of the actual position of the switch.
2) The variable
out2
changed its value to self. The return value is also independent of the switch.
3) The remaining variables have not changed their value.
From 1) I concluded that the function returns a value of type
kern_return_t
, since
0x10000003
corresponds to the MACH_SEND_INVALID_
DEST
system error. Apparently, the error pointed to the wrong port [in this case, the port is an abstraction of the mach kernel (mach kernel) characterizing the rights and priority of the process]. As a rule, if the port number in the function call is used, then it is the first argument. From 2) it follows that the function returns a certain value by reference through the second argument.
As a result of these simple actions, the following signature is obtained:
kern_return_t SBGetRingerSwitchState(mach_port_t port, SInt32 *state);
By the way, if the name of the function contains the word get, then according to the naming conventions of Objective-C, this function should return the value by reference. This is also evident from the above example.
3. Disassembly. For example, the same SBGetRingerSwitchState. Use 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 // register r2 is overwritten
000038d4 9205 str r2, [sp, # 20]
000038d6 ac08 add r4, sp, # 32 // ... as well as register r4
000038d8 ab0f add r3, sp, # 60 // ... and r3
000038da 9304 str r3, [sp, # 16]
000038dc 9103 str r1, [sp, # 12] // the value of r1 is stored on the stack
000038de 4925 ldr r1, [pc, # 148] (0x3974)
000038e0 6011 str r1, [r2, # 0]
000038e2 6020 str r0, [r4, # 0] // the value of r0 is also stored on the stack
...
From this code, using even the superficial knowledge of the arm-assembler, we can assume that the function takes two arguments of the type "word" (word)
It turns out that the function has two arguments. We go further to the very end.
...
00003964 9e04 ldr r6, [sp, # 16]
00003966 6836 ldr r6, [r6, # 0]
00003968 9903 ldr r1, [sp, # 12]
0000396a 600e str r6, [r1, # 0]
// approximately corresponds (in terms of the C language): * r1 = r6; those. at the address stored in r1, the value from r6 is written;
// This means that the function returns the value by reference.
0000396c 462e mov r6, r5
0000396e 4630 mov r0, r6
// the result of the function is placed in r0
00003970 b012 add sp, # 72
00003972 bdf0 pop {r4, r5, r6, r7, pc}
...
In the dry residue we get:
int SBGetRingerSwitchState(int arg1, int* arg2);
Continuing to analyze this assembler code, we specify the types and arrive at the final version:
kern_return_t SBGetRingerSwitchState(mach_port_t port, SInt32 *state);
Different firmware and different devices: what can break and how to fix it?
It is clear that undocumented APIs do not necessarily work the same on all devices. In my experience, most often nothing changes, and the API works the same on all devices and all firmware. For example, all the
UIDevice-IOKitExtensions extension functions (except IMEI definition) work equally well on all devices and all firmware. What changes can occur when updating iOS?
Here are some practical options.
To avoid compatibility problems, follow simple rules: check for functions (for example, using
-[NSObject respondsToSelector:]
), classes (
NSClassFromString(@"SomeClass")
returns
nil
if there is no
SomeClass
), etc. and also think in advance what the program should do if there is no API. When using dynamic linking, you should also always check dlsym (...) and dlopen (...) return values ​​for NULL equality.
Examples
Example 1:
Vibro side switch position detection (aka Ring / Silent switch, Mute switch)
One of the tasks that faced me was to determine the position of the side switch, which in the original is called the ring / silent switch. This switch is used to switch between "quiet" and normal / "loud" modes in iPhone and iPad. Searching for StackOverflow gave the solution:
#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); // - , "" }
Which, however, does not work in iOS 5. It did not work and the use of a newer API (kAudioSessionProperty_AudioRouteDescription) which gives extensive information on audio inputs and outputs. (AUDIOROUTE)
My further searches on StackOverflow brought me to
this post . It describes the library function AudioServicesAddSystemSoundCompletion (), whose non-standard behavior was considered by developers as a bug.
#import <AudioToolbox/AudioToolbox.h> ... void playSound() { AudioServicesAddSystemSoundCompletion(MySoundId, NULL, NULL, MyAudioServicesSystemSoundCompletionProc, AudioServicesPlaySystemSound(MySoundId); } void MyAudioServicesSystemSoundCompletionProc (SystemSoundID ssID, void *clientData) { // NSLog(@"Playback has been finished"); }
Non-standard behavior is that the call to MyAudioServicesSystemSoundCompletionProc callback takes place at the end of playing the sound in normal mode, but immediately after calling AudioServicesPlaySystemSound in silent mode. This creates a loophole for determining the current state of the switch. If, for example, the length of the audio file that we are playing is 1 second, then the difference in the call time of MyAudioServicesSystemSoundCompletionProc () in the “quiet” and loud mode is 1 second. With this, I built my second, asynchronous solution for determining the position of the side switch. Here it is:
#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
Although this new solution was a worker, it did not suit me for several reasons. First, it was asynchronous and worked with a noticeable delay (about 1/10 of a second). Reducing the delay led to false positives. Secondly, there was a side effect - the sound being played, which sounded loud enough to embarrass the user. Later, I artificially turned the volume to zero in the audio editor. Thirdly, it was already too much like a dirty hack, although this, for example, did not prevent the creators of
VSSilentSwitch from selling their solution, apparently based on the same effect.
About a month later I returned to this problem. I started using the nm command to search for characters in object files, based on it I wrote a simple shell script, the listing of which can be found below (in the "Tools" section). The script runs with one, two or three parameters, each of which represents a keyword.
$ sh ~ / Documents / LookSDKForSymbol.sh RingerSwitch
# Some results omitted
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
The function called
SBGetRingerSwitchState
looked promising.
To obtain the desired port function was used:
mach_port_t SBSSpringBoardServerPort();
from the same framework.
Here's what happened in the end:
@implementation MuteSwitchTest ... - (int) switchState {
Example 2:
IMEI Definition
IMEI (International Mobile Equipment Identity) - unique identification
the code assigned to each phone is a kind of phone’s MAC address (although the phone also has a MAC address)
I don’t remember how I came up with the project of Erica Sadun
uidevice-extension , but as I dealt with it, it seemed to me more and more that sort of programmer “gold mine”.
One of the categories, UIDevice (IOKit_Extensions) contains functions for defining IMEI. I tested these functions on iPhone 4 with iOS 5.1 and iPad with iOS 4.3, everything worked and I switched to other tasks. But during beta testing, it turned out that the function for determining IMEI does not work on new devices: iPad 2, the new iPad and iPhone 4S. To find out the reasons, I went to StackOverflow, where my fears were confirmed. The search led me to a framework called 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
It can be assumed that the function (_CTServerConnectionCopyMobileEquipmentInfo (...)) returns a dictionary (CFDictionaryRef) with keys of the form kCTMobileEquipmentInfo * and the values ​​corresponding to them. Fortunately, this time I did not have to restore the signature. A Google search for _CTServerConnectionCopyMobileEquipmentInfo led me to
this page , and soon the function for determining IMEI was ready.
This method of determining IMEI works on all devices.
Later I found another method for determining IMEI (via lockdownd).
Example 3:
Using undocumented alerts: pressing the volume buttons.
Initially, I naively thought that any symbolic constant ending with “Notification” is the name of the system alert and can be used simply by registering an observer using [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
... and about a dozen of other frameworks.
Having written a test program, I began to check which alerts came in response to the volume keystrokes.
From the rather large list of the opobences I compiled, only these 2 came:
AVController_E effectiveVolumeDidChangeNotification
AVController_VolumeDidChangeNotification
The disadvantage of these alerts is that
1) You can not directly determine which of the two buttons was pressed
2) It is impossible to track when each of the buttons is pressed and released
Looking for other keywords:
$ 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
... and a few dozen more from different frameworks
Four alerts from UIKit did not work right away: it was necessary to send an associated command.
[[UIApplication sharedApplication] setWantsVolumeButtonEvents: YES];
After that, they began to receive alerts about pressing the corresponding buttons.
Side effect: calling this function causes the volume buttons to no longer adjust the volume, so that when you finish working with the buttons, you should call
[[UIApplication sharedApplication] setWantsVolumeButtonEvents: NO];
Example 4:
Using undocumented alerts: SIM status tracking
We work according to a proven scheme:
$ 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
$
The most appropriate alerts seemed to me under the names:
1) kCTSIMSupportSIMInsertionNotification
2) kCTSIMSupportSIMStatusChangeNotification
3) kCTSIMSupportSIMTrayStatusNotification
The simplest test program showed that notifications called (1) came only at the moment of inserting a SIM card (I could have guessed earlier by name), (2) came exactly when I needed (when inserting and removing), notifications (3 ) did not come at all. Later, I learned that alerts (3) belong to a special alert center called CTTelephonyCenter. About using CTTelephonyCenter can be read
here .
SIM status alerts:
#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]; }
Additions
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
As you know, objects of the UIView class usually have a parent view (superview) and there may be child views (subviews). The top (as we will see later, the top) of this hierarchy is the UIWindow object (s). What if you go through the whole hierarchy? There is only one subtlety: as it is not strange, there can be more than one object of UIWindow type in the program.For __in all __ windows I used the undocumented function + [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 ).
Instruments
nm is a UNIX utility that prints the symbol table of a new file.Based on nm, I wrote a simple (and rather stupid) bash script that searches all libraries and object files inside the iOS SDK.LookSDKForSymbol.sh:
c ++ filt - restore ( demangling ) names. Only for C ++; the names of objective-C and simply C go immediately in human readable form.otool is a standard utility for analyzing and disassembling object files.hexdump - dump is dump and there is :-)class-dump-z is a super-useful utility. Allows you to generate a header file from the object file. Description of all structures, protocols, classes, categories, their methods, properties, and so on.Repository on Google Code - here you can find a more detailed description and download the source code of this project.Hex-Rays ARM Decompiler - plug-in for HEX-Rays IDA, ARM code decompiler. Official site.Utilities Erica Sadun - There are several useful utilities on Erica’s website, for example, a utility for analyzing alerts.Useful resources, sources
Wiki
www.iphonedevwiki.netThe only wiki I know about jailbreak development. Although the information is outdated(iOS 3.x, iOS 4.x), it is still a very useful resource.Persons
1. (Jay Freeman, saurik)
www.saurik.com2. (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-Cookbookgithub.com/erica/iphone-3.0-cookbook-github.com/erica/uidevice-extension3. KennyTM / networkpx — class-dump-z, StackOverflow.com , API
networkpx.blogspot.comcode.google.com/p/networkpxgithub.com/kennytmstackoverflow.com/users/224671/kennytmgithub.com/kennytm/iphone-private-frameworks(iOS 3).
Books
«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