- Building Objective-C or Swift apps for iOS
- 1. Linking your repository
- 2. Selecting a branch
- 3. Setting up your first build
- 3.1 Project/workspace and scheme
- 3.2. Xcode version
- 3.3. Build triggers
- 3.4. Increment build number
- 3.5. Tests
- 3.6. Code signing
- 3.7. Launch your successful build on a real device
- 3.8. CocoaPods
- 3.9. Distribute to a distribution group
- 4. Build results
- 4.1. Build logs
- 4.2. The app (.ipa)
- 4.3. The symbols file (.dsym)
- Supported versions and requirements
- Разработка под Apple. Objective-C
- Инстанс методы:
- Калькулятор
- Задание
- Задания повышенной сложности:
Building Objective-C or Swift apps for iOS
To build your first native iOS app, you must take the following actions:
- Connect to your repository service account (GitHub, Bitbucket, VSTS, Azure DevOps)
- Select a repository and a branch where your app lives
- Configure the build’s project or workspace, and the scheme you want to build
To run the app on a real device, the build must be code signed with a valid provisioning profile and a certificate.
1. Linking your repository
If you haven’t previously connected to your repository service account, you must authorize the connection. Once your account is connected, select the repository where your iOS project is located. App Center requires that your account has Admin and Pull permission to set up a build for a repository.
2. Selecting a branch
After selecting a repository, select the branch you want to build. By default, all the active branches will be listed.
3. Setting up your first build
Configure your iOS project before your first build.
3.1 Project/workspace and scheme
For a build configuration, an Xcode project or an Xcode workspace, and a shared scheme are required. App Center automatically detects the projects, workspaces, and shared schemes (as long as the schemes are in the correct folder) in your branch. Select the project or the workspace you want to build and the corresponding scheme.
If no scheme is found, make sure the scheme you want is shared and its container is the project or workspace you’ve selected. You should also confirm those changes are checked into the branch you’re configuring.
Keep in mind that you can’t export an .xcscheme file and place it anywhere in the project. It must be in the xcshareddata/xcschemes/ folder. Make sure that this path isn’t in your .gitignore file.
3.2. Xcode version
Select the Xcode version to run the build on.
3.3. Build triggers
By default, a new build is triggered every time a developer pushes to a configured branch. This process is referred to as «Continuous Integration». If you prefer to trigger a new build manually, you can change this setting in the build configuration.
3.4. Increment build number
When enabled, the CFBundleVersion in the Info.plist of your app automatically increments for each build. The change happens pre-build and won’t be committed to your repository.
3.5. Tests
If the selected scheme has a test action with a test target selected, you can configure the tests to run as part of each build. App Center can currently run XCTest unit tests.
3.6. Code signing
Building an iOS app for real devices requires signing it with valid credentials. To sign builds in App Center, enable code signing in the configuration pane and upload a provisioning profile ( .mobileprovision ) and a valid certificate ( .p12 ), along with the password for the certificate.
The settings in your Xcode project must be compatible with the files you’re uploading. You can read more about code signing in the official Apple Developer documentation.
Apps with app or watchOS extensions require an additional provisioning profile per extension to be signed.
3.7. Launch your successful build on a real device
Use your newly produced .ipa file to test if your app starts on a real device. Launching on a real device will add approximately 10 more minutes to the total build time. Read more about how to configure launch tests.
3.8. CocoaPods
App Center scans the selected branch and if it finds a Podfile, it will automatically do a pod install step at the beginning of every build. This step will ensure that all dependencies are installed.
If the repository already contains a /Pods folder, App Center assumes you’ve checked in the pods in your repository and will no longer perform pod install . If you remove or modify the /Pods folder, you might have to resave the Build configuration manually using Save or Save and Build for the update to take effect.
3.9. Distribute to a distribution group
You can configure each successful build from a branch to be distributed to a previously created distribution group. You can add a new distribution group from within the Distribute section. There’s always a default distribution group called «Collaborators» that includes all the users who have access to the app.
Once you save the configuration, a new build will be kicked off automatically.
4. Build results
After a build is triggered, it can be in the following states:
- queued — the build is queued waiting for resources to be freed up.
- building — the build is running the predefined tasks.
- succeeded — the build is completed and it succeeded.
- failed — the build completed but it failed. You can troubleshoot what went wrong by inspecting the build logs.
- canceled — the build was canceled by a user action or it timed out
4.1. Build logs
For a completed build (succeeded or failed), download the logs to understand more about how the build went. App Center provides an archive with the following files:
The build step-specific logs (located in the build/ directory of the archive) are helpful for troubleshooting and understanding in what step and why the build failed.
4.2. The app (.ipa)
The .ipa file is an iOS device application archive file that contains the iOS app.
- Unsigned builds won’t produce an .ipa file. The artifact of an unsigned build is the .xcarchive file that can be used to generate an .ipa file with the Xcode Archives organizer.
- If the build is signed correctly, the .ipa file can be installed on a real device corresponding to the provisioning profile used when signing. More details about code signing and distribution with App Center can be found in App Center’s iOS code signing documentation.
- If the build hasn’t been signed, the .ipa file can be signed by the developer (for example, locally using codesign) or used for other purposes (for example, upload to Test service for UI testing on real devices or run in the simulator).
4.3. The symbols file (.dsym)
The .dsym files contain the debug symbols for the app.
- If you’ve previously integrated the App Center SDK in your app with the crash reporting module enabled, the crash reporting service requires this .dsym file for a build to display human readable (symbolicated) crash reports.
- If you’ve previously integrated another SDK for crash reporting purposes in your app (for example, HockeyApp SDK), the corresponding service requires the .dsym file to display human readable crash reports.
Keep in mind that the .dsym files don’t change upon code signing the .ipa . If you decide to code sign the build later, the .dsym generated before code signing is still valid.
Supported versions and requirements
Build machine Xcode version details are updated each time a new version of Xcode is added. We keep an eye on the latest versions released by Apple and include them as soon as possible on the VMs used to run the builds.
Разработка под Apple. Objective-C
Objective-C — это простой язык программирования, разработанный как язык объектно-ориентированного программирования. Objective-C, также как и C++ является расширением ANSI-C, и имеет поддержку таких возможностей, как описание классов, методов и свойств. В отличие от C++ Objective-C полностью совместим с языком С, то есть любой код, написанный на языке Си (это в первую очередь будет касаться сторонних библиотек), будет работать c Objective-C.
Настоятельно рекомендуется ознакомиться с основами языка Си (ссылка на предыдущую статью).
В самой первой статье на примере простого приложения для iOS мы уже сталкивались с классами, методами и объектами. Сегодня мы более подробно изучим данные конструкции и напишем более сложное приложение.
И реальный пример:
— (NSArray *)shipsAtPoint:(CGPoint)bombLocation withDamage:(BOOL) damaged;
В данном случае представлен метод с двумя аргументами. Метод начинается с знака «-» для методов экземпляра класса или с «+» для методов класса. Подробнее об отличиях мы поговорим позже.
Далее идет тип возвращаемых значений, он может быть (void), если ничего не возвращается, или (id), если возвращается объект, при этом тип его может быть любым ( NSString, NSArray, NSNumber, NSDictionary …). Однако рекомендуется по возможности явно указывать тип данных, для того, чтобы компилятор мог предупредить нас о возможной ошибке.
*Разрешается не писать тип возвращаемого значения и не ставить скобки в начале метода вообще, тогда это расценивается как (id). Также, когда мы указываем (IBAction), это аналогично (void), но при этом мы сообщаем Interface Builder, что этот метод будет вызван из интерфейса.
После идет название метода с маленькой буквы. Objective-C имеет не совсем обычные названия методов, которые разделены аргументами (в большинстве языков аргументы методов перечисляются после названия метода). В данном случае имя метода shipsAtPoint:withDamage: . Такой синтаксис во многом удобен, т.к. делает метод лучше читаемым.
К именам аргументов применяются те же правила, что и в С (не начинается с % и тп), при этом аргументы используются в реализации метода как локальные переменные.
Если имя метода слишком длинное, можно разделить его на строки, при этом Xcode умеет выравнивать их по знаку «:».
Как было сказано выше, есть instance methods (методы экземпляра класса) и class methods (методы класса).
Инстанс методы:
• Начинаются с «-»
• Оперируют с переменными экземпляра (инстанс-переменными) как с локальными переменными.
• Могут отправлять сообщения self и super. Разница в реализации: self использует вашу реализацию метода, super — реализацию метода суперкласса (не используется наследование).
В Objective-C объекты обмениваются между собой сообщениями, синтаксис сообщений: [получатель сообщение] ;
В качестве получателя выступает объект, а сообщением является метод. Сообщения могут содержать аргументы:
[объект метод:аргумент1 продолжениеНазванияМетода:аргумент2];
BOOL destroyed = [ship dropBomb:bombType at:dropPoint from:height];
Т.е. переменная destroyed получает возвращаемое методом dropBomb:at:from логическое значение типа BOOL. При этом реализуется метод объекта ship.
Методы класса адресуются классу и используются в трех случаях:
• для выделения памяти под объект, метод + (id)alloc ;
• для создания объекта с помощью фабрик, например, + (Ship *)motherShip ;
• методы для получения информации о структуре класса, + (int)
turrentsOnShipOfSize:(int)shipSize; (возвращает значение количества орудий на корабле определенного размера).
Методы класса не имеют доступа к инстанс-переменным.
Примеры вызова методов:
NSArray *myArray = [[NSArray alloc] init]; — создаем массив. При этом метод alloc является методом класса, а метод init — методом экземпляра класса (инстанса).
NSString *myString = [NSString stringWithString:@“Моя строка”]; — используем фабрику, при этом автоматически выделяется память и инициализируется инстанс. О преимуществах первого и второго подхода мы поговорим в статье управление памятью.
Мы немного отложили разговор о классах и объектах, чтобы наглядно представить, что это такое, и какое отношение они имеют друг к другу рассмотрим схему:
Таким образом, у класса может быть суперкласс, от которого он наследует методы, и инстанс (экземпляр класса) — конкретный объект, который использует переменные, методы и свойства определенные в классе. Существую абстрактные классы — это классы, предназначенные только для наследования, они не могут использоваться для непосредственного создания объекта.
Если взять в пример физические объекты, то «Транспортные средства» будут абстрактным суперклассом, «Легковые автомобили» — классом, а «BMW 318i» — экземпляром класса.
Класс мы описываем в .h (заголовки) и .m (реализация) файлах, синтаксис класса в файле заголовка (MyClass.h) выглядит так:
@interface MyClass : NSObject
<
NSString *name;
>
— (IBAction)valueChanged;
Здесь название класса MyClass, который наследует у суперкласса NSObject. Между фигурными скобками объявляются инстанс-переменные; а после скобок — методы и свойства.
В файле реализации (MyClass.m)
@implementation CalculatorBrain
— (IBAction)valueChanged <
//Какие-то действия
>
Cоздать экземпляр класса MyClass мы можем в другом классе, импортировав в его .h файл ссылку на #import «MyClass.h» наш класс и создав, его в файле реализации .m
@implementation MyApplicationViewController //какой-то класс
-(void)нашМетод <
MyClass *myClassObject = [[MyClass alloc] init]; //создали экземпляр класса
[myClassObject methodDefinedInMyClass]; //используем метод нашего класса
>
Свойства (property)
В редакции Objective-C 2.0 появились свойства (properties) и новый dot-синтаксис для доступа к ним. По сути свойства позволяют получать доступ к инстанс-переменным объекта используя dot-синтаксис: myObject.property
Свойства мы определяем в файле заголовка (.h) после фигурных скобок:
@interface MyClass : NSObject
<
double memoryStorage;
>
@property (double) memoryStorage; //свойство
— (IBAction)valueChanged; //методы
В файле реализации (.m) мы можем либо вручную прописать getter и setter для свойства:
– (double) memoryStorage <
return memoryStorage; //getter, просто возвращает значение memoryStorage
>
– (double) setMemoryStorage:(double)newValue <
memoryStorage = newValue; //setter
>
Либо использовать @synthesize memoryStorage , который синтезирует за нас стандартные сеттер и геттер. Если далее мы напишем свой геттер, например, то он будет использоваться, а сгенерированный геттер @synthesize будет проигнорирован. Также можно использовать
@synthesize (readonly) memoryStorage;
В этом случае генерируется только геттер (полезно для случаев, когда мы не хотим, чтобы кто-то имел доступ к изменению значения данного свойства).
Если мы хотим создать private property, свойство к которому может получить доступ только наш класс, используется следующий синтаксис. В файле реализации добавляется перед @implementation :
#import «ClassName.h»
@interface ClassName()
@property NSString *myString;
@end
@implementation ClassName
B //реализация класса
@end
Важно не забыть поставить скобки «()» после имени класса.
Как вы могли заметить, dot-синтаксис в свойствах значительно напоминает работу со структурами в Си. И это неспроста, объекты в Objective-C это по сути те же struct (структуры) в Cи, только в отличие от структур, имеющие методы.
На этом мы заканчиваем с теорией и переходим к практике!
Калькулятор
Сегодня мы создадим гораздо более сложное приложение — Калькулятор, где мы создадим собственный класс, познакомимся с новыми контроллами и
элементами UI.
Для начала создадим новый проект в Xcode и назовем его Calculator.
Создадим View-based Application — Xcode сгенерирует для нас View и Controller, а модель (наш собственный класс) мы создадим сами:
Назовем приложение Calculator и сохраним проект в папке Developer, как мы делали в прошлом примере.
Итак, перед нами наш проект:
Слева в списке файлов CalculatorViewController.h и CalculatorViewController.m — файлы заголовков и реализации нашего контроллера. CalculatorViewController.xib — наш графический интерфейс (View).
Создадим Модель. Нажмем Cmd + N или в меню File ➤ New ➤ New File…
Выберем Objective-C class, так как мы создаем наш собственный класс, нажмем далее и выберем суперкласс: NSObject и назовем наш класс CalculatorBrain. В итоге мы получим файлы CalculatorBrain.h и CalculatorBrain.m .
Начнем с нашего контроллера. Отроем файл CalculatorViewController.h .
Рекомендую также использовать Ассистент, который откроет CalculatorViewController.m параллельно с нашим файлом. Для этого нажмите Cmd + Alt + Return, а чтобы скрыть левую панель навигатора файлов, нажмите Cmd + 0:
В контроллере нам нужно создать:
1) outlets (инстанс-переменные, которые указывают на объекты в нашем графическом интерфейсе);
2) actions (методы, которые посылаются из графического интерфейса);
3) инстанс-переменную, которая указывает на нашу модель CalculatorBrain .
Добавим outlet, который будет отображать результат или текущее число, которое мы ввели:
@interface CalculatorViewController : UIViewController <
IBOutlet UILabel *display;
>
@end
Далее добавим инстанс-переменную, ссылающуюся на нашу модель, но перед этим также добавим ссылку на файл-заголовок нашей модели:
#import
#import «CalculatorBrain.h»
@interface CalculatorViewController : UIViewController <
IBOutlet UILabel *display;
CalculatorBrain *brain;
>
@end
Нам понадобится еще одна инстанс-переменная логического типа, для того, чтобы определить, что пользователь в процессе ввода числа, т.е. например, ввел 5 и далее нажал 3.
B CalculatorBrain *brain;
B BOOL userIsInTheMiddleOfTypingANumber;
Далее добавим методы. Всего в данный момент у нас предусмотрено два действия: нажатие кнопки с цифрами и нажатие кнопки с операцией:
>
— (IBAction)digitPressed:(UIButton *)sender;
— (IBAction)operationPressed:(UIButton *)sender;
@end
( UIButton * )sender в данном случае позволит нам узнать, какая кнопка нажата, так как передаст в сообщении объект UIButton в качестве аргумента.
Вот так это будет выглядеть:
Как вы помните, контроллер отвечает за управление элементами графического интерфейса. Мы закончили с файлом-заголовком нашего контроллера и можем перейти к созданию самого интерфейса, чтобы вы наглядно себе представляли, как будет выглядеть наш калькулятор.
Нажмите Cmd+T, чтобы открыть новую вкладку (контроллер нам еще понадобится, когда мы перейдем к его реализации). Выберите CalculatorViewController.xib в выпадающем списке:
Ассистент можно закрыть (Cmd + Return), а правую панель открыть (Cmd + Alt + 4). Если запомнить комбинации пока не удается, можно пользоваться кнопками в правом верхнем углу окна Xcode.
Наш контроллер расположен слева в виде светло-оранжевого полупрозрачного куба и называется File’s Owner , с ним мы и будем связывать элементы нашего UI, аналогично тому, как мы это делали в первой лекции.
Добавим UILabel и две кнопки, назовем первую «1», а вторую «+»:
Теперь свяжем данные элементы с контроллером, перетягиванием мыши с нажатым Ctrl от контроллера к UILabel и от UIButton к контроллеру. Для «1» выберем метод digitPressed , для «+» — operationPressed . Скопируем кнопки, отвечающие за цифры и переименуем их, чтобы получить клавиатуру калькулятора от 0 до 9. Аналогично поступим с операцией.
Когда мы копируем элемент интерфейса, связанный с контроллером, мы также копируем эту связь, поэтому нам не нужно будет соединять все кнопки с
контроллером.
*Однако будьте внимательнее и не перепутайте кнопки с цифрами с кнопками с операциями.
Выделив элемент вы можете поэкспериментировать с его внешним видом, а также познакомиться с другими вкладками, присутствующими в инспекторе (панель справа).
Вот, что у вас должно получиться в итоге:
Итак, перед тем как закончить работу с нашим контроллером, который обеспечит работу с интерфейсом и моделью, нам нужно написать реализацию модели (создать API, которое мы предоставим контроллеру).
Откроем новую вкладку и файл CalculatorBrain.h , используем инспектор, чтобы параллельно просматривать CalculatorBrain.m
В файле CalculatorBrain.h напишем:
@interface CalculatorBrain : NSObject <
B double operand;
>
@end
Чтобы устанавливать значение «операнда» мы можем реализовать метод — (void)setOperand:(double)aDouble; но мы используем свойства, которые рассматривали ранее:
>
@property double operand; //свойство для operand
— (double)performOperation:(NSString *)operation; //метод выполнить
операцию
@end
Контроллер может передавать нашей модели два типа значений — операцию и числовое значение. Для первого мы будем использовать метод performOperation , для второго свойство operand .
Перейдем к файлу реализации CalculatorBrain.m Синтезируем свойство operand и добавим метод:
@implementation CalculatorBrain
@synthesize operand;
— (double)performOperation:(NSString *)operation <
>
@end
Создадим обработку события «нажатие кнопки извлечения квадратного корня»:
— (double)performOperation:(NSString *)operation
<
if ([operation isEqual:@»√»])
<
operand = sqrt(operand);
>
return operand;
>
Мы только что отправили сообщение объекту operation , спросив у него соответствует ли он строке @»√». isEqual — метод класса NSObject , от которого наследуют все сабклассы, в том числе и NSString , экземпляром которого является operation.
Для операций вроде извлечения квадратного корня или 1/x получение результата возможно мгновенно, но для операций с двумя операндами 10 + 5 =
нам нужно хранить в памяти первый операнд.
Поэтому вернемся к файлу CalculatorBrain.h и добавим две инстанс-переменные:
@interface CalculatorBrain : NSObject <
double operand;
NSString *waitingOperation;
double waitingOperand;
>
Теперь в файле CalculatorBrain.m добавим поддержку операций с двумя операндами:
operand = sqrt(operand);
>
else
<
[self performWaitingOperation];
waitingOperation = operation;
waitingOperand = operand;
>
return operand;
>
Мы отправляем сообщение performWaitingOperation получателю self , т.е. объекту, который сейчас отправляет сообщение (данному экземпляру класса CalculatorBrain). performWaitingOperation будет частным (private) методом, к которому мы не будем иметь доступа из контроллера, поэтому не нужно объявлять этот метод в файле-заголовке CalculatorBrain.h .
Добавим метод performWaitingOperation , но необходимо поместить его перед performOperation , чтобы компилятор знал, что такой метод существует, поскольку он не объявлен в .h-файле:
@implementation CalculatorBrain
@synthesize operand;
— (void)performWaitingOperation <
if ([@»+» isEqual:waitingOperation])
<
operand = waitingOperand + operand;
>
else if ([@»*» isEqual:waitingOperation])
<
operand = waitingOperand * operand;
>
else if ([@»-» isEqual:waitingOperation])
<
operand = waitingOperand — operand;
>
else if ([@»/» isEqual:waitingOperation])
<
if (operand) <
operand = waitingOperand / operand;
>
>
>
Мы просто в зависимости от waitingOperation выполняем соответствующее действие, а при делении мы дополнительно проверяем, что делитель не равен «0».
С моделью мы закончили, перейдем к реализации контроллера. У нас уже открыт в одной из вкладок CalculatorViewController.m . В файле вы видите методы, которые сгенерировал Xcode для нам. Они нам сейчас не нужны, выделите всё между @implementation и @end и удалите.
Нам нужно реализовать методы нажатия на цифровую кнопку и кнопку операции, также нам нужно создать экземпляр класса CalculatorBrain :
— (CalculatorBrain *)brain <
if (!brain) brain = [[CalculatorBrain alloc] init];
return brain;
>
Нам не нужно несколько объектов класса CalculatorBrain , поэтому мы перед тем как создать новый проверяем не существует ли уже такой объект.
Реализуем метод operationPressed:
— (IBAction)operationPressed:(UIButton *)sender <
if (userIsInTheMiddleOfTypingANumber) <
self.brain.operand = [display.text doubleValue];
userIsInTheMiddleOfTypingANumber = NO;
>
NSString *operation = sender.titleLabel.text;
double result = [self.brain performOperation:operation];
display.text = [NSString stringWithFormat:@»%g», result];
>
Вначале мы проверяем не в середине ли ввода чисел мы находились, если да, то мы должны передать модели ( self.brain ) полный операнд (число, которое мы видим на дисплее), и так как мы нажали на кнопку операции userIsInTheMiddleOfTypingANumber получит значение NO .
Далее мы выполняем получаем значение titleLabel.text (надпись на нажатой кнопке) и передаем сообщение модели выполнить вычисление. После чего мы просто выводим результат на экран.
Теперь реализуем метод digitPressed :
— (IBAction)digitPressed:(UIButton *)sender <
NSString *digit = sender.titleLabel.text;
if (userIsInTheMiddleOfTypingANumber) <
display.text = [display.text stringByAppendingString:digit];
>
else
<
display.text = digit;
userIsInTheMiddleOfTypingANumber = YES;
>
>
В начале мы получили значение названия кнопки (в данном случае какую-то цифру), далее, если мы в середине ввода числа, то мы добавляем цифру в конец строки, используя стандартный метод NSString — stringByAppendingString , подробнее об этом методе можно посмотреть в документации. И выводим новое число на экран.
Если мы только начали ввод, то мы просто выводим число на экран и меняем значение userIsInTheMiddleOfTypingANumber на YES , т.к. мы начали ввод числа.
*По умолчанию значение userIsInTheMiddleOfTypingANumber при старте программы равно NO .
Мы закончили с калькулятором и можем запускать его в симуляторе (Cmd + R) или кнопка Build and Run.
Задание
Наш калькулятор пока далек от того, что мы привыкли называть калькулятором, поэтому в качестве домашнего задания предлагается доработать приложение:
1) Добавить возможность совершать операции с плавающей точкой. Для этого вам может потребоваться добавить еще один метод floatPressed, а для отлавливания плавающей точки (нам ведь не нужно числа вроде 12.34.1244.012) посмотрите в документации класс NSRange и его метод rangeOfString .
2) У нас отсутствуют такие операции как 1/x, +/–, C (сброс). Добавление этих операций достаточно тривиально, однако нужно учесть то, что делить на ноль, например, нельзя.
3) Реализуйте систему сообщений об ошибках (при делении на ноль, попытке извлечения квадратного корня из отрицательного числа и тп). Посмотрите класс UIAlertView , представляющий стандартные информационные сообщения в iOS.
Задания повышенной сложности:
1) Добавьте возможность работы с памятью, используя кнопки M+, MC (стереть число в памяти), MR (извлечь число из памяти).
2) Добавьте возможность работы с Sin/Cos, а также переключатель режимов Deg/Rad. Для переключателя можно использовать UISegmentedControl, а для выполнения операций — стандартные мат. операции sin(operand);