Grok 3 xAI в разработке iOS приложения «Страны Мира» (отображение границ)

В предыдущей статье я писала о создании с помощью различных ИИ довольно простого тестовое iOS приложение Countries («Страны Мира»), которое показывает все страны Мира по регионам (Европа, Азия, Латинская Америка и т.д.) и для каждой страны отображается её название, флаг, численность населения и ВВП. Если вы выбираете какую-то страну, то на карте будет представлена страна с местоположением её столицы..

Я хочу добавить на карту границы страны:

На данный момент я ничего не знаю о том, где взять информацию о границах стран, но это должен быть либо JSON файл, либо JSON информация из интернета, которую можно считать с помощью async / await и декодировать в Swift.

Я хочу обратиться к Grok 3 xAI и попросить его написать для меня Swift код, который бы добавил границы выбранной страны на карту. 

Сразу скажу, что я не считаю, что какой-то ИИ может вместо вас написать целое iOS приложение. В лучшем случае ИИ может предложить вам действительно замечательный код с небольшим количеством ошибок (2-5), которые кстати иногда трудно найти даже опытному программисту высшей квалификации ( у меня так было с Claude 3.5 Sonnet, хотя считаю его одним из лучших ИИ для кодинга). Если вы находите эти ошибки самостоятельно или, продолжая спрашивать ИИ, вы действительно получаете высокопрофессиональное iOS приложение, потратив на его разработку существенно меньше времени.

Вот под таким углом мы и будем рассматривать нашу задачу о границах стран на карте, которую будем решать вместе с Grok 3 xAI. 

Забегая вперед, скажу, что Grok 3 xAI практически полностью справился со всеми моими запросами, предложив совершенно неожиданное для меня решение со встроенным в MapKit декодером MKGeoJSONDecoder. Тем не менее я попросила Grok 3 создать Codable Модель в Swift для декодирования GeoJSON данных, имеющих не тривиальную JSON структуру, и он с ней превосходно справился. По ходу дела Grok 3 вообще выдал целый «Комплексный обзор о получении GeoJSON данных стран» и предоставил очень четкие «рассуждения», которые вполне можно использовать как отличный обучающий материал.

Так что Grok 3 xAI в этой конкретной задаче зарекомендовал себя серьезным помощником в iOS программировании.

Код iOS приложения Countries с границами стран находится на Github.

Полностью протокол взаимодействия с Grok 3 можно посмотреть здесь. Ниже представлены лишь отдельные фрагменты этого протокола.

Читать далее

Использование DeepSeek при создании iOS приложения Countries Страны Мира. Часть 3. Swift 6 concurrency и Map.

Это продолжение. Начало находится на «Использование DeepSeek при создании iOS приложения Countries Страны Мира. Часть 1« и «Использование DeepSeek при создании iOS приложения Countries Страны Мира. Часть 2″.

Код находится на Github.

Swift 6 concurrency

Преобразуй это iOS приложение в Swift 6 concurrency версию

В результате мы получаем рассуждения, которые заняли 57 секунд:

Вот перевод этих рассуждений:

Хорошо, мне нужно преобразовать существующее приложение SwiftUI iOS для использования возможностей многопоточности Swift 6. Позвольте мне начать с того, что Swift 6 предлагает в плане многопоточности. Я знаю, что Swift 5.5 представил async / await, акторы actor и структурную многопоточность, но ожидается, что Swift 6 улучшит их более строгими проверками многопоточности, полной изоляцией акторов actor и, возможно, некоторыми новыми возможностями.
Читать далее

Использование DeepSeek при создании iOS приложения Countries Страны Мира. Часть 2. Численность населения, ВВП, флаг.

Это продолжение. Начало находится в посте «Использование DeepSeek при создании iOS приложения Countries Страны Мира. Часть 1. Список стран.».

Код находится на Github.

Корректируем поля в Индикаторе 

И опять в  улучшенной реализации с населением, ВВП, столицы и эмодзи флага мы имеем НЕВЕРНЫЙ код для ResponseMetadata 

 struct ResponseMetadata: Decodable {
            let page: Int
            let pages: Int
            let perPage: String
            let total: Int
 }

вместо ПРАВИЛЬНОГО:

struct ResponseMetadata: Decodable {
            let page: Int
            let pages: Int
            let perPage: String
            let total: Int
            
            enum CodingKeys: String, CodingKey {
                case page, pages, total
                case perPage = "per_page"
            }
 }

Но даже если мы будем использовать правильный код, мы все равно получим ту же самую ошибку: “ The data couldn’t be read because it is missing.”

Небольшая отладка и просмотр JSON данных для численности населения:

[ 
 {
    "page": 1,
    "pages": 9,
    "per_page": 30,
    "total": 266,
    "sourceid": "2",
    "lastupdated": "2025-01-28"
  },
  [
    {
      "indicator": {
        "id": "NY.GDP.MKTP.CD",
        "value": "GDP (current US$)"
      },
      "country": {
        "id": "ZH",
        "value": "Africa Eastern and Southern"
      },
      "countryiso3code": "AFE",
      "date": "2022",
      "value": 1191423176242.96,
      "unit": "",
      "obs_status": "",
      "decimal": 0
    },
....
  ]
]

… позволит определить, что поля countryiso2code, указанного в Модели  struct Entry, НЕТ в JSON данных, а есть поле countryiso3code, которое является 3-х буквенным IOS кодом страны, но это не то, что нам нужно. Нам нужен 2-х буквенный IOS код страны, который находится в id в объекте country индикатора, так что DeepSeek немного запутался. И не зря, он не рискнул взять id в объекте country response indicator, так как id в объекте Country response country — это  3-х буквенный IOS кодом страны. Нелогичность конструирования JSON данных в API World Bank приводит  DeepSeek в замешательство.

Давайте сообщим ему об этом:

В JSON indicator данных нет поля countryios2code.
Используй id из объекта country в JSON indicator данных.
Читать далее

Использование DeepSeek при создании iOS приложения Countries Страны Мира. Часть 1. Список стран.

Я решила создать довольно простое тестовое iOS приложение Countries, которое показывает все страны Мира по регионам (Европа, Азия, Латинская Америка и т.д.) и для каждой страны её название и флаг, a также численность населения population и размер ВВП (валового внутреннего продукта) gdp:

Код находится на Github.

Создание iOS приложения Countries для отображения стран Мира 

Давайте попросим DeepSeek создать полноценного iOS приложения Countries для отображения стран Мира по категориям с использованием SwiftUI и данных Всемирного Банка. 

Создай iOS приложение, которое показывает страны Мира по категориям.
Используй информацию Всемирного Банка.

Мы использовали режимы “DeepThinkingn (R1)” и «Search», который обещает применять логические выводы для получения результата:

Рассуждения 1

В результате мы получаем рассуждения, которые длятся 184 секунд (более 3 минут):

И вот их перевод:

Хорошо, мне нужно создать  iOS приложение с помощью SwiftUI, которое отображает страны мира, классифицированные с помощью API Всемирного банка. Позвольте мне разбить мои действия на шаги.
Сначала я должен выяснить, какие endpoints API Всемирного банка использовать. Пользователь упомянул категории, так что, может быть, API индикаторов indicator? Позвольте мне проверить. У Всемирного банка есть API стран countries, которое может получать список стран с информацией о регионах region, в которых они находятся. O, верно, регионы region могут быть категориями. Например, страны могут быть сгруппированы по регионам, таким как Европа, Азия и т. д.
Читать далее

Подводные камни Swift Actor: понимание и управление Reentrancy

Это перевод статьи The Swift Actor Pitfall: Understanding and Managing Reentrancy.

Введение

Сегодня я обсуждаю эту важную тему “Подводные камни Swift Actor”, так что если вы работали со Swift довольно долго, скорее всего, вы сталкивались с этим. Позвольте мне дать вам быстрое определение акторов actor — это ссылочный (reference) ТИП, похожий на класс class, но в отличие от классов позволяет только одной задаче получать доступ к своему изменяемому состоянию в один конкретный момент времени. 

Так что, как мы все знаем, гонка данных (data race) происходит только тогда, когда несколько задач пытаются получить доступ к одному и тому же изменяемому состоянию в одно и то же время. Это означает что при использовании акторов actor мы можем эффективно предотвратить гонки данные. Так ли это?

К сожалению, ответ “Нет” и причина в том, что есть actor reentrancy. Что собой в действительности представляет собой actor reentrancy и чем же actor reentrancy отличается от данных гонки (data race)? 

Читать далее

Многопоточность по шагам: системы с изменяемым состоянием

Это перевод статьи Concurrency Step-by-Step: Stateful Systems

В предыдущих первом и во втором постах я использовал исключительно систему только для чтения. Это далеко от реальных задач! Реальные приложения пишут данные в локальное хранилище, в удаленные сервисы и, как правило, находятся «по самые уши» в изменяемом состоянии.

В этом посте мы собираемся создать приложение SwiftUI, которое работает с изменяемым состоянием, размещенным на (мнимой) удаленной сетевой службе.

Краткие заметки

Как и во всех предыдущих постах этой серии, я буду игнорировать ошибки и требовать Xcode 16. Я также остаюсь новичком в SwiftUI, а здесь больше SwiftUI, чем в других постах.

Еще одно важное замечание заключается в том, что я пытался найти подходящую бесплатную удаленную службу, но безуспешно. Сначала я думал, что это будет решающим фактором. Но потом я понял, что это может быть счастливой случайностью. Читайте дальше, чтобы узнать почему!

«Удаленная» (‘remote’) система

Для начала нам нужна какая-то удаленная (remote) служба для взаимодействия. Весь смысл этого упражнения — иметь дело с состоянием, поэтому важно, чтобы эта система сохраняла состояние. Но я не смог ее найти, поэтому мы просто притворимся.

final class RemoteSystem: @unchecked Sendable {
	private var state = false
	private let queue = DispatchQueue(label: "RemoteSystemQueue")

	func toggleState(completionHandler: @escaping @Sendable () -> Void) {
		queue.async {
			self.state.toggle()
			completionHandler()
		}
	}
	
	func readState(completionHandler: @escaping @Sendable (Bool) -> Void) {
		queue.async {
			completionHandler(self.state)
		}
	}
}

Это симуляция “удаленной” системы, которая управляет значением ровно одной булевой Bool переменной var state. Внешний мир может переключать или считывать текущее булевское Bool значение этой переменной state, но он должен делать это асинхронно.

Я уверен, вы также заметили, что я решил реализовать это с помощью Dispatch. Чтобы это работало со Swift 6, мы должны сообщить компилятору, что мы взяли на себя ответственность за потокобезопасность, отметив ТИП RemoteSystem как @unchecked Sendable. Нам также нужно несколько @Sendable замыканий.

Читать далее

Многопоточность по шагам: Чтение из хранилища

Это перевод статьи Concurrency Step-by-Step: Reading from Storage

Тема, которая снова и снова возникает в связи с многопоточностью (concurrency) в Swift, — это попытка «сделать компилятор довольным». Вы просто хотите, чтобы глупые ошибки исчезли. Пытаясь сделать это, вы натыкаетесь на множество вещей, таких как Sendable или @preconcurrency. Вы даже можете начать менять класс class на актор actor , и непонятно, насколько это может отличаться, но это даже то же самое количество символов. Поэтому вы просто начинаете бросаться синтаксисом в проблему. Это понятно!

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

Добро пожаловать во вторую часть «Concurrency Swift шаг за шагом». Первая часть «Concurrency Swift шаг за шагом» находится здесь. Цель этих постов— проработать общую задачу, чтобы помочь сформировать реальное понимание того, что происходит. В прошлый раз мы рассматривали сетевой запрос. На этот раз мы загрузим модель из хранилища данных.

Краткие заметки

Я проигнорирую обработку ошибок, чтобы сосредоточиться на многопоточности.
Я не очень хорош в SwiftUI. Нам потребуется Xcode 16 или более поздняя версия.
Мне было очень трудно придумать пример, который был бы одновременно простым и иллюстрировал проблему. Я думаю, что локальное хранилище будет работать хорошо, но нам придется сделать его довольно надуманным. Я не думаю, что это действительно уведет нас от каких-либо идей. Но я все равно хочу это подчеркнуть, потому что идея «хранилища данных» здесь не будет похожа на SwiftData, CoreData или другие вещи, которые может использовать реальное приложение.

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

Расставляем детали по местам

Итак, начнем с определения интерфейса нашей системы хранения данных.

class DataModel {
	let name: String

	init(name: String) {
		self.name = name
	}
}

class Store {
	func loadModel(named name: String) async -> DataModel {
		DataModel(name: name)
	}
}

Я же говорил, что это будет надуманно!
Есть только ТИП DataModel для хранения простого значения name, а также Store, который «загружает» для нас модели. Ни один из них не делает ничего полезного. Но на самом деле нас интересуют только ТИПы и их интерфейсы.

Теперь нам нужен SwiftUI View, чтобы связать все это воедино.

struct ContentView: View {
	@State private var store = Store()
	@State private var name: String = "---"

	var body: some View {
		Text("hello \(name)!")
			.task {
				self.name = await store.loadModel(named: "friends").name
			}
	}
}

Этот фрагмент кода должен очень удобно поместиться на одном экране. Неплохо!

Отступление: система ТИПов

Я вставил небольшой комментарий выше, который заслуживает большего внимания.
Но на самом деле нас интересуют только ТИПы и их интерфейсы.
Это важно и довольно нетривиально! 

Читать далее

Многопоточность по шагам:  Сетевой запрос.

Это перевод статьи Concurrency Step-by-Step: A Network Request

Это было ошибкой, что я не уделял больше внимания людям, которые только начинают. Я попытаюсь исправить это, углубившись в некоторые основы. Давайте шаг за шагом рассмотрим сетевой запрос с помощью SwiftUI.

Предисловие

Несколько быстрых заметок. Во-первых, я практически опустил всю обработку ошибок. Я сделал это, чтобы сосредоточиться на теме многопоточности. Я также не особо искушенный разработчик SwiftUI, поэтому здесь могут быть некоторые неоптимальные шаблоны.

Важно, что этот пост был написан для Xcode 16. Если вы используете более раннюю версию, некоторые вещи будут работать не так.

Расставляем все по местам

Давайте рассмотрим очень простую программу SwiftUI, которая загружает что-то из сети. Мне нужно было найти бесплатный API для использования, и я остановился на Robohash. Это восхитительное сочетание простого, интересного и необычного.

Поскольку наши данные будут загружаться из сети, нам нужно обработать случай, когда нам нечего отображать. Начнем с небольшого View, которое может обрабатывать Optional изображение cgImage.

struct LoadedImageView: View {
	let cgImage: CGImage?
	
	var body: some View {
		if let cgImage {
			Image(cgImage, scale: 1.0, label: Text("Robot"))
		} else {
			Text("no robot yet")
		}
	}
}

Я использовал CGImage здесь, поэтому этот код может работать без изменений на всех платформах Apple.

Теперь мы можем перейти к более интересным вещам. Давайте создадим View, которое фактически загружает некоторые данные из сети.

struct RobotView: View {
	@State private var cgImage: CGImage?

	var body: some View {
		LoadedImageView(cgImage: cgImage)
			.onAppear {
				loadImageWithGCD()
			}
	}
	
	private func loadImageWithGCD() {
		let request = URLRequest(url: 
                 URL(string: "https://robohash.org/hash-this-text.png")!)

		let dataTask = URLSession.shared.dataTask(with: request) { data, _, _ in
			guard let data else { return }

			DispatchQueue.main.async {
				let provider = CGDataProvider(data: data as CFData)!

				self.cgImage = CGImage(
					pngDataProviderSource: provider,
					decode: nil,
					shouldInterpolate: false,
					intent: .defaultIntent
				)
			}
		}

		dataTask.resume()
	}
}

Я использовал GCD (Grand Central Dispatch). Надеюсь, это выглядит очень привычно, даже если вы вообще не разбираетесь в многопоточности. Но я все равно хочу отметить, что я использую здесь режим компиляции использования языка Swift 6, и этот код компилируется без ошибок.

Читать далее

Опыт создания iOS приложения Countries  с помощью Claude 3.5 Sonnet, ChatGPT 4.o1-mini, ChatGPT 4.o1-preview и Gemini 2.0 Flash

Я занимаюсь исследованием того, как можно эффективно создавать iOS приложения с помощью ИИ: ChatGPT 4.o1, Claude 3.5 Sonnet, Gemini 2.0 Flash, а также с помощью сред разработки (IDE) типа Cursor AI и Alex Sidebar. Для этих целей я решила создать довольно простое тестовое iOS приложение Countries, которое показывает все страны Мира по регионам (Европа, Азия, Латинская Америка и т.д.) и для каждой страны её название и флаг. Если вы выбираете какую-то страну, то о ней сообщается дополнительная информация о численности населения population и размере ВВП (валового внутреннего продукта) gdp:

Я считаю Claude 3.5 Sonnet лучшим AI (ИИ) для программирования iOS приложений с точки зрения архитектуры всего приложения. Поэтому эту ИИ мы будем использовать в качестве основной и начнем именно с её использования.

Забегая немного вперед и обрисовывая наши планы, скажу, что сначала мы получим от Claude 3.5 Sonnet вариант этого iOS приложения с использованием старого GCD (Grand Central Dispatch) для многопоточной выборки данных из интернета (Github).

Затем мы сделаем рефакторинг кода, чтобы перейти к более современной версии многопоточности с использованием async await (Github).

И в заключении мы постараемся перейти на ещё более продвинутую версию многопоточности Swift 6 strict concurrency, в которой гарантировано нет “гонки данных” (data races) (Github). Но чтобы оценить этот последний вариант нашего приложения, необходимо иметь представление о возможностях Swift 6 concurrency, которое можно получить в компактной форме из превосходной статьи Concurrency Step-by-Step: A Network Request или её перевода на русский язык Многопоточность по шагам:  Сетевой запрос.

С помощью Claude 3.5 Sonnet при первом же обращении нам удастся получить превосходное iOS приложение с Моделью данных для расшифровки JSON данных, с CountriesViewModel, которая выбирает всю необходимую информацию с сервера Всемирного банка, преобразует JSON данные в данные Модели и предоставляет их Views для отображения на экране пользователя. Мы не зададим не единой ссылки на сайты Всемирного банка, ни единого намека на структуру данных, и тем не менее получим полноценное iOS приложение. 

Однако запустив это приложение, мы получим ошибку, связанную с декодированием JSON данных, полученных с сервера Всемирного Банка, которые оказались не совсем стандартными. И дальнейшие “уговоры”  Claude 3.5 Sonnet не помогли решить нам эту проблему. Так что пришлось смириться с тем фактом, что Claude 3.5 Sonnet плохо “декодирует” несложные, хотя и нестандартные JSON данные.

Нам придется обратиться к другим ИИ.

Gemini 1.5 Flash также оказался неспособным дать правильное решение, и только ChatGPT 4.o1-mini и Gemini 2.0 Flash справятся с этой казалось бы легкой задачей, их ответ и пришлось интегрировать в успешное в остальном приложение, полученное Claude 3.5 Sonnet. Надо сказать, что ChatGPT 4.o1-Preview, который, как было объявлено, отличается способностью к построению логических цепочек даст вообще фантастический результат, который вряд ли смог бы предоставить даже программист супер высокого класса, он сложнее, чем результат полученный ChatGPT 4.o1-mini, но это очень красивое решение (Примечание 2 в конце статьи).

Все нейросети:  Claude 3.5 Sonnet,  Gemini 2.0 Flash, ChatGPT 4.o1-mini, ChatGPT 4.o1-Preview прекрасно справились с рефакторингом асинхронного кода с CGD на async await и далее на Swift 6 strict concurrency.

Читать далее

Cursor AI в iOS разработке. Приложение «Фото с Flickr.com». Часть 2.

Мы исследуем как работает Cursor AI на примере создания iOS приложения с выборкой данных с ресурса публичных фотографий на Flickr.com.
UI этого iOS приложения представляет собой строку поиска вверху и сетку Grid под ней для отображения миниатюр этих фотографий. Вы можете кликнуть на любую из фотографий и получить подробную информацию о ней:

Начальное решение этой задачи, то есть создание такого iOS приложения, описано в предыдущем посте «Cursor AI в iOS разработке. Приложение «Фото с Flickr.com». Часть 1.»
Окончательный вариант iOS приложения PhotomaniaCursor находится на Github.com.
Здесь мы продолжим совершенствовать наше приложение PhotomaniaCursor, которое может выбирать публичные фотографии с Flickr.com. И вот наш первый вопрос:

Как часто происходит обращение к  Flickr API при наборе текста в строке поиска?

Наша следующая версия 8 приложения PhotomaniaCursor будет работать в точности как и версия 7, но мы хотим четко увидеть, при каких тегах tags осуществляется обращение к  Flickr API при наборе пользователем текста в строке поиска searchText. Для этого мы будем печатать в FlickrViewModel теги tags, для которых этот запрос выполняется, :

if let encodedTags = tags.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
           print ("--------------------------- \(encodedTags)")
          urlString += "&tags=\(encodedTags)           
}
Читать далее