Swift | Вопросы собесов
2.13K subscribers
28 photos
951 links
Download Telegram
🤔 Что такое шардирование?

Это процесс разделения больших наборов данных на более мелкие части (шарды) для распределённого хранения и обработки.
1. Каждый шард хранит часть данных, и они могут находиться на разных серверах.
2. Это повышает масштабируемость и производительность базы данных.
3. Сложность шардирования заключается в управлении распределением данных и маршрутизации запросов к нужным шардам.


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

Это возможность центрального процессора (ЦПУ) или программы выполнять несколько задач (потоков) одновременно. В контексте программирования она используется для улучшения производительности приложений за счёт параллельной обработки задач. Это особенно важно для задач, требующих интенсивных вычислений, или приложений, которые должны одновременно реагировать на множество входных данных, таких как пользовательский интерфейс.

🚩Основные понятия

🟠Поток (Thread)
Минимальная единица обработки, которая может быть выполнена операционной системой.

🟠Конкуренция (Concurrency)
Способность программы делать прогресс в нескольких задачах одновременно. Конкуренция достигается за счёт переключения между задачами.

🟠Параллелизм (Parallelism)
Способность программы выполнять несколько операций одновременно, используя множество процессоров или ядер.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 В чем разница между garbage collector и ARC?

1. Garbage Collector (GC): автоматически освобождает память, анализируя объекты, на которые больше нет ссылок. Он работает в фоне, но может вызывать задержки из-за пауз при сборке мусора.
2. ARC (Automatic Reference Counting): подсчитывает ссылки на объекты и освобождает их, когда счётчик ссылок достигает нуля. Это менее ресурсоёмко, так как выполняется в момент изменения ссылок, но требует осторожности для предотвращения циклических ссылок.


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

Важный аспект разработки iOS-приложений, особенно для поддержания плавности пользовательского интерфейса. Основной принцип заключается в том, что все операции, связанные с обновлением пользовательского интерфейса, должны выполняться на основном потоке (main thread). Другие задачи, такие как сетевые запросы, обработка данных или любые длительные операции, могут выполняться на фоновых потоках.

🚩Основной поток

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

🚩Фоновые потоки

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

🚩Основные механизмы многопоточности

🟠Grand Central Dispatch (GCD)
Это низкоуровневый API для управления многозадачностью. Он позволяет легко выполнять задачи асинхронно на различных очередях (queues).
Main Queue: Очередь, связанная с основным потоком.
Global Queues: Глобальные очереди для выполнения фоновых задач.
   // Выполнение задачи в фоновом потоке
DispatchQueue.global(qos: .background).async {
// Долгая задача
let result = someLongRunningTask()

// Возвращение к основному потоку для обновления UI
DispatchQueue.main.async {
self.updateUI(with: result)
}
}


🟠Operation Queues
Это более высокоуровневый API для управления очередями операций (Operation). Он предоставляет больше возможностей для управления зависимостями между задачами.
   let operationQueue = OperationQueue()

let operation = BlockOperation {
// Долгая задача
let result = someLongRunningTask()

// Возвращение к основному потоку для обновления UI
OperationQueue.main.addOperation {
self.updateUI(with: result)
}
}

operationQueue.addOperation(operation)


🚩Примеры многопоточности

Асинхронная загрузка изображения
   func loadImageAsync(url: URL) {
DispatchQueue.global(qos: .userInitiated).async {
if let data = try? Data(contentsOf: url),
let image = UIImage(data: data) {
DispatchQueue.main.async {
self.imageView.image = image
}
}
}
}


Обработка данных в фоновом режиме
   func processDataInBackground(data: Data) {
DispatchQueue.global(qos: .utility).async {
let processedData = self.process(data)

DispatchQueue.main.async {
self.updateUI(with: processedData)
}
}
}


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

MemoryLayout предоставляет информацию о размерах, выравнивании и смещении данных в памяти.
1. Размер можно посчитать с помощью MemoryLayout<MyType>.size.
2. Для протоколов это работает только при конкретизации типа, так как сами протоколы не имеют фиксированного размера.


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

Когда клавиатура появляется и скрывает важную часть интерфейса в iOS-приложении, нужно сдвигать или изменять размер представлений, чтобы они оставались видимыми.

🚩Подписка на уведомления о клавиатуре

Подпишитесь на уведомления о появлении и скрытии клавиатуры, чтобы корректировать интерфейс:
import UIKit

class ViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
var activeTextField: UIView?

override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}

@objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardSize.height, right: 0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets

var aRect = self.view.frame
aRect.size.height -= keyboardSize.height
if let activeField = self.activeTextField, !aRect.contains(activeField.frame.origin) {
scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}

@objc func keyboardWillHide(notification: NSNotification) {
let contentInsets = UIEdgeInsets.zero
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
}

deinit {
NotificationCenter.default.removeObserver(self)
}
}

extension ViewController: UITextFieldDelegate, UITextViewDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
self.activeTextField = textField
}

func textViewDidBeginEditing(_ textView: UITextView) {
self.activeTextField = textView
}

func textFieldDidEndEditing(_ textField: UITextField) {
self.activeTextField = nil
}

func textViewDidEndEditing(_ textView: UITextView) {
self.activeTextField = nil
}
}


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

Это механизм управления памятью, который подсчитывает количество ссылок на объекты.
1. Когда счётчик ссылок объекта достигает нуля, память, занимаемая объектом, автоматически освобождается.
2. Этот механизм используется в Swift и Objective-C.


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

В старых версиях iOS (до iOS 6), IBOutlet переменные обычно освобождались (релизились) в методе viewDidUnload, чтобы освободить память. Это было связано с управлением памятью и ресурсами на устройствах с ограниченными ресурсами.

🚩Обоснование

🟠Управление памятью
Устройства с ограниченными ресурсами могли столкнуться с нехваткой памяти. Освобождение ресурсов, связанных с представлениями, помогало улучшить производительность приложения и предотвращало его крах.

🟠Метод `viewDidUnload`
viewDidUnload вызывался, когда система освобождала память, удаляя невидимые представления. В этом методе освобождались ресурсы, которые можно было легко пересоздать при следующем отображении представления.

🚩Пример кода

До iOS 6, код мог выглядеть так
class ViewController: UIViewController {
@IBOutlet weak var myLabel: UILabel?

override func viewDidUnload() {
super.viewDidUnload()
// Освобождение IBOutlet
myLabel = nil
}
}


🚩Современные подходы

С введением автоматического управления памятью (ARC) и изменений в iOS 6, метод viewDidUnload был устаревшим и удалённым. Теперь система управления памятью в iOS гораздо эффективнее, и необходимость вручную освобождать IBOutlet-ы больше не актуальна. Вместо этого представления и связанные с ними ресурсы освобождаются автоматически, когда они больше не нужны.

🚩Современный код

Сейчас мы просто используем ARC и не беспокоимся об освобождении IBOutlet-ов
class ViewController: UIViewController {
@IBOutlet weak var myLabel: UILabel!

override func viewDidLoad() {
super.viewDidLoad()
// Настройка myLabel
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🤔 Зачем нужны свойства "Content Hugging Priority"?

Content Hugging Priority управляет тем, насколько UI-элемент сопротивляется увеличению своего размера.
1. Более высокий приоритет заставляет элемент занимать минимальное пространство.
2. Это полезно при настройке адаптивных интерфейсов и разрешении конфликтов автолейаута.


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

Это метод, который вызывается после того, как объект был загружен из файла XIB или NIB. Этот метод используется для выполнения дополнительной настройки после загрузки объекта.

🚩Когда используется

Этот метод вызывается, когда объект создается из XIB или NIB файла. Это место для выполнения дополнительных операций по настройке, которые не могут быть выполнены в Interface Builder.

class CustomView: UIView {
@IBOutlet weak var label: UILabel!

override func awakeFromNib() {
super.awakeFromNib()
// Дополнительная настройка
label.text = "Hello, World!"
}
}


🚩Разница между XIB и NIB файлами

🟠XIB (XML Interface Builder)
XIB файлы — это XML файлы, которые содержат описание интерфейса. XIB файлы редактируются в Interface Builder, который предоставляет визуальный способ создания и настройки интерфейсов. XIB файлы компилируются в NIB файлы во время сборки приложения.

🟠NIB (NeXT Interface Builder)
NIB файлы — это двоичные файлы, которые содержат скомпилированное описание интерфейса. NIB файлы были использованы в ранних версиях macOS и iOS, а также в NeXTSTEP, на основе которого была построена macOS. Приложение загружает NIB файлы в память во время выполнения для создания и настройки интерфейсов.

🚩Основные отличия

🟠Формат
XIB: XML-файл, который читается и редактируется в текстовом формате. NIB: Скомпилированный двоичный файл, который загружается в память во время выполнения.

🟠Редактирование
XIB: Редактируется в Interface Builder. NIB: Содержит скомпилированные данные и не редактируется напрямую.

🟠Использование
XIB: Используется для разработки и редактирования интерфейсов. NIB: Загружается и используется приложением во время выполнения.

class CustomView: UIView {
@IBOutlet weak var label: UILabel!

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}

override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}

private func commonInit() {
Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)
addSubview(label)
label.frame = self.bounds
}

override func awakeFromNib() {
super.awakeFromNib()
// Дополнительная настройка
label.text = "Hello, World!"
}
}


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

1. viewDidLoad: вызывается после загрузки представления в память.
2. viewWillAppear: вызывается перед отображением на экране.
3. viewDidAppear: вызывается сразу после отображения.
4. viewWillDisappear и viewDidDisappear: перед и после удаления представления с экрана.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3👾1
🤔 Иерархия наследования UIButton.?

🟠NSObject
Это корневой класс для большинства классов в iOS. Он предоставляет базовую функциональность, такую как управление памятью (retain, release), метод доступа к информации о классе (class), и многое другое.

🟠UIResponder
Этот класс отвечает за обработку событий, таких как касания экрана. UIResponder является базовым классом для всех объектов, которые могут реагировать на события.

🟠UIView
UIView является базовым классом для всех визуальных элементов интерфейса. Он предоставляет функциональность для управления рамками, трансформациями, слоями (CALayer), анимациями и жестами.

🟠UIControl
UIControl наследуется от UIView и добавляет поддержку механизма целевых действий (target-action) и управление состояниями (например, включено/выключено). Он является базовым классом для всех элементов управления, таких как кнопки, переключатели и слайдеры.

🟠UIButton
Наконец, UIButton наследуется от UIControl и представляет собой конкретную реализацию элемента управления, который реагирует на касания и может выполнять действия при нажатии. Он предоставляет дополнительные свойства и методы для настройки заголовка, изображения и фона кнопки.
import UIKit

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

// Создаем кнопку
let button = UIButton(type: .system)
button.frame = CGRect(x: 100, y: 100, width: 200, height: 50)
button.setTitle("Нажми меня", for: .normal)

// Добавляем целевое действие для кнопки
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)

// Добавляем кнопку на экран
self.view.addSubview(button)
}

@objc func buttonTapped() {
print("Кнопка была нажата!")
}
}


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

Используйте Set, когда важна уникальность элементов и требуется быстрая проверка их наличия.
1. Set быстрее для поиска и вставки, но порядок элементов не сохраняется.
2. Для упорядоченных данных предпочтителен Array.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🤔 Как работают push нотификации?

Это сообщения, которые отправляются приложением пользователю даже тогда, когда само приложение не активно. Это эффективный способ удержания пользователей и предоставления им важной информации.

🚩Как работают push-уведомления

1⃣Регистрация устройства
Когда приложение устанавливается и запускается на устройстве, оно регистрируется для получения push-уведомлений. Для этого приложение отправляет запрос к Apple Push Notification Service (APNs) с запросом на получение уникального токена устройства (device token).

2⃣Получение токена устройства
APNs генерирует уникальный токен для устройства и отправляет его обратно приложению. Приложение затем передает этот токен на свой сервер.

3⃣Отправка уведомления на сервер
Когда необходимо отправить push-уведомление, сервер приложения формирует сообщение, включающее содержимое уведомления и токен устройства, и отправляет его на APNs.

4⃣Доставка уведомления
APNs принимает сообщение от сервера, определяет устройство по токену и отправляет уведомление на это устройство.

5⃣Получение уведомления на устройстве
Когда устройство получает уведомление, операционная система отображает его пользователю. Если пользователь взаимодействует с уведомлением, приложение может выполнить определенные действия, такие как открытие конкретного экрана.

🚩Пример кода для регистрации устройства

В AppDelegate
import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Запрос разрешения на отправку уведомлений
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
return true
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// Преобразуем токен в строку
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
let token = tokenParts.joined()
print("Device Token: \(token)")

// Отправляем токен на сервер
// serverAPI.registerDeviceToken(token)
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register: \(error)")
}
}


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

Это улучшение производительности приложения за счёт уменьшения времени выполнения, потребления памяти или других ресурсов.
1. Основа — выявление узких мест с помощью профайлеров.
2. Важно оптимизировать только критические участки, чтобы не усложнять код.


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

Это функции, которые вы можете применять к вьюшкам (представлениям), чтобы изменить их внешний вид или поведение. Они возвращают новое представление с измененными свойствами, позволяя вам легко настраивать элементы интерфейса. Модификаторы являются неотъемлемой частью декларативного подхода SwiftUI, где вы описываете, что должно быть сделано, а не как.

🚩Основные модификаторы

🟠Фон (background)
Изменяет фон представления.
Text("Привет, мир!")
.background(Color.yellow)


🟠Границы (border)
Добавляет границу вокруг представления.
Text("Привет, мир!")
.border(Color.red, width: 2)


🟠Отступы (padding)
Добавляет отступы вокруг содержимого представления.
Text("Привет, мир!")
.padding()


🟠Тень (shadow)
Добавляет тень к представлению.
Text("Привет, мир!")
.shadow(color: .black, radius: 2, x: 0, y: 2)


🟠Угловой радиус (cornerRadius)
Закругляет углы представления.
Text("Привет, мир!")
.background(Color.blue)
.cornerRadius(10)


🟠Размеры (frame)
Устанавливает размеры представления.
Text("Привет, мир!")
.frame(width: 200, height: 50)


Пример использования нескольких модификаторов
import SwiftUI

struct ContentView: View {
var body: some View {
Text("Привет, мир!")
.padding() // Добавляем отступы
.background(Color.yellow) // Фон желтого цвета
.cornerRadius(10) // Закругляем углы
.shadow(color: .gray, radius: 5, x: 0, y: 5) // Добавляем тень
}
}


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

1. Использовать кэширование изображений с помощью библиотек вроде SDWebImage.
2. Загружать изображения асинхронно, избегая блокировки основного потока.
3. Применять сжатие или адаптивные форматы изображений.


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

Применение модификатора к вью в SwiftUI изменяет его внешний вид или поведение. Каждый модификатор возвращает новое представление с измененными свойствами, не изменяя исходное представление. Это позволяет создавать сложные пользовательские интерфейсы, применяя цепочки модификаторов.

🚩Что происходит при применении модификатора

🟠Изменение внешнего вида
Модификаторы могут изменять визуальные характеристики вью, такие как цвет, шрифт, отступы и границы.
Text("Привет, мир!")
.padding() // Добавляет отступы вокруг текста
.background(Color.yellow) // Устанавливает желтый фон
.cornerRadius(10) // Закругляет углы текста


🟠Изменение поведения
Модификаторы могут изменять поведение вью, такие как обработка событий и взаимодействие.
Button(action: {
print("Кнопка нажата")
}) {
Text("Нажми меня")
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)


🟠Компоновка
Модификаторы могут изменять размеры и позиционирование вью.
Text("Привет, мир!")
.frame(width: 200, height: 50) // Устанавливает размеры вью
.padding()
.background(Color.green)


Пример с пояснением
import SwiftUI

struct ContentView: View {
var body: some View {
Text("Привет, мир!")
.padding() // Добавляет отступы вокруг текста
.background(Color.yellow) // Устанавливает желтый фон
.cornerRadius(10) // Закругляет углы текста
.shadow(color: .gray, radius: 5, x: 0, y: 5) // Добавляет тень
}
}


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

1. Semaphore: синхронизирует доступ к ресурсу, позволяя определённое количество потоков одновременно. Например, для 3 потоков используется семафор с разрешениями 3.
2. Mutex: обеспечивает эксклюзивный доступ к ресурсу для одного потока.
3. Основное отличие в уровне параллелизма: mutex работает с одним потоком, а semaphore с несколькими.


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

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

🚩Создание ViewModifier

Чтобы создать собственный модификатор, нужно реализовать протокол ViewModifier, который требует определения метода body(content:), возвращающего измененное представление.

🚩Пример создания ViewModifier

Допустим, мы хотим создать модификатор, который добавляет отступы, фон, закругленные углы и тень к вью.
import SwiftUI

// Создаем кастомный модификатор
struct CustomModifier: ViewModifier {
func body(content: Content) -> some View {
content
.padding() // Добавляем отступы
.background(Color.yellow) // Устанавливаем фон
.cornerRadius(10) // Закругляем углы
.shadow(color: .gray, radius: 5, x: 0, y: 5) // Добавляем тень
}
}

extension View {
func customStyle() -> some View {
self.modifier(CustomModifier())
}
}

struct ContentView: View {
var body: some View {
Text("Привет, мир!")
.customStyle() // Применяем наш кастомный модификатор
}
}


🚩Объяснение

1⃣Создание структуры, соответствующей протоколу ViewModifier
   struct CustomModifier: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(Color.yellow)
.cornerRadius(10)
.shadow(color: .gray, radius: 5, x: 0, y: 5)
}
}


2⃣Расширение View для удобного использования модификатора
   extension View {
func customStyle() -> some View {
self.modifier(CustomModifier())
}
}


3⃣Применение кастомного модификатора в представлении
   struct ContentView: View {
var body: some View {
Text("Привет, мир!")
.customStyle()
}
}


🚩Плюсы

Повторное использование кода
Модификаторы позволяют инкапсулировать часто используемые изменения и применять их в разных частях приложения.
Чистота и читаемость кода
Применение модификаторов упрощает и сокращает код, делая его более читаемым.
Модульность
Выделение стилей и поведения в отдельные модификаторы улучшает структуру кода и упрощает его поддержку.

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