آموزش و بررسی Class ها در سوئیفت

زمان مطالعه : ۴ دقیقه
آخرین بروزرسانی : ۳۱ اردیبهشت ۱۴۰۵
آموزش class ها از مبحث شی گرایی در زبان برنامه نویسی سوئیفت

class ها از مباحث بسیار مهم نه تنها در سوئیفت بلکه در اکثر زبان های برنامه نویس هستند.

از class ها برای ذخیره کردن مجموعه ای از داده ها استفاده میشود، که ازین جهت تا حده زیادی شبیه به structure ها هستند. اما در مجموع تفاوت های مهمی با استراکچر ها دارند که به آن اشاره میکنیم.

تعریف Class در Swift

برای تعریف یک کلاس از کلمه کلیدی class استفاده میکنیم، و درون آن متغیر ها و تابع هایی که نیاز داریم را مینویسیم.

اما به یک نکته مهم باید دقت کرد، بر خلاف struct ها، در کلاس امکان تعریف یک متغیر بدون مقداردهی اولیه را نداریم. برای حل این مشکل سه راه حل وجود دارد:

  1. استفاده کردن از علامت ? که متغیر ها را بصورت optional قرار میدهد. (که بنده این روش را پیشنهاد نمیکنم.)
  2. استفاده از مقدار اولیه، یعنی برای هر متغیری که قرار میدهید، یک value اولیه را هم قرار دهید. که بستگی به نیازهایی که دارید، میتواند مفید مواقع شود.
  3. و اما بهترین راه استفاده از init هست که مقدار دهی اولیه متغیر ها را موقع تعریف یک نسخه از class انجام میدهد.

حالا هر سه تا روشی که بالا گفتیم را در مثال زیر میتوانید ببینید.

// first method:
class Apple {
    var product: String?
    var price: Int?
}
// second method:
class Apple {
    var product: String = "Mac Book air M1"
    var price: Int = 999
}
// third method:
class Apple {
    var product: String
    var price: Int
    init(product: String, price: Int) {
        self.product = product
        self.price = price
    }
}

ساخت یک نسخه از کلاس هایی که در بالا تعریف کردیم (به ترتیب) :

// first method:
var device = Apple()
// second method:
var device = Apple()
// third method:
var device = Apple(product: "iPhone", price: 1199)
توضیحات بیشتر:
  • ما همیشه هنگام تعریف متغیر، رنگ آن را سفید میگذاشتیم، اما در روش سوم برای واضح شدن و درک بهتر، رنگ متغیر های درون کلاس را نارنجی کردیم.
  • از آن جایی که هم اسم متغیر های درون کلاس، و هم اسم متغیر های درون init مثل یک دیگر هستند، برای این که مشخص کنیم کدام اسم به کدام متغیر اشاره میکند از self استفاده میکنیم. در واقع self به متغیری که درون class تعریف شده اشاره میکند.
  • اگر نام متغیر های درون init با نام متغیر های درون کلاس متفاوت باشد دیگر نیازی به استفاده از self نیست. (اما معمول هست که اسم ها یک جور باشند.)

از این به بعد از روش سوم برای ساخت class استفاده میکنیم.


تعریف تابع درون Class در Swift

تعریف تابع درون کلاس تا حد زیادی شبیه به استراکچر ها هست، در مثال زیر میتوانید نمونه ای از تعریف تابع درون کلاس را ببینید.

class Point {
    var x: Double
    var y: Double
    init(x: Double, y: Double) {
        self.x = x
        self.y = y
    }
    func locate() {
        print("location: X = \(x) and Y = \(y) ")
    }
}
let location = Point(x: 343.32, y: 1199.21)
print(location.x)
location.locate()

خروجی:

343.32
location: X = 343.32 and Y = 1199.21

اگر قصدتون فقط آشنایی با class ها بود تا همین جا کفایت میکند، از این جا به بعد مخصوص کسایی هستش که میخواهند برنامه نویس سوئیفت شوند و بحث یکم تخصصی تر میشود...

ارث بری یا Inheritance در Class

در مورد ارث بری در سوئیفت بعدا بطور مفصل صحبت میکنیم، اما از آنجایی که یادگیری آن ها در مبحث کلاس ها بسیار مهم هست، میخواهیم در این جا هم در مورد ارث بری صحبت کنیم.

به طور خلاصه ارث بری یعنی ویژگی های یک کلاس (کلاس پدر یا supper class) را به یک کلاس دیگر (کلاس فرزند یا subclass) بدهیم.

دقت کنید که مقداردهی کردن متغیر های مربوط به supper class یا کلاس پدر را با استفاده از super.init انجام میدهیم، به مثال زیر دقت کنید.

class Apple {
    var name: String
    var price: Double
    init(name: String, price: Double) {
        self.name = name
        self.price = price
    }
}

class IPhone: Apple {
    var camera: String
    init(camera: String, name: String, price: Double) {
        self.camera = camera
        super.init(name: name, price: price)
    }
}

class MacBook: Apple {
    var gpu: String
    init(gpu: String, name: String, price: Double) {
        self.gpu = gpu
        super.init(name: name, price: price)
    }
}
توضیحات کد بالا:
  • تمام محصولات اپل یک اسم و یک قیمتی دارند، پس کلاسی تعریف کردیم به نام Apple که شامل ویژگی کلی محصولات اپل باشد.
  • حال دو کلاس متفاوت برای IPhone و MacBook درست کردیم که ویژگی های مربوط به آن کلاس ها را دارد.
  • حال چون هم MacBook و هم IPhone محصول اپل هستند، کلاس Apple را به عنوان کلاس پدر این دو کلاس قرار دادیم.
  • دقت کنید که هنگام initialize کردن، اول متغیر های مربوط به خود کلاس را به روش همیشگی مقدار دهی میکنیم، و متغیر های مربوط به کلاس پدر را با استفاده از super.init مقدار دهی میکنیم.
  • حال اگر نسخه ای از کلاس MacBook بسازیم، علاوه بر متغیر های مربوط به خودش، متغیر های مربوط به کلاس پدرش، یعنی Apple را به ارث میبرد.

حالا میخواهیم یک نسخه از کلاس MacBook و IPhone بسازیم:

let iPhone = IPhone(camera: "12 MP", name: "iPhone 12 Pro Max", price: 1099)
let macBook = MacBook(gpu: "7 Core", name: "MacBook Air M1", price: 999)
print(iPhone.price)
print(macBook.gpu)

خروجی:

1099
7 Core

بازنویسی توابع (Override) در Class

گاهی اوقات کلاس فرزند میخواهد رفتار یکی از تابع هایی که در کلاس پدر تعریف شده را تغییر دهد. در این مواقع از کلمه کلیدی override استفاده میکنیم.

در واقع با override میگوییم که این تابع قبلا در کلاس پدر وجود داشته، اما ما میخواهیم نسخه جدیدی از آن را در کلاس فرزند تعریف کنیم.

به مثال زیر دقت کنید:

class Animal {
    func makeSound() {
        print("Some sound")
    }
}

class Dog: Animal {
    override func makeSound() {
        print("Woof!")
    }
}

let dog = Dog()
dog.makeSound()

خروجی:

Woof!
توضیح:
  • تابع makeSound در کلاس Animal تعریف شده بود.
  • اما در کلاس Dog آن را با استفاده از override بازنویسی کردیم.
  • به همین دلیل وقتی تابع اجرا میشود، نسخه مربوط به کلاس Dog اجرا میشود.

استفاده از super برای دسترسی به کلاس پدر

در مثال ارث بری دیدیم که برای مقداردهی متغیر های کلاس پدر از super.init استفاده میکنیم.

اما super فقط برای init نیست. با استفاده از آن میتوانیم به تابع ها و ویژگی های کلاس پدر هم دسترسی داشته باشیم.

فرض کنید یک تابع در کلاس پدر داریم و میخواهیم همان تابع در کلاس فرزند اجرا شود، اما در کنارش یک رفتار جدید هم اضافه کنیم.

class Vehicle {
    func start() {
        print("Vehicle started")
    }
}

class Car: Vehicle {
    override func start() {
        super.start()
        print("Car is ready to move")
    }
}

let car = Car()
car.start()

خروجی:

Vehicle started
Car is ready to move
توضیح:
  • در کلاس Car تابع start را بازنویسی کردیم.
  • با super.start() ابتدا تابع کلاس پدر اجرا میشود.
  • سپس رفتار مخصوص کلاس Car اضافه میشود.

متد های نوع (Type Methods)

تمام تابع هایی که تا الان نوشتیم مربوط به یک نسخه از کلاس بودند. یعنی باید اول از کلاس یک instance بسازیم، بعد تابع را اجرا کنیم.

اما گاهی لازم داریم تابعی داشته باشیم که به خود کلاس مربوط باشد، نه به یک نسخه از آن. در این مواقع از static استفاده میکنیم.

class MathHelper {

    static func square(number: Int) -> Int {
        return number * number
    }

}

let result = MathHelper.square(number: 5)
print(result)

خروجی:

25
توضیح:
  • برای اجرای این تابع نیازی به ساخت instance از کلاس نداریم.
  • تابع را مستقیما از طریق نام کلاس اجرا میکنیم.
  • این نوع متد ها معمولا برای عملیات های کمکی (helper functions) استفاده میشوند.

Property Observer ها در Class

گاهی اوقات میخواهیم وقتی مقدار یک متغیر تغییر کرد، یک عمل خاص انجام شود.

برای این کار میتوانیم از willSet و didSet استفاده کنیم.

  • willSet قبل از تغییر مقدار اجرا میشود.
  • didSet بعد از تغییر مقدار اجرا میشود.
class BankAccount {

    var balance: Int = 0 {

        willSet {
            print("Balance will change to \(newValue)")
        }

        didSet {
            print("Balance changed from \(oldValue) to \(balance)")
        }

    }

}

var account = BankAccount()
account.balance = 500

خروجی:

Balance will change to 500
Balance changed from 0 to 500
توضیح:
  • در willSet میتوانیم به مقدار جدید با newValue دسترسی داشته باشیم.
  • در didSet میتوانیم مقدار قبلی را با oldValue ببینیم.
  • از این قابلیت معمولا برای لاگ گرفتن، بررسی تغییرات یا بروزرسانی UI استفاده میشود.

این مقاله در حال بروز رسانی است و در آینده نزدیک مباحث جدید تری اضافه میشود...
میلاد خط شب
میلاد خط شب
معتقدم هر آدمی میتواند در یک زمینه ای مفید و تاثیر گذار باشد و اصولا آدم "غیر مفید" نداریم. فقط کافی است به موقع و صحیح راهنمایی شود.
پاسخ دهید


برخی از افرادی که این مقاله را پسندیدند 🥰

32