AppSome – iOS – Swift https://appsome.net We are AppSome! Sat, 28 Mar 2020 21:21:23 +0000 tr hourly 1 https://wordpress.org/?v=5.3.21 SOLID https://appsome.net/solid/ https://appsome.net/solid/#respond Sat, 28 Mar 2020 19:34:13 +0000 https://appsome.net/?p=138 Yazılım ekosistemindeki birçok yazılımcı veya programcı kendisinden talep edileni karşılayan ve çalışabilen bir yazılım ürünü ortaya çıkarabilir. Ancak şöyle bir durum var ki yaptığı işin yani yazdığı kodun her zaman kaliteli olduğu anlamına gelmez. Yazılımda kalite çoğunlukla kişiden kişiye göre değişmektedir. Bazılarına göre çalışıyorsa iyidir düşüncesi varken bazılarında ise görsel olarak ne kadar şık olduğu

READ MORE

The post SOLID appeared first on AppSome - iOS - Swift.

]]>
Yazılım ekosistemindeki birçok yazılımcı veya programcı kendisinden talep edileni karşılayan ve çalışabilen bir yazılım ürünü ortaya çıkarabilir. Ancak şöyle bir durum var ki yaptığı işin yani yazdığı kodun her zaman kaliteli olduğu anlamına gelmez. Yazılımda kalite çoğunlukla kişiden kişiye göre değişmektedir. Bazılarına göre çalışıyorsa iyidir düşüncesi varken bazılarında ise görsel olarak ne kadar şık olduğu düşüncesi ağır basmaktadır. İşin aslı tam olarak böyle değil. Yazılımda kalite esneklik, güvenlik, modülerlik gibi birçok konuda incelenebilir. Bir yazılımın bu tarz özelliklere sahip olması için belirli kurallar bütününe göre yazılması gerekir. Bu kurallara sahip olmayan yazılım ürünleri 4 maddeye ayırabileceğimiz sorunlarla karşı karşıya kalacaklardır. Nedir bu sorunlar ?

  1. Rijidite(Esnemezlik): Yazılımda inşa ettiğimiz sistemin/tasarımın değişime karşı koyma isteğidir. İyi bir tasarımda bu isteğin çok az olması beklenir.
  2. Fragility(Kırılganlık): Bir noktada yaptığımız değişiklik sistemin birçok noktasında başımızı ağrıtıyorsa, bu sistem kırılgan bir sistemdir.
  3. Immobilite(Sabitlik): Yazılım sistemleri tak-çalıştır şeklinde tasarlanmalıdır. Yani bir projede yazdığımız bir kodu başka bir projeye de eklediğimizde çalışmalıdır.
  4. Cost(Maliyet): Geliştirme sürecinin hem zaman olarak hem de maddi olarak maliyetinin artmasıdır.

Özetle bu 4 özelliğe sahip bir yazılım tasarımı kötü tasarlanmış bir sistemdir. İşte bu noktada nesneye yönelik programlama prensipleri olan SOLID prensipleri karşımıza çıkıyor. SOLID prensiplerini dikkate alarak yazılan sistemlerde yukarıda bahsettiğim sorunların oluşma ihtimali azalacaktır. O halde çok uzatmadan sırasıyla açıklamaya geçelim.

SOLID Prensipleri

  1. Single Responsibility Principle – Tek Sorumluluk Prensibi
  2. Open/Closed Principle – Açık/Kapalı Prensibi
  3. Liskov Substitution Principle – Liskov Yerine Geçme Prensibi
  4. Interface Segregation Principle – Arayüz Ayırma Prensibi
  5. Dependency Inversion Principle – Bağımlılıkları Tersine Çevirme Prensibi

Single Responsibility Principle – Tek Sorumluluk Prensibi

  • Bir modül/sınıf/method sadece tek bir sorumluluğu/görevi yerine getirmek üzere geliştirilmedir.
  • İlgili modülü/sınıfı/methodu değiştirmek için tek bir nedenimiz olmalıdır.

Örnek:

class Membership {
    func delete() { }
    func login() { }
    func register() { }
    func sendMail() { }
    func sendSMS() { }
    func update() { }
}

Membership sınıfı çok net bir şekilde olması gerekenden çok fazla bir şeyler yapıyor. Bu durumda tek sorumluluk prensibine aykırı bir davranış sergilediği anlamına geliyor. Bu sınıfı tek sorumluluk prensibine uygun hale getirmek için sınıfın üzerindeki gereksiz ve yapmaması gereken işleri parçalayıp ilgili sınıflara böleceğiz. Aşağıdaki tasarım bu prensibe çok daha uygun bir tasarımdır.

class Membership {
    func delete() { }
    func register() { }
    func update() { }
}

class LoginService {
    func login() { }
    func logout() { }
}

class SMSService {
    func send() { }
}

class MailService {
    func send() { }
}

Open/Closed Principle – Açık/Kapalı Prensibi

  • Bir modül/sınıf/method gelişime açık, değişime kapalı olmalıdır.
  • Değişim sadece yeni kodlar ekleyerek yapılmalıdır.

Örnek:

class Draw {
    func circle() { }
    func rectangle() { }
}

class Shape {

}

class Circle: Shape {

}

class Rectangle: Shape {
    
}

Elimizde ekrana daire ve dikdörtgen çizen bir Draw sınıfımız ve bunlarla ilgili Circle ve Rectangle sınıflarımız var. Buradaki tasarım açık/kapalı prensibine aykırı bir tasarımdır. Nedeni ise belirli bir zaman sonra üçgen ve kare sınıflarını eklediğimizde Draw sınıfına yeni fonksiyonlar eklemek zorunda kalacağız.

Doğru bir tasarımda ise Shape adında bir protocol tanımlanıp draw() adında bir fonksiyona sahip olması gerekiyor. Shape protocol’ünden türeyen her bir sınıf kendi draw() fonksiyonunu implemente edebilmelidir.

protocol Shape {
    func draw()
}

class Circle: Shape {
    func draw() {
        print("Circle is drawed")
    }
}

class Rectangle: Shape {
    func draw() {
        print("Rectangle is drawed")
    }
}

class Draw {
    func drawShape(shape: Shape) {
        shape.draw()
    }
}

let draw = Draw()
let circle = Circle()
let rectangle = Rectangle()

draw.drawShape(shape: circle)
draw.drawShape(shape: rectangle)

Liskov Substitution Principle – Liskov Yerine Geçme Prensibi

  • Türetilmiş sınıf nesnelerinin türetilen sınıf yerine geçmesidir.
  • Ana sınıf üzerinde olan özellikler alt sınıflarca da kullanılabilmelidir.

Örnek:

protocol DBService {
    func connect()
    func query()
}

class SQLService: DBService {
    func connect() {
        print("SQL")
    }

    func query() {
        print("SELECT * FROM CUSTOMER")
    }
}

class OracleService: DBService {
    func connect() {
        print("Oracle")
    }

    func query() {
        print("SELECT * FROM PRODUCT")
    }
}

class Connection {
    func connect(with service: DBService) {
        service.connect()
    }
}

let sqlService = SQLService()
let oracleService = OracleService()

let connection = Connection()
connection.connect(with: sqlService)
connection.connect(with: oracleService)

Yukarıdaki örnekte veritabanı işlemleri için bir modül yazılmıştır. Bu tasarımda, Connection sınıfındaki connect(with service: DBService) fonksiyonu DBService türünde aldığı parametereye bu sınıftan türeyen parametre verilmesiyle Liskov prensibini sağlamaktadır.

Interface Segregation Principle – Arayüz Ayırma Prensibi

  • Kısaca protocolleri(arayüzleri) ayırmaktır.
  • Ortak bir protocolü conform(implemente eden) eden sınıflarda, bir sınıf protocoldeki bir özelliği implemente etmek zorunda olmayabilir. Bu durumda protocolleri(arayüzleri) ayırmak gerekmektedir.

Örnek:

protocol LivingSpecifications {
    func breath()
    func eat()
    func run()
    func fly()
}

class Birds: LivingSpecifications{
    func breath() { print("I can") }
    func eat() { print("I can") }
    func run() { print("I can") }
    func fly() { print("I can") }
}

class Human: LivingSpecifications {
    func breath() { print("I can") }
    func eat() { print("I can") }
    func run() { print("I can") }
    func fly() { print("I can't") }
}

class Flower: LivingSpecifications {
    func breath() { print("I can") }
    func eat() { print("I can") }
    func run() { print("I can't") }
    func fly() { print("I can't") }
}

Yukarıdaki örnekte tüm canlılar için LivingSpecifications adlı bir protocol yazıldı. Bu durumda Birds sınıfında tüm özellikler implemente edilmesi gerekiyorken Human sınıfında tüm özelliklerine implemente edilmesi şart değil. Şu anki tasarımda arayüz ayırma prensibini ihlal etmiş bulunmaktayız. Bu sorunu protocollere ayırarak gidereceğiz.

protocol LivingSpecifications {
    func breath()
    func eat()
}

protocol FlightfulSpecifications {
    func fly()
}

protocol SprintingSpecifications {
    func run()
}


class Birds: LivingSpecifications, FlightfulSpecifications, SprintingSpecifications {
    func breath() { print("I can") }
    func eat() { print("I can") }
    func run() { print("I can") }
    func fly() { print("I can") }
}

class Human: LivingSpecifications, SprintingSpecifications {
    func breath() { print("I can") }
    func eat() { print("I can") }
    func run() { print("I can") }
}

class Flower: LivingSpecifications {
    func breath() { print("I can") }
    func eat() { print("I can") }
}

Dependency Inversion Principle – Bağımlılıkları Tersine Çevirme Prensibi

  • Üst/yüksek seviyeli sınıfların alt seviyeli sınıflara doğrudan bağımlılığı olmamalıdır.
  • Çözüm: Doğrudan bağımlılık yerine araya bir protocol yazılmalıdır.

Örnek:

class FileLogger {
    func log() { }
}

class DBLogger {
    func log() { }
}

class LogManager {

    var fileLogger: FileLogger!
    var dbLogger: DBLogger!

    public init(fileLogger: FileLogger, dbLogger: DBLogger) {
        self.fileLogger = fileLogger
        self.dbLogger = dbLogger
    }

    func log(){
        fileLogger.log()
        dbLogger.log()
    }
}

Yukarıdaki örnekte üst seviye sınıfımız LogManager sınıfıdır, alt seviye sınıflar ise DBLogger ve FileLogger sınıflarıdır. Bu durumda çok net bir şekilde üst seviye sınıfımız LogManager, alt seviye sınıflarımıza DBLogger ve FileLogger sınıflarımıza doğrudan bağımlıdır. Çözüm ise üst ve alt seviye sınıf ilişkilerini protocol ile yönetmektir.

protocol Logger {
    func log()
}

class FileLogger: Logger {
    func log() { }
}

class DBLogger: Logger {
    func log() { }
}

class LogManager {

    var logger: Logger!

    init(logger: Logger) {
        self.logger = logger
    }

    func log() {
        logger.log()
    }
}

let dbLogger = DBLogger()
let fileLogger = FileLogger()

let logManager = LogManager(logger: dbLogger)
logManager.log()

Yararlanılan Kaynaklar

  • TAŞDELEN Aykut, UML ve Dizayn Paternleri, Pusula Yayıncılık, 2015

The post SOLID appeared first on AppSome - iOS - Swift.

]]>
https://appsome.net/solid/feed/ 0
VIPER’a Giriş https://appsome.net/vipergiris/ https://appsome.net/vipergiris/#comments Tue, 24 Mar 2020 17:54:05 +0000 http://appsome.net/?p=68 Merhaba, hazırlamış olduğum bu yazıda VIPER mimarisinin ne olduğu, neden tercih edilmesi gerektiği, avantajları ve dezavantajları hakkında bilgilere yer verdim. VIPER Nedir? VIPER, uygulamamızı bir modül içerisinde, farklı özelliklere sahip beş katmana ayıran bir mimaridir. Her bir katmanın ayrı bir rolü bulunmaktadır. Bunları aşağıdaki gibi özetleyebiliriz: View: View, çoğu zaman dokunma olaylarını alan dummy bir

READ MORE

The post VIPER’a Giriş appeared first on AppSome - iOS - Swift.

]]>
Merhaba, hazırlamış olduğum bu yazıda VIPER mimarisinin ne olduğu, neden tercih edilmesi gerektiği, avantajları ve dezavantajları hakkında bilgilere yer verdim.

VIPER Nedir?

VIPER, uygulamamızı bir modül içerisinde, farklı özelliklere sahip beş katmana ayıran bir mimaridir. Her bir katmanın ayrı bir rolü bulunmaktadır. Bunları aşağıdaki gibi özetleyebiliriz:

View: View, çoğu zaman dokunma olaylarını alan dummy bir nesnedir. MVC’nin binlerce kod satırı içeren devasa ViewController’ı yerine, yapılacak işlemler ile ilgili tüm kodlar ve karar verme işlemleri View’de bulunmamalıdır. Örneğin, kullanıcıdan bir dokunma işlemi alındığında, View nesnesi Presenter’a bu durumu bildirmelidir.

Interactor: Interactor business logic içerir. Ve çoğunlukla API çağrılarından sorumludur. Bu katmanda yapılan işlemler UI dan bağımsız olarak gerçekleşir.

Presenter: Presenter, bu mimarinin merkezindeki katman diyebiliriz. Diğer katmanlarla haberleşmeyi sağlar. Bütün kararlar presenter katmanında ele alınır. View’dan gelen bildirimleri alır ve onlarla ne yapılacağına karar verir. Örneğin, Interactor’dan bazı veriler isteyebiliriz veya Router’a farklı bir ekrana yönlenmesini söyleyebiliriz. Presenter ayrıca, Interactor’dan veriler alır ve komutlarla View’a iletir.

Entity: Entity, Interactor tarafından yönlendirilen veri objeleridir. 

Router: Router’ın işi ekranlar arasında yönlenmeyi sağlamaktır. Yalnızca Presenter’ı tanır ve ondan komutlar alır.

VIPER Mimarisi

İşleyiş

  • View, Presenter ile haberleşir. Kullanıcının gerçekleştirdiği aksiyonu Presenter’a iletir. Presenter’dan gelen cevaba göre işlemi gerçekleştirir.
  • Interactor, Presenter ile haberleşir. Servis ile haberleşme de Interactor’da yapılır.
  • Router, presenter ile haberleşir. Ve ekran yönlenmeleri sadece Router’da yapılır.
  • Katmanlar arası Protocol’ler ile iletişim kurulması sağlanır.

Projemiz büyüdüğünde binlerce satıra sahip ViewContoller olması durumunda, global değişkenlerin gittikçe artması ve uygulamayı debug etmenin gittikçe zorlaşması durumunda VIPER tasarım desenini tercih edebiliriz.

Avantajlar

  • Her bir nesnenin tek bir rolü olması
  • Veri akışının kolay takip edilebilmesi
  • Büyük projelerde hata ayıklamanın kolay olması ve test kolaylığı

Dezavantajlar

  • Küçük projelerde karmaşık olması
  • Yeni bir geliştiricinin VIPER’ ın temellerini öğrenmesi için normalden fazla zaman harcaması gerekmesi

Örnek Uygulamaya Giriş

Uygulamamız ekranda ‘Hello VIPER’ metni göstermek üzerine kurgulandı. Öncelikle projemizi oluşturup katmanlarımızı hazır hale getirmeliyiz. Aşağıdaki gibi bir klasörleme  yapısı oluşturabiliriz.

Klasörleme Yapısı

Presenter’ı mimarinin merkezinde düşünebiliriz. Böylece yapı kafamızda daha rahat canlanabilir. Entity hariç her bir katmanın Protocol’ü bulunur. Bu protocol’ler ile katmanlar arası iletişim kurulur.

Protocol’ler oluşturulduktan sonra hangi katmanla iletişim kurulması gerektiği göz önünde bulundurularak her katman ile ilgili değişkenler tanımlanır.

Ardından, Modül oluşturma methodu yazmamız gerekmektedir. Genel kullanım olarak; farklı modüller Router ile iletişim kurduğu için bu methodu Router içerisine yazmamız daha uygun olacaktır.

HomeRouter.swift

“createModule” fonksiyonu yukarıda bahsedildiği gibi Router içerisinde oluşturuldu.

import UIKit

protocol HomeRouterInterface {
    
}

class HomeRouter {
    func createModule() -> UIViewController {
        let view = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
        let interactor = HomeInteractor()
        let router = HomeRouter()
        let presenter = HomePresenter(view: view, interactor: interactor, router: router)
        view.presenter = presenter
        return view
    }
}

extension HomeRouter: HomeRouterInterface {
    
}

HomeRouter.swift

ViewController’da Label oluşturuldu, updateTitle fonksiyonu yazıldı. Burada amaç Interactor içerisinde oluşturulan getTitle methodunun nasıl çalıştığını görüp ardından updateTitle methodundan gelen değerin ekrana nasıl basıldığını görmek.

HomeViewController.swift  

import UIKit

protocol HomeViewControllerInterface: class {
    func updateTitle()
}

class HomeViewController: UIViewController {
    
    @IBOutlet var helloLabel: UILabel!
    var presenter: HomePresenterInterface!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.presenter.viewDidLoad()
        
    }
}

extension HomeViewController: HomeViewControllerInterface {
    func updateTitle() {
        helloLabel.text = "Hello VIPER"
    }
}

HomeViewController.swift

HomePresenter.swift

import UIKit

protocol HomePresenterInterface {
    func viewDidLoad()
}

class HomePresenter {
    weak var view: HomeViewControllerInterface?
    var interactor: HomeInteractorInterface
    var router: HomeRouterInterface
    
    init(view: HomeViewControllerInterface, interactor: HomeInteractorInterface, router: HomeRouterInterface) {
        self.view = view
        self.interactor = interactor
        self.router = router
    }
}

extension HomePresenter: HomePresenterInterface {
    func viewDidLoad() {
        let homeModel = self.interactor.getTitle()
        print("Home Model value is \(homeModel)")
        view?.updateTitle()
    }
}

HomePresenter.swift

View, Presenter ile haberleştiği için View içerisinde presenter değişkeni oluşturuldu. 

Presenter içerisinde View’da bulunan viewDidLoad fonksiyonu eklendi. Ayrıca view, router ve interactor değişlenleri tanımlandı.

– Ekran açıldığında viewDidLoad çalışacak presenter’a gidilecek. 

– Presenter’da kullanıcıya gösterilecek title için getTitle ve updateTitle methoduna gidilecek. getTitle methodu Interactor’da tanımlanmıştır. Title’ın ne olacağı ilk olarak Interactor’da belirlenmiştir.

– Presenter Interactor’da oluşturulan title değerini alacak ardından bunu çağıracak.

– Title ın nasıl güncellendiğini görebilmemiz için de Presenter updateTitle methodunu çağıracak. Ve ekranda ne yazacağını View’a haber verecek. Router da hangi ekrana yönlenileceğini belirleyecek.

– Son olarak Presenter tüm bu bilgileri View’a haber verecek ve kullanıcı ekranda verilen mesajı görebilecek.

HomeInteractor.swift

import Foundation

protocol HomeInteractorInterface {
    func getTitle() -> HomeModel
}

class HomeInteractor { }

extension HomeInteractor: HomeInteractorInterface {
    func getTitle() -> HomeModel {
        return HomeModel(title: "Home VIPER")
    }
}

HomeInteractor.swift

HomeModel.swift

struct HomeModel {
    
    let title: String
}

AppDelegate.swift dosyasındaki application fonksiyonunun içerisine aşağıdaki gibi tanımlamamızı yapıyoruz. Uygulama açıldığında gösterilecek olan ViewController’ ımızın hangisi olacağını burada belirtiyoruz.

self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = HomeRouter().createModule()
self.window?.makeKeyAndVisible()

Sonuç

GetTitle methoduyla; title ‘Home VIPER’ olarak alınmıştır.

updateTitle methodu çağırıldığında; title ‘Hello VIPER’ olmuştur.

Output

The post VIPER’a Giriş appeared first on AppSome - iOS - Swift.

]]>
https://appsome.net/vipergiris/feed/ 1