Xcode Feuille de chaleur
Xcode - Environnement de développement intégré d'Apple
Xcode est l'environnement de développement intégré (IDE) d'Apple pour macOS, utilisé pour développer des logiciels pour macOS, iOS, iPadOS, watchOS et tvOS. Il comprend une série d'outils de développement de logiciels comprenant un éditeur de code source, un débogueur et un constructeur d'interface utilisateur graphique.
Sommaire
- [Installation] (LINK_0)
- [Pour commencer] (LINK_0)
- Structure du projet
- [Constructeur d'interface] (LINK_0)
- [Swift Programming] (LINK_0)
- [UIKit Development] (LINK_0)
- [SwiftUI Development] (LINK_0)
- [Données de base] (LINK_0)
- [Réseau] (__LINK_0___)
- [Tests] (LINK_0)
- [Débogage] (LINK_0)
- [Performance] (LINK_0)
- [App Store Connect] (LINK_0)
- [Shorts clavier] (LINK_0)
- [Meilleures pratiques] (LINK_0)
- [Dépannage] (LINK_0)
Installation
Exigences du système
# macOS 12.5 or later
# At least 8GB of RAM (16GB recommended)
# At least 50GB of available disk space
# Check macOS version
sw_vers
# Check available disk space
df -h
Télécharger et installer
# Download from Mac App Store
# Search for "Xcode" and click "Get"
# Or download from Apple Developer Portal
# https://developer.apple.com/xcode/
# Command Line Tools (if needed separately)
xcode-select --install
# Verify installation
xcode-select -p
xcodebuild -version
```_
### Première configuration de lancement
```bash
# Accept license agreement
sudo xcodebuild -license accept
# Install additional components when prompted
# iOS Simulator, watchOS Simulator, etc.
# Set up Apple ID for development
# Xcode > Preferences > Accounts > Add Apple ID
```_
## Commencer
### Créer un nouveau projet
```swift
// File > New > Project
// Choose template:
// - iOS App
// - macOS App
// - watchOS App
// - tvOS App
// - Multiplatform App
// Project configuration:
// Product Name: MyApp
// Team: Your Development Team
// Organization Identifier: com.yourname.myapp
// Bundle Identifier: com.yourname.myapp
// Language: Swift
// Interface: SwiftUI or Storyboard
// Use Core Data: Optional
// Include Tests: Recommended
Modèles de projet
// iOS App with UIKit
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
}
}
// iOS App with SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.padding()
}
}
Structure du projet
MyApp.xcodeproj/
├── MyApp/
│ ├── AppDelegate.swift
│ ├── SceneDelegate.swift
│ ├── ViewController.swift
│ ├── Main.storyboard
│ ├── Assets.xcassets
│ ├── LaunchScreen.storyboard
│ ├── Info.plist
│ ├── Models/
│ ├── Views/
│ ├── Controllers/
│ └── Services/
├── MyAppTests/
│ └── MyAppTests.swift
├── MyAppUITests/
│ └── MyAppUITests.swift
└── Products/
└── MyApp.app
Constructeur d'interface
Scénario de base
// Creating outlets
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var submitButton: UIButton!
@IBOutlet weak var textField: UITextField!
// Creating actions
@IBAction func submitButtonTapped(_ sender: UIButton) {
guard let text = textField.text, !text.isEmpty else {
showAlert(message: "Please enter some text")
return
}
titleLabel.text = text
textField.text = ""
}
// Segues
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let detailVC = segue.destination as? DetailViewController {
detailVC.data = selectedData
}
}
}
// Programmatic segue
performSegue(withIdentifier: "showDetail", sender: self)
Mise en page automatique
// Programmatic constraints
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.topAnchor, constant: 20),
view.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: 16),
view.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: -16),
view.heightAnchor.constraint(equalToConstant: 50)
])
// Using NSLayoutAnchor
let constraints = [
titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
titleLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
submitButton.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20),
submitButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
submitButton.widthAnchor.constraint(equalToConstant: 200),
submitButton.heightAnchor.constraint(equalToConstant: 44)
]
NSLayoutConstraint.activate(constraints)
// Stack Views
let stackView = UIStackView(arrangedSubviews: [titleLabel, submitButton])
stackView.axis = .vertical
stackView.spacing = 20
stackView.alignment = .center
stackView.distribution = .fill
Vues personnalisées
@IBDesignable
class CustomButton: UIButton {
@IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
}
}
@IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
@IBInspectable var borderColor: UIColor = .clear {
didSet {
layer.borderColor = borderColor.cgColor
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.cornerRadius = cornerRadius
layer.borderWidth = borderWidth
layer.borderColor = borderColor.cgColor
}
}
Programmation rapide
Syntaxe de base
// Variables and Constants
var mutableVariable = "Hello"
let immutableConstant = "World"
// Data Types
let integer: Int = 42
let double: Double = 3.14159
let float: Float = 2.718
let boolean: Bool = true
let string: String = "Swift"
// Optionals
var optionalString: String? = "Optional"
var implicitlyUnwrappedOptional: String! = "Implicitly Unwrapped"
// Optional Binding
if let unwrappedString = optionalString {
print("Value: \(unwrappedString)")
}
// Guard Statement
guard let unwrappedString = optionalString else {
return
}
// Nil Coalescing
let result = optionalString ?? "Default Value"
Fonctions et fermetures
// Functions
func greet(name: String, age: Int = 25) -> String {
return "Hello, \(name)! You are \(age) years old."
}
// Function with multiple return values
func minMax(array: [Int]) -> (min: Int, max: Int)? {
guard !array.isEmpty else { return nil }
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
// Closures
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
let filtered = numbers.filter { $0 > 2 }
let sum = numbers.reduce(0) { $0 + $1 }
// Trailing closure syntax
UIView.animate(withDuration: 0.3) {
self.view.alpha = 0.5
}
Classes et structures
// Structure
struct Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func greet() -> String {
return "Hello, I'm \(name) and I'm \(age) years old."
}
mutating func haveBirthday() {
age += 1
}
}
// Class
class Vehicle {
var brand: String
var model: String
var year: Int
init(brand: String, model: String, year: Int) {
self.brand = brand
self.model = model
self.year = year
}
func description() -> String {
return "\(year) \(brand) \(model)"
}
}
// Inheritance
class Car: Vehicle {
var numberOfDoors: Int
init(brand: String, model: String, year: Int, numberOfDoors: Int) {
self.numberOfDoors = numberOfDoors
super.init(brand: brand, model: model, year: year)
}
override func description() -> String {
return "\(super.description()) with \(numberOfDoors) doors"
}
}
// Protocols
protocol Drawable {
func draw()
}
extension Car: Drawable {
func draw() {
print("Drawing a car: \(description())")
}
}
Gestion des erreurs
// Define errors
enum NetworkError: Error {
case invalidURL
case noData
case decodingError
}
// Throwing function
func fetchData(from urlString: String) throws -> Data {
guard let url = URL(string: urlString) else {
throw NetworkError.invalidURL
}
// Simulate network call
guard let data = "Sample data".data(using: .utf8) else {
throw NetworkError.noData
}
return data
}
// Error handling
do {
let data = try fetchData(from: "https://api.example.com")
print("Received data: \(data)")
} catch NetworkError.invalidURL {
print("Invalid URL provided")
} catch NetworkError.noData {
print("No data received")
} catch {
print("Unknown error: \(error)")
}
// Try? and try!
let optionalData = try? fetchData(from: "https://api.example.com")
// let forcedData = try! fetchData(from: "https://api.example.com") // Use with caution
UIKit Développement
Afficher les contrôleurs
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// View is about to appear
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// View has appeared
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// View is about to disappear
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// View has disappeared
}
private func setupUI() {
view.backgroundColor = .systemBackground
title = "My View Controller"
// Add navigation bar button
navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .add,
target: self,
action: #selector(addButtonTapped)
)
}
@objc private func addButtonTapped() {
// Handle add button tap
}
}
Tableau des vues
class TableViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
private var items = ["Item 1", "Item 2", "Item 3"]
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
}
private func setupTableView() {
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
}
extension TableViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
}
extension TableViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
print("Selected: \(items[indexPath.row])")
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
}
Vues de collection
class CollectionViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
private var items = Array(1...20)
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
}
private func setupCollectionView() {
collectionView.dataSource = self
collectionView.delegate = self
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 100, height: 100)
layout.minimumInteritemSpacing = 10
layout.minimumLineSpacing = 10
collectionView.collectionViewLayout = layout
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
}
}
extension CollectionViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
cell.backgroundColor = .systemBlue
return cell
}
}
extension CollectionViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Selected item: \(items[indexPath.item])")
}
}
Navigation
// Push view controller
let detailVC = DetailViewController()
navigationController?.pushViewController(detailVC, animated: true)
// Present modally
let modalVC = ModalViewController()
let navController = UINavigationController(rootViewController: modalVC)
present(navController, animated: true)
// Dismiss modal
dismiss(animated: true)
// Pop view controller
navigationController?.popViewController(animated: true)
// Pop to root
navigationController?.popToRootViewController(animated: true)
// Tab Bar Controller
let firstVC = FirstViewController()
let secondVC = SecondViewController()
firstVC.tabBarItem = UITabBarItem(title: "First", image: UIImage(systemName: "1.circle"), tag: 0)
secondVC.tabBarItem = UITabBarItem(title: "Second", image: UIImage(systemName: "2.circle"), tag: 1)
let tabBarController = UITabBarController()
tabBarController.viewControllers = [firstVC, secondVC]
Développement SwiftUI
Vues fondamentales
import SwiftUI
struct ContentView: View {
@State private var name = ""
@State private var isToggled = false
@State private var selectedOption = 0
var body: some View {
NavigationView {
VStack(spacing: 20) {
// Text
Text("Hello, SwiftUI!")
.font(.largeTitle)
.foregroundColor(.blue)
// Image
Image(systemName: "star.fill")
.font(.system(size: 50))
.foregroundColor(.yellow)
// TextField
TextField("Enter your name", text: $name)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.horizontal)
// Button
Button("Tap Me") {
print("Button tapped!")
}
.buttonStyle(.borderedProminent)
// Toggle
Toggle("Enable notifications", isOn: $isToggled)
.padding(.horizontal)
// Picker
Picker("Options", selection: $selectedOption) {
Text("Option 1").tag(0)
Text("Option 2").tag(1)
Text("Option 3").tag(2)
}
.pickerStyle(SegmentedPickerStyle())
.padding(.horizontal)
Spacer()
}
.navigationTitle("SwiftUI Demo")
}
}
}
Listes et navigation
struct Item: Identifiable {
let id = UUID()
let name: String
let description: String
}
struct ListView: View {
let items = [
Item(name: "Item 1", description: "Description 1"),
Item(name: "Item 2", description: "Description 2"),
Item(name: "Item 3", description: "Description 3")
]
var body: some View {
NavigationView {
List(items) { item in
NavigationLink(destination: DetailView(item: item)) {
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.description)
.font(.subheadline)
.foregroundColor(.secondary)
}
}
}
.navigationTitle("Items")
}
}
}
struct DetailView: View {
let item: Item
var body: some View {
VStack {
Text(item.name)
.font(.largeTitle)
.padding()
Text(item.description)
.font(.body)
.padding()
Spacer()
}
.navigationTitle("Detail")
.navigationBarTitleDisplayMode(.inline)
}
}
Administration de l ' État
// ObservableObject
class UserData: ObservableObject {
@Published var username = ""
@Published var isLoggedIn = false
func login() {
isLoggedIn = true
}
func logout() {
isLoggedIn = false
username = ""
}
}
// Using ObservableObject
struct LoginView: View {
@StateObject private var userData = UserData()
var body: some View {
VStack {
if userData.isLoggedIn {
Text("Welcome, \(userData.username)!")
Button("Logout") {
userData.logout()
}
} else {
TextField("Username", text: $userData.username)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button("Login") {
userData.login()
}
.disabled(userData.username.isEmpty)
}
}
.padding()
}
}
// Environment Objects
struct ParentView: View {
@StateObject private var userData = UserData()
var body: some View {
ChildView()
.environmentObject(userData)
}
}
struct ChildView: View {
@EnvironmentObject var userData: UserData
var body: some View {
Text("User: \(userData.username)")
}
}
Modificateurs personnalisés
struct CardModifier: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
}
}
extension View {
func cardStyle() -> some View {
modifier(CardModifier())
}
}
// Usage
Text("Hello, World!")
.cardStyle()
Données de base
Modèle de données
// Create .xcdatamodeld file
// Add Entity: Person
// Add Attributes: name (String), age (Int16), email (String)
import CoreData
// NSManagedObject subclass
@objc(Person)
public class Person: NSManagedObject {
}
extension Person {
@nonobjc public class func fetchRequest() -> NSFetchRequest<Person> {
return NSFetchRequest<Person>(entityName: "Person")
}
@NSManaged public var name: String?
@NSManaged public var age: Int16
@NSManaged public var email: String?
}
Pile de données de base
import CoreData
class CoreDataManager {
static let shared = CoreDataManager()
private init() {}
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "DataModel")
container.loadPersistentStores { _, error in
if let error = error {
fatalError("Core Data error: \(error)")
}
}
return container
}()
var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
func saveContext() {
if context.hasChanges {
do {
try context.save()
} catch {
print("Save error: \(error)")
}
}
}
}
CRUD Opérations
class PersonService {
private let coreDataManager = CoreDataManager.shared
// Create
func createPerson(name: String, age: Int16, email: String) {
let person = Person(context: coreDataManager.context)
person.name = name
person.age = age
person.email = email
coreDataManager.saveContext()
}
// Read
func fetchAllPersons() -> [Person] {
let request: NSFetchRequest<Person> = Person.fetchRequest()
do {
return try coreDataManager.context.fetch(request)
} catch {
print("Fetch error: \(error)")
return []
}
}
func fetchPersons(with name: String) -> [Person] {
let request: NSFetchRequest<Person> = Person.fetchRequest()
request.predicate = NSPredicate(format: "name CONTAINS[cd] %@", name)
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
do {
return try coreDataManager.context.fetch(request)
} catch {
print("Fetch error: \(error)")
return []
}
}
// Update
func updatePerson(_ person: Person, name: String, age: Int16, email: String) {
person.name = name
person.age = age
person.email = email
coreDataManager.saveContext()
}
// Delete
func deletePerson(_ person: Person) {
coreDataManager.context.delete(person)
coreDataManager.saveContext()
}
}
Réseautage
URLSession
import Foundation
class NetworkManager {
static let shared = NetworkManager()
private init() {}
func fetchData<T: Codable>(from url: URL, type: T.Type, completion: @escaping (Result<T, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(NetworkError.noData))
return
}
do {
let decodedData = try JSONDecoder().decode(type, from: data)
completion(.success(decodedData))
} catch {
completion(.failure(error))
}
}.resume()
}
func postData<T: Codable>(to url: URL, body: Data, type: T.Type, completion: @escaping (Result<T, Error>) -> Void) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = body
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(NetworkError.noData))
return
}
do {
let decodedData = try JSONDecoder().decode(type, from: data)
completion(.success(decodedData))
} catch {
completion(.failure(error))
}
}.resume()
}
}
enum NetworkError: Error {
case noData
case invalidURL
case decodingError
}
// Usage
struct Post: Codable {
let id: Int
let title: String
let body: String
let userId: Int
}
class PostService {
func fetchPosts(completion: @escaping (Result<[Post], Error>) -> Void) {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
completion(.failure(NetworkError.invalidURL))
return
}
NetworkManager.shared.fetchData(from: url, type: [Post].self, completion: completion)
}
}
Async/Attention (iOS 15+)
class ModernNetworkManager {
static let shared = ModernNetworkManager()
private init() {}
func fetchData<T: Codable>(from url: URL, type: T.Type) async throws -> T {
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(type, from: data)
}
func postData<T: Codable>(to url: URL, body: Data, type: T.Type) async throws -> T {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = body
let (data, _) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(type, from: data)
}
}
// Usage with async/await
class ModernPostService {
func fetchPosts() async throws -> [Post] {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
throw NetworkError.invalidURL
}
return try await ModernNetworkManager.shared.fetchData(from: url, type: [Post].self)
}
}
// In SwiftUI
struct PostsView: View {
@State private var posts: [Post] = []
@State private var isLoading = false
var body: some View {
NavigationView {
List(posts, id: \.id) { post in
VStack(alignment: .leading) {
Text(post.title)
.font(.headline)
Text(post.body)
.font(.caption)
.foregroundColor(.secondary)
}
}
.navigationTitle("Posts")
.task {
await loadPosts()
}
}
}
private func loadPosts() async {
isLoading = true
do {
posts = try await ModernPostService().fetchPosts()
} catch {
print("Error loading posts: \(error)")
}
isLoading = false
}
}
Essais
Essai en unité
import XCTest
@testable import MyApp
class CalculatorTests: XCTestCase {
var calculator: Calculator!
override func setUpWithError() throws {
calculator = Calculator()
}
override func tearDownWithError() throws {
calculator = nil
}
func testAddition() {
let result = calculator.add(2, 3)
XCTAssertEqual(result, 5)
}
func testDivisionByZero() {
XCTAssertThrowsError(try calculator.divide(10, 0)) { error in
XCTAssertEqual(error as? CalculatorError, CalculatorError.divisionByZero)
}
}
func testAsyncOperation() async throws {
let result = try await calculator.asyncCalculation()
XCTAssertGreaterThan(result, 0)
}
func testPerformance() {
measure {
for _ in 0..<1000 {
_ = calculator.complexCalculation()
}
}
}
}
Test d'assurance-chômage
import XCTest
class MyAppUITests: XCTestCase {
var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launch()
}
func testLoginFlow() throws {
let usernameTextField = app.textFields["Username"]
let passwordSecureTextField = app.secureTextFields["Password"]
let loginButton = app.buttons["Login"]
XCTAssertTrue(usernameTextField.exists)
XCTAssertTrue(passwordSecureTextField.exists)
XCTAssertTrue(loginButton.exists)
usernameTextField.tap()
usernameTextField.typeText("testuser")
passwordSecureTextField.tap()
passwordSecureTextField.typeText("password123")
loginButton.tap()
let welcomeLabel = app.staticTexts["Welcome"]
XCTAssertTrue(welcomeLabel.waitForExistence(timeout: 5))
}
func testTableViewNavigation() throws {
let tableView = app.tables["ItemsTable"]
XCTAssertTrue(tableView.exists)
let firstCell = tableView.cells.element(boundBy: 0)
firstCell.tap()
let detailView = app.navigationBars["Detail"]
XCTAssertTrue(detailView.waitForExistence(timeout: 5))
let backButton = app.navigationBars.buttons.element(boundBy: 0)
backButton.tap()
XCTAssertTrue(tableView.waitForExistence(timeout: 5))
}
}
Déboguement
Points d'arrêt
// Set breakpoints by clicking on line numbers
// Conditional breakpoints: Right-click breakpoint > Edit Breakpoint
func processData(_ data: [String]) {
for (index, item) in data.enumerated() {
// Set conditional breakpoint: index == 5
print("Processing: \(item)")
// Symbolic breakpoint for specific method calls
// Debug > Breakpoints > Create Symbolic Breakpoint
// Symbol: -[UIViewController viewDidLoad]
}
}
Imprimer le débogage
// Basic print
print("Debug message")
// Print with separator and terminator
print("Value 1", "Value 2", separator: " | ", terminator: "\n")
// Debug print (only in debug builds)
debugPrint("Debug information")
// Custom debug description
extension Person: CustomDebugStringConvertible {
var debugDescription: String {
return "Person(name: \(name ?? "nil"), age: \(age))"
}
}
// Dump for detailed object inspection
dump(person)
// Assert for debugging
assert(age >= 0, "Age cannot be negative")
// Precondition for runtime checks
precondition(users.count > 0, "Users array cannot be empty")
Instruments
# Launch Instruments
# Product > Profile (Cmd+I)
# Common instruments:
# - Time Profiler: CPU usage analysis
# - Allocations: Memory usage tracking
# - Leaks: Memory leak detection
# - Energy Log: Battery usage analysis
# - Network: Network activity monitoring
Rendement
Gestion de la mémoire
// Weak references to avoid retain cycles
class Parent {
var children: [Child] = []
}
class Child {
weak var parent: Parent?
}
// Unowned references (use when reference will never be nil)
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
}
// Capture lists in closures
class ViewController: UIViewController {
var completion: (() -> Void)?
func setupCompletion() {
// Strong reference cycle
completion = {
self.dismiss(animated: true)
}
// Weak reference to avoid cycle
completion = { [weak self] in
self?.dismiss(animated: true)
}
// Unowned reference (use when self will never be nil)
completion = { [unowned self] in
self.dismiss(animated: true)
}
}
}
Techniques d'optimisation
// Lazy properties
class DataManager {
lazy var expensiveResource: ExpensiveResource = {
return ExpensiveResource()
}()
}
// Computed properties with caching
class Calculator {
private var _cachedResult: Double?
private var _lastInput: Double?
func expensiveCalculation(input: Double) -> Double {
if let cached = _cachedResult, _lastInput == input {
return cached
}
let result = performExpensiveCalculation(input)
_cachedResult = result
_lastInput = input
return result
}
private func performExpensiveCalculation(_ input: Double) -> Double {
// Expensive calculation here
return input * input
}
}
// Efficient collection operations
let numbers = Array(1...1000000)
// Use lazy evaluation for chained operations
let result = numbers
.lazy
.filter { $0 % 2 == 0 }
.map { $0 * 2 }
.prefix(10)
.reduce(0, +)
// Use appropriate collection types
var uniqueItems = Set<String>() // O(1) lookup
var orderedItems = [String]() // O(1) append, O(n) search
var keyValuePairs = [String: Any]() // O(1) lookup by key
Connexion App Store
Configuration de l'application
// Info.plist configuration
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>My App</string>
<key>CFBundleIdentifier</key>
<string>com.yourcompany.myapp</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
Construire et archiver
# Archive for distribution
# Product > Archive
# Validate archive
# Window > Organizer > Archives > Validate App
# Distribute archive
# Window > Organizer > Archives > Distribute App
# Choose distribution method:
# - App Store Connect
# - Ad Hoc
# - Enterprise
# - Development
Présentation de l'App Store
# Upload to App Store Connect
# Use Xcode Organizer or Application Loader
# App Store Connect configuration:
# - App Information
# - Pricing and Availability
# - App Store Information
# - Screenshots and Previews
# - App Review Information
# - Version Information
# - Build selection
# Submit for Review
# App Store Connect > My Apps > Your App > Submit for Review
Raccourcis clavier
Navigation
# File Navigation
Cmd+Shift+O # Open Quickly
Cmd+Shift+J # Reveal in Navigator
Cmd+Ctrl+Up # Switch between .h and .m files
Cmd+Ctrl+Left/Right # Navigate back/forward
# Code Navigation
Ctrl+6 # Jump to method/function
Cmd+L # Go to line
Cmd+Shift+F # Find in project
Cmd+Shift+G # Find next
Édition
# Code Editing
Cmd+/ # Comment/uncomment
Cmd+[ # Shift left
Cmd+] # Shift right
Cmd+Ctrl+E # Edit all in scope
Cmd+Shift+A # Add documentation
# Refactoring
Cmd+Ctrl+E # Rename
Cmd+Shift+A # Add documentation
Construction et fonctionnement
# Build and Run
Cmd+R # Run
Cmd+B # Build
Cmd+Shift+K # Clean
Cmd+U # Test
Cmd+I # Profile
Cmd+. # Stop
Déboguement
# Debugging
F6 # Step over
F7 # Step into
F8 # Step out
Cmd+Ctrl+Y # Continue
Cmd+Y # Activate/deactivate breakpoints
Meilleures pratiques
Code Organisation
// MARK: - Use MARK comments for organization
class ViewController: UIViewController {
// MARK: - Properties
@IBOutlet weak var tableView: UITableView!
private var dataSource: [String] = []
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
// MARK: - Setup
private func setupUI() {
// UI setup code
}
// MARK: - Actions
@IBAction func buttonTapped(_ sender: UIButton) {
// Action handling
}
// MARK: - Private Methods
private func updateData() {
// Private method implementation
}
}
// MARK: - Extensions for protocol conformance
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Cell configuration
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = dataSource[indexPath.row]
return cell
}
}
Gestion des erreurs
// Use Result type for better error handling
enum APIError: Error {
case networkError
case decodingError
case serverError(Int)
}
func fetchData(completion: @escaping (Result<[Item], APIError>) -> Void) {
// Implementation
}
// Use do-catch for throwing functions
do {
let data = try JSONSerialization.data(withJSONObject: dictionary)
// Process data
} catch {
print("JSON serialization failed: \(error)")
}
// Use guard for early returns
func processUser(_ user: User?) {
guard let user = user else {
print("User is nil")
return
}
guard user.isActive else {
print("User is not active")
return
}
// Process active user
}
Rendement
// Use appropriate data structures
// Array for ordered collections
// Set for unique items with fast lookup
// Dictionary for key-value pairs
// Avoid force unwrapping
// Use optional binding or nil coalescing instead
if let value = optionalValue {
// Use value
}
let finalValue = optionalValue ?? defaultValue
// Use weak references in delegates
protocol MyDelegate: AnyObject {
func didComplete()
}
class MyClass {
weak var delegate: MyDelegate?
}
Dépannage
Erreurs de construction communes
# Clean build folder
Product > Clean Build Folder (Cmd+Shift+K)
# Reset simulator
Device > Erase All Content and Settings
# Clear derived data
~/Library/Developer/Xcode/DerivedData
# Delete the folder for your project
# Update provisioning profiles
Xcode > Preferences > Accounts > Download Manual Profiles
# Fix code signing issues
Project Settings > Signing & Capabilities
# Ensure correct team and bundle identifier
Problèmes liés à l'exécution
// Debug memory issues
// Enable Address Sanitizer in scheme settings
// Edit Scheme > Run > Diagnostics > Address Sanitizer
// Debug UI issues on main thread
// Edit Scheme > Run > Diagnostics > Main Thread Checker
// Debug view hierarchy
// Debug > View Debugging > Capture View Hierarchy
// Check for retain cycles
// Debug Memory Graph (Debug navigator)
Problèmes de simulation
# Reset simulator
Device > Erase All Content and Settings
# Restart simulator
Device > Restart
# Reset simulator to factory settings
xcrun simctl erase all
# List available simulators
xcrun simctl list devices
# Boot specific simulator
xcrun simctl boot "iPhone 14 Pro"
Résumé
Xcode est l'IDE complet d'Apple pour le développement d'applications sur toutes les plateformes Apple. Les principales caractéristiques sont les suivantes :
- Environnement de développement intégré: chaîne d'outils complète pour le développement iOS, macOS, watchOS et tvOS
- Constructeur d'interface: outil de conception visuelle pour créer des interfaces utilisateur
- Swift et Objective-C Support: Support en langage complet avec mise en évidence syntaxique et achèvement du code
- Simulator: Tester les applications sur différentes configurations d'appareils sans matériel physique
- Outils de débogage: Débogueur puissant avec points de rupture, analyse de mémoire et profilage des performances
- Cadre de test: capacités intégrées de test d'unité et de test d'interface utilisateur
- Instruments: Outils d'analyse des performances pour l'utilisation de la mémoire, du processeur et de l'énergie
- ** Intégration de l'App Store** : Soumission et distribution d'applications sans soudure
- Contrôle de la version: intégré Support Git avec diff visuelle et outils de fusion
- Documentation: Production intégrée de navigateur de documentation et de documentation de code
Xcode fournit tout ce qu'il faut pour créer, tester, déboguer et distribuer des applications de haute qualité pour l'écosystème d'Apple, ce qui en fait l'outil essentiel pour le développement de la plateforme Apple.