Swift | Вопросы собесов
2.23K subscribers
30 photos
962 links
Download Telegram
🤔 Какие есть ссылки в ARC?

Сильные (strong), слабые (weak), и безвладельческие (unowned). Сильные удерживают объект, остальные — нет, и нужны для предотвращения retain cycle.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊3
🤔 Что может быть ключом и значением для dictionary?

Словарь (Dictionary) представляет собой коллекцию пар ключ-значение, где каждый ключ должен быть уникальным. Чтобы использовать какой-либо тип в качестве ключа словаря, этот тип должен соответствовать протоколу Hashable. Это требование обусловлено тем, что Swift использует хеш-таблицу для хранения элементов словаря, что обеспечивает быстрый доступ к его элементам.

🚩Ключи

Должны быть уникальными: Каждый ключ в словаре должен быть уникальным. При попытке добавить в словарь элемент с ключом, который уже существует в словаре, старое значение будет заменено на новое.
Должны соответствовать протоколу Hashable: Это означает, что тип ключа должен иметь способность быть правильно хешированным. Большинство базовых типов Swift (например, String, Int, Double и др.) уже соответствуют Hashable, поэтому их можно использовать в качестве ключей без дополнительных усилий.

🚩Значения

Могут быть любого типа: Значения в словаре могут быть любого типа, и они не обязаны соответствовать протоколу Hashable.
Могут повторяться: Разные ключи могут иметь одинаковые значения.
var personAge: [String: Int] = ["John": 30, "Sara": 25]


🚩Собственные типы в качестве ключей

Вы также можете использовать собственные пользовательские типы в качестве ключей словаря, но для этого ваш тип должен соответствовать протоколу Hashable. Это включает в себя реализацию требуемых методов для сравнения на равенство (==) и хеширования (hash(into:)).
struct Person: Hashable {
var name: String
var id: Int
}

var peopleDictionary: [Person: String] = [Person(name: "John", id: 1): "Engineer"]


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 Какие есть способы вёрстки?

- Auto Layout (наиболее гибкий и универсальный).
- Stack Views (удобные группы элементов с автоматической раскладкой).
- Frame-based layout (устаревший способ вручную).
- Constraint anchors и визуальный язык разметки (VFL).
- Interface Builder (через Xcode UI).
- SwiftUI (декларативный подход).


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие есть инструменты для работы с потоками?

В Swift есть несколько инструментов для работы с многопоточностью и параллельным выполнением кода. Вот основные из них:

🟠GCD (Grand Central Dispatch) – главный инструмент для потоков
GCD – это низкоуровневая технология, позволяющая управлять задачами (тасками) в очередях (DispatchQueue).
DispatchQueue.global(qos: .background).async {
print("Фоновый поток")

DispatchQueue.main.async {
print("Вернулись в главный поток")
}
}


🟠OperationQueue – более удобный API для задач
OperationQueue – это более гибкая и объектно-ориентированная альтернатива GCD.
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2 // Ограничение на 2 задачи одновременно

queue.addOperation {
print("Операция 1")
}

queue.addOperation {
print("Операция 2")
}


🟠Actors – безопасная работа с потоками в Swift 5.5+
С actor можно работать с потоками без гонок данных, потому что все его свойства защищены от одновременного доступа.
actor Counter {
private var value = 0

func increment() {
value += 1
}

func getValue() -> Int {
return value
}
}

let counter = Counter()

Task {
await counter.increment()
print(await counter.getValue()) // Потокобезопасный доступ
}


🟠Task & Async/Await (Swift 5.5+) – современный подход к асинхронности
С async/await код становится читаемым и удобным.
func fetchData() async -> String {
try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 секунда задержки
return "Данные загружены"
}

Task {
let result = await fetchData()
print(result)
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1
🤔 Как обращаются к объектам класса и объектам структуры?

Классы передаются по ссылке, а структуры по значению.
- К объектам класса обращаются через ссылку (let obj = MyClass()), изменения в одной переменной отразятся на всех экземплярах.
- К объектам структуры обращаются как к копиям, каждое присваивание создает новый объект (let obj = MyStruct()).


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие есть фрейм ворк механизмы в айос для выполненной бэкраунд задач?

В iOS для выполнения фоновых задач существуют несколько ключевых механизмов:

🚩Основные механизмы

🟠Grand Central Dispatch (GCD): Используется для асинхронного выполнения задач на глобальных или пользовательских очередях.
DispatchQueue.global(qos: .background).async {
// Фоновая задача
}


🟠OperationQueue: Высокоуровневый API для управления очередями операций с возможностью указания зависимостей.
let queue = OperationQueue()
queue.addOperation {
// Фоновая операция
}


🟠Background Fetch: Позволяет приложению периодически загружать новые данные в фоновом режиме.
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// Фоновое обновление данных
completionHandler(.newData)
}


🟠BGTaskScheduler: Новый фреймворк для планирования и выполнения фоновых задач.
import BackgroundTasks

func scheduleBackgroundTask() {
let request = BGAppRefreshTaskRequest(identifier: "com.example.app.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
try? BGTaskScheduler.shared.submit(request)
}


🟠URLSession Background Transfers: Выполнение загрузки и выгрузки данных в фоновом режиме.
let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background")
let session = URLSession(configuration: configuration)
let url = URL(string: "https://example.com/largefile")!
let task = session.downloadTask(with: url)
task.resume()


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие есть состояния в жизненном цикле приложения?

– Not running — приложение не запущено.
– Inactive — запущено, но не получает событий (например, переход между экранами).
– Active — активно и реагирует на действия пользователя.
– Background — работает в фоновом режиме (например, обновляет контент).
– Suspended — приложение в памяти, но не выполняет код (возможен переход в этот режим из background).


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 Что под капотом стэка?

Это абстрактная структура данных, работающая по принципу LIFO (Last In, First Out), что означает "последний пришёл — первый вышел". Это значит, что последний добавленный элемент будет первым при извлечении из стека. Под капотом реализации стека могут быть разные, и они зависят от конкретного языка программирования и задач, которые необходимо решить.

🟠Массивы
Один из самых распространённых способов реализации стека — это использование массива. В такой реализации элементы стека хранятся в массиве, и индекс последнего элемента (вершина стека) отслеживается отдельной переменной.
struct Stack<Element> {
private var storage: [Element] = []

mutating func push(_ element: Element) {
storage.append(element)
}

mutating func pop() -> Element? {
return storage.popLast()
}

func peek() -> Element? {
return storage.last
}

var isEmpty: Bool {
return storage.isEmpty
}
}


🟠Связные списки
Стек можно реализовать с использованием связных списков, где каждый элемент списка содержит данные и ссылку на следующий элемент в стеке. Вершина стека в такой реализации — это начало связного списка.
class Node<Element> {
var value: Element
var next: Node?

init(value: Element) {
self.value = value
}
}

struct Stack<Element> {
private var head: Node<Element>?

mutating func push(_ element: Element) {
let node = Node(value: element)
node.next = head
head = node
}

mutating func pop() -> Element? {
let node = head
head = head?.next
return node?.value
}

func peek() -> Element? {
return head?.value
}

var isEmpty: Bool {
return head == nil
}
}


🟠Стек вызовов
Это системный стек, который используется во время выполнения программы для хранения информации о вызовах функций/методов. Он хранит адреса возврата, параметры функций, локальные переменные и другие данные, необходимые для управления вызовами функций и их возврата.

🚩Зачем он нужен?

Обратную польскую нотацию для вычисления арифметических выражений. Управление вызовами функций в программном стеке. Поддержка операций undo в приложениях.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 Что известно про IRS?

IRS может означать разное в зависимости от контекста, но в iOS-разработке, если ты имел в виду:
- Input Response System (в неформальной терминологии) — реакция на ввод (клавиатура, тап, жест).
- В архитектуре Swift часто это относится к реакции на события пользовательского ввода.
Если ты имел в виду другое значение (например, из налогообложения или архитектурных систем) — уточни, пожалуйста.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊7🤔2
🤔 Какой путь проделывает ивент , когда пользователь нажимает на приложение?

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


🚩Разберём путь события подробнее

🟠Пользователь нажимает на иконку (SpringBoard)
iOS-устройства управляются системой SpringBoard – это оболочка, отвечающая за домашний экран, иконки, фоновые процессы.
Когда пользователь тапает на иконку приложения, SpringBoard отправляет событие UIApplicationLaunchOptionsKey в систему.

🟠iOS загружает процесс приложения
Если приложение не запущено:
- iOS создаёт новый процесс и выделяет память.
- Загружаются зависимости (библиотеки, фреймворки).
- Создаётся объект UIApplication.

🟠Вызывается `application(_:didFinishLaunchingWithOptions:)`
Здесь приложение инициализируется и загружается основной UI.
Метод в AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
print("Приложение запущено")
return true
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 Разница между Void и ()?

Void и () — это одно и то же. Void — это просто псевдоним для пустого кортежа (). Оба обозначают, что функция не возвращает значение. Разница лишь в синтаксисе: Void читается как «ничего не возвращает», а () — как «пустое значение».


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊2
🤔 Почему по дефолту дабл, а не фолт?

В Swift (и во многих других языках программирования) по умолчанию числовой литерал с плавающей запятой интерпретируется как `Double`, а не Float.

🚩Основные причины:

🟠Бóльшая точность
Double имеет 64 бита, а Float32 бита. Это значит, что Double может хранить более точные значения, что особенно важно при математических вычислениях.

🟠Совместимость со стандартными API
Большинство API и стандартных библиотек Swift (например, sin(), cos(), pow()) работают именно с Double.
Например:

   let x = 3.14 // По умолчанию это Double
let y = sin(x) // sin() принимает Double


🟠Производительность на современных процессорах
На современных 64-битных процессорах операции с Double выполняются так же быстро или даже быстрее, чем с Float, из-за оптимизаций в аппаратном обеспечении.

🟠Меньше неожиданных ошибок округления
Float может округлять числа с потерей точности, что может привести к неожиданным результатам.
Пример ошибки округления в Float:

   let a: Float = 0.1 + 0.2
print(a == 0.3) // false 😱


🚩Как сделать `Float` вручную?
Если всё же нужен Float, надо указать это явно:
let number: Float = 3.14


или
let number = 3.14 as Float


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 Какие виды ссылок бывают в Swift?

– strong — по умолчанию, удерживает объект в памяти.
– weak — не увеличивает счётчик ссылок и становится nil, когда объект освобождён.
– unowned — не увеличивает счётчик, но не обнуляется (используется, когда объект точно не должен быть уничтожен до использования).


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 В чём разница между вью и леером?

В iOS и macOS приложениях, разработанных с использованием Swift или Objective-C, UIView и CALayer играют ключевые роли в управлении и отображении пользовательского интерфейса. Несмотря на то, что они тесно связаны, между ними есть важные различия. Давайте рассмотрим их подробнее.

🚩UIView

UIView — это базовый класс для всех элементов пользовательского интерфейса в iOS. Он представляет собой прямоугольную область на экране, которая может отображать контент и реагировать на события пользователя, такие как нажатия, жесты и касания.
UIView предоставляет множество свойств и методов для управления внешним видом, положением и поведением представления. Примеры включают frame, bounds, center, backgroundColor, alpha, isHidden, и subviews. UIView также поддерживает анимации, автолэйаут и работу с событийной системой.
UIView обрабатывает пользовательские события, такие как касания и жесты. Он предоставляет методы, такие как touchesBegan, touchesMoved, и touchesEnded, для обработки этих событий.
   let myView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
myView.backgroundColor = .blue
view.addSubview(myView)


🚩CALayer

CALayer — это базовый класс для всех графических слоев, используемых в Core Animation. Он обеспечивает низкоуровневую поддержку для рендеринга, анимации и композиции графики.
CALayer предоставляет свойства для управления внешним видом, такими как backgroundColor, borderWidth, cornerRadius, shadowOpacity, contents (для отображения изображений), и transform. CALayer также поддерживает анимации, используя ключевые кадры и основные анимации. CALayer не обрабатывает пользовательские события напрямую. Эти задачи оставлены на UIView, который может содержать один или несколько слоев.
   let myLayer = CALayer()
myLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
myLayer.backgroundColor = UIColor.blue.cgColor
view.layer.addSublayer(myLayer)


🚩Основные различия между UIView и CALayer

🟠По уровеню абстракции
UIView
— это более высокий уровень абстракции, предназначенный для работы с пользовательским интерфейсом и обработкой событий. CALayer — более низкоуровневый элемент, который фокусируется на рендеринге и анимации графики.

🟠Реакция на события
UIView обрабатывает события пользовательского интерфейса. CALayer не обрабатывает события, но предоставляет возможности для рендеринга и анимации.

🟠Иерархия
UIView может содержать другие представления (subviews) и управлять их иерархией. CALayer может содержать другие слои (sublayers) и управлять их иерархией.

🟠Анимации
UIView использует UIView.animate для создания анимаций, которые высокоуровневые и просты в использовании. CALayer использует CAAnimation и его подклассы для создания анимаций, которые более гибкие и мощные, но требуют большего количества кода.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🤔 Какого типа очереди main и global?

- DispatchQueue.main — serial очередь, исполняется на главном потоке;
-
DispatchQueue.global() — concurrent очередь, исполняется в пуле фоновых потоков GCD.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие знаешь способы внедрения зависимостей?

Внедрение зависимостей (Dependency Injection, DI) в iOS-приложениях позволяет сделать код более модульным, тестируемым и поддерживаемым. Рассмотрим основные способы внедрения зависимостей в Swift.

🟠Внедрение через инициализатор (Initializer Injection)
Это самый распространенный и рекомендуемый способ. Зависимости передаются в объект через его инициализатор.
protocol NetworkServiceProtocol {
func fetchData()
}

class NetworkService: NetworkServiceProtocol {
func fetchData() {
print("Данные загружены")
}
}

// Класс, которому нужна зависимость
class ViewModel {
private let networkService: NetworkServiceProtocol

init(networkService: NetworkServiceProtocol) {
self.networkService = networkService
}

func loadData() {
networkService.fetchData()
}
}

// Использование
let networkService = NetworkService()
let viewModel = ViewModel(networkService: networkService)
viewModel.loadData()


🟠Внедрение через свойства (Property Injection)
Зависимость передается через свойство класса.
class ViewModel {
var networkService: NetworkServiceProtocol?

func loadData() {
networkService?.fetchData()
}
}

// Использование
let viewModel = ViewModel()
viewModel.networkService = NetworkService()
viewModel.loadData()


🟠Внедрение через метод (Method Injection)
Зависимость передается непосредственно в метод, который её использует.
class ViewModel {
func loadData(with networkService: NetworkServiceProtocol) {
networkService.fetchData()
}
}

// Использование
let viewModel = ViewModel()
let networkService = NetworkService()
viewModel.loadData(with: networkService)


🟠Использование Service Locator (Антипаттерн)
Класс сам запрашивает зависимость у глобального локатора.
class ServiceLocator {
static let shared = ServiceLocator()

private var services: [String: Any] = [:]

func register<T>(_ service: T) {
let key = String(describing: T.self)
services[key] = service
}

func resolve<T>() -> T? {
let key = String(describing: T.self)
return services[key] as? T
}
}

// Регистрация зависимостей
let locator = ServiceLocator.shared
locator.register(NetworkService() as NetworkServiceProtocol)

// Использование
class ViewModel {
func loadData() {
let networkService: NetworkServiceProtocol? = ServiceLocator.shared.resolve()
networkService?.fetchData()
}
}


🟠Использование DI-контейнеров (например, Swinject)
Специальные библиотеки помогают управлять зависимостями.
import Swinject

let container = Container()
container.register(NetworkServiceProtocol.self) { _ in NetworkService() }

class ViewModel {
private let networkService: NetworkServiceProtocol

init(networkService: NetworkServiceProtocol) {
self.networkService = networkService
}

func loadData() {
networkService.fetchData()
}
}

// Разрешение зависимости через контейнер
let networkService = container.resolve(NetworkServiceProtocol.self)!
let viewModel = ViewModel(networkService: networkService)
viewModel.loadData()


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 С какими сложностями мы можем сортировать данные массива?

Сложности могут быть связаны с:
- Алгоритмом: у разных алгоритмов разная временная сложность (например, O(n log n) у быстрой сортировки).
- Типами данных: если элементы не Comparable — нужна дополнительная логика сравнения.
- Объёмом данных: при большом объёме массивов возникает нагрузка по памяти и времени.
- Стабильностью сортировки: при необходимости сохранить порядок одинаковых элементов.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Для каких сущностей работает copy on write?

Механизм Copy-on-Write (CoW) используется для оптимизации производительности и использования памяти при копировании объектов. Этот механизм особенно полезен для неизменяемых (immutable) структур данных. CoW часто ассоциируется со стандартными коллекциями и собственными типами данных, реализованными как структуры (value types), такие как Array, String, Dictionary, и Set.

🚩Принцип работы Copy-on-Write

Работает так, что копия объекта создаётся только в тот момент, когда происходит попытка модификации. До этого момента все копии объекта фактически ссылаются на одни и те же данные в памяти. Это позволяет сэкономить как время, так и память, поскольку избегается ненужное дублирование данных, когда оно не требуется.

🚩Как это работает в Swift

Автоматически применяет механизм CoW к своим стандартным коллекциям, таким как Array, String, Dictionary, и Set. Это означает, что при передаче этих объектов в функции или при их копировании реальное дублирование данных происходит только в случае модификации одной из копий. Таким образом, если вы создаёте копию массива и не изменяете его, обе переменные будут указывать на одни и те же данные в памяти. Как только вы модифицируете одну из копий, Swift создаст реальную копию данных для этой копии, обеспечивая независимость данных между оригиналом и копией.
var originalArray = [1, 2, 3]
var copiedArray = originalArray // На этом этапе данные не дублируются

copiedArray.append(4) // Теперь данные копируются, потому что copiedArray модифицируется


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 Что известно о Swift Intermediate Language (SIL)?

SIL (Swift Intermediate Language) — это промежуточное представление кода, которое Swift-компилятор использует между стадиями компиляции и генерации байткода.
SIL:
- Позволяет проводить оптимизации на уровне Swift-кода.
- Более абстрактен, чем машинный код, но ближе к нему, чем сам Swift.
- Существует в двух формах: raw SIL (до оптимизаций) и canonical SIL (после).
- Используется для анализа производительности, валидации ARC, оптимизации кода, проверки правильности lifetime объектов.
SIL — ключевая часть того, почему Swift работает быстро и надёжно, несмотря на высокоуровневую природу языка.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
🤔 Зачем нужны слабые ссылки?

Слабые ссылки (weak references) играют важную роль в управлении памятью, особенно когда нужно предотвратить циклы сильных ссылок (retain cycles) и утечки памяти.

🚩Основные причины использования

🟠Предотвращение циклов сильных ссылок
Циклы сильных ссылок возникают, когда два объекта держат сильные ссылки друг на друга, что препятствует их освобождению из памяти. Слабые ссылки разрывают этот цикл, позволяя одному из объектов освобождаться.
class Person {
var name: String
weak var friend: Person?
init(name: String) {
self.name = name
}
}

var alice: Person? = Person(name: "Alice")
var bob: Person? = Person(name: "Bob")

alice?.friend = bob
bob?.friend = alice

alice = nil // Теперь объекты могут быть освобождены
bob = nil


🟠Избежание утечек памяти
Утечки памяти происходят, когда объекты, которые больше не нужны, не освобождаются из памяти. Слабые ссылки помогают избежать этих утечек, обеспечивая правильное освобождение памяти. Делегаты часто объявляются как слабые ссылки, чтобы избежать утечек памяти.
protocol TaskDelegate: AnyObject {
func taskDidFinish()
}

class Task {
weak var delegate: TaskDelegate?
func complete() {
delegate?.taskDidFinish()
}
}

class ViewController: TaskDelegate {
var task = Task()
init() {
task.delegate = self
}

func taskDidFinish() {
print("Task finished")
}
}


🟠Управление временными зависимостями
Слабые ссылки удобны для временных зависимостей, когда объект не должен удерживаться в памяти, если нет других сильных ссылок. Использование слабых ссылок для временных объектов.
class Cache {
weak var temporaryObject: SomeClass?
}

class SomeClass {
// Код класса
}

var cache = Cache()
var object = SomeClass()
cache.temporaryObject = object
object = SomeClass() // Старый объект удаляется, так как на него нет сильных ссылок


🚩Плюсы и минусы

Предотвращение циклов ссылок
Основное преимущество слабых ссылок заключается в их способности разрывать циклы ссылок, предотвращая утечки памяти.
Обеспечение корректного управления памятью
Слабые ссылки позволяют объектам освобождаться из памяти, когда на них больше нет сильных ссылок, что улучшает управление ресурсами.
Гибкость и безопасность
Использование слабых ссылок обеспечивает более гибкое и безопасное управление зависимостями между объектами.
Сложности управления
Необходимо учитывать, что слабые ссылки могут стать nil в любой момент, поэтому требуется дополнительная проверка.
Понимание жизненного цикла объектов
Требуется хорошее понимание жизненного цикла объектов и управления памятью, чтобы правильно использовать слабые ссылки.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM