কম্পিউটার

কিভাবে সুইফটে একটি API কল করতে হয়

আপনি যদি একজন iOS ডেভেলপার হতে চান, তাহলে জানার মতো কিছু মৌলিক দক্ষতা রয়েছে। প্রথমত, টেবিল ভিউ তৈরির সাথে পরিচিত হওয়া গুরুত্বপূর্ণ। দ্বিতীয়ত, আপনার জানা উচিত কিভাবে ডেটা দিয়ে সেই টেবিল ভিউগুলিকে পপুলেট করতে হয়। তৃতীয়ত, আপনি যদি একটি API থেকে ডেটা আনতে পারেন এবং আপনার টেবিল ভিউতে এই ডেটা ব্যবহার করতে পারেন তবে এটি দুর্দান্ত৷

তৃতীয় পয়েন্ট আমরা এই নিবন্ধে কভার করব কি. Codable প্রবর্তনের পর থেকে Swift 4 এ, API কল করা অনেক সহজ। পূর্বে অধিকাংশ মানুষ Alamofire এবং SwiftyJson-এর মত পড ব্যবহার করত (আপনি এটি কীভাবে করবেন তা এখানে পড়তে পারেন)। এখন সুইফ্ট ওয়ে বাক্সের বাইরে অনেক সুন্দর, তাই পড ডাউনলোড করার কোন কারণ নেই৷

আসুন কিছু বিল্ডিং ব্লকের মধ্য দিয়ে যাই যা প্রায়শই একটি API কল করতে ব্যবহৃত হয়। আমরা প্রথমে এই ধারণাগুলি কভার করব, কারণ এগুলি কীভাবে একটি API কল করতে হয় তা বোঝার জন্য গুরুত্বপূর্ণ অংশ৷

  • কমপ্লিশন হ্যান্ডলার
  • URLSession
  • DispatchQueue
  • চক্র ধরে রাখুন

অবশেষে আমরা সব একসাথে করা হবে. আমি এই প্রকল্পটি তৈরি করতে ওপেন সোর্স Star Wars API ব্যবহার করব। আপনি GitHub এ আমার সম্পূর্ণ প্রজেক্ট কোড দেখতে পারেন।

অস্বীকৃতি সতর্কতা:আমি কোডিং-এ নতুন এবং আমি মূলত স্ব-শিক্ষিত। আমি কিছু ধারণা ভুলভাবে উপস্থাপন করলে ক্ষমাপ্রার্থী৷

কমপ্লিশন হ্যান্ডলার

কিভাবে সুইফটে একটি API কল করতে হয়
দরিদ্র রোগী Pheobe

ফ্রেন্ডস এর সেই পর্বের কথা মনে আছে যেখানে ফিওব কয়েকদিন ধরে ফোনে আটকে আছে গ্রাহক পরিষেবার সাথে কথা বলার অপেক্ষায়? কল্পনা করুন যদি সেই ফোন কলের একেবারে শুরুতে, পিপ নামে একজন সুন্দর ব্যক্তি বলেছিলেন:"কল করার জন্য ধন্যবাদ। আমি জানি না আপনাকে কতক্ষণ অপেক্ষা করতে হবে, তবে আমরা প্রস্তুত হলে আমি আপনাকে কল করব। তোমার জন্য." এটি এতটা মজার হত না, কিন্তু পিপ ফিওবের জন্য একটি সম্পূর্ণ হ্যান্ডলার হওয়ার প্রস্তাব দিচ্ছে৷

আপনি একটি ফাংশনে একটি সমাপ্তি হ্যান্ডলার ব্যবহার করেন যখন আপনি জানেন যে ফাংশনটি সম্পূর্ণ হতে কিছু সময় লাগবে। আপনি কতক্ষণ জানেন না, এবং আপনি এটি শেষ হওয়ার জন্য আপনার জীবনকে থামাতে চান না। তাই আপনি পিপকে আপনার কাঁধে টোকা দিতে বলুন যখন সে আপনাকে উত্তর দিতে প্রস্তুত। এইভাবে আপনি আপনার জীবন সম্পর্কে যেতে পারেন, কিছু কাজ চালাতে পারেন, একটি বই পড়তে পারেন এবং টিভি দেখতে পারেন। যখন পিপ উত্তর দিয়ে আপনার কাঁধে টোকা দেয়, আপনি তার উত্তর নিতে পারেন এবং এটি ব্যবহার করতে পারেন।

API কলগুলির সাথে এটিই ঘটে। আপনি একটি সার্ভারে একটি URL অনুরোধ পাঠান, এটি কিছু ডেটার জন্য জিজ্ঞাসা করুন৷ আপনি আশা করি সার্ভার দ্রুত ডেটা ফেরত দেবে, কিন্তু আপনি জানেন না কতক্ষণ লাগবে। আপনার ব্যবহারকারীকে সার্ভারের জন্য আপনাকে ডেটা দেওয়ার জন্য ধৈর্য ধরে অপেক্ষা করার পরিবর্তে, আপনি একটি সমাপ্তি হ্যান্ডলার ব্যবহার করেন। এর মানে হল আপনি আপনার অ্যাপটিকে বন্ধ করতে এবং অন্যান্য কাজ করতে বলতে পারেন, যেমন পৃষ্ঠার বাকি অংশ লোড করা৷

আপনি সমাপ্তি হ্যান্ডলারকে আপনার কাঁধে ট্যাপ করতে বলুন একবার এটিতে আপনার পছন্দসই তথ্য রয়েছে। আপনি যে তথ্য কি নির্দিষ্ট করতে পারেন. এইভাবে, যখন আপনার অ্যাপটি কাঁধে ট্যাপ হয়ে যায়, তখন এটি সমাপ্তি হ্যান্ডলার থেকে তথ্য নিতে পারে এবং এটির সাথে কিছু করতে পারে। সাধারণত আপনি যা করবেন তা হল টেবিল ভিউ পুনরায় লোড করা যাতে ব্যবহারকারীর কাছে ডেটা প্রদর্শিত হয়।

একটি সম্পূর্ণ হ্যান্ডলার দেখতে কেমন তার একটি উদাহরণ এখানে। প্রথম উদাহরণ হল API কল নিজেই সেট আপ করা:

func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
  // Setup the variable lotsOfFilms
  var lotsOfFilms: [Film]
  
  // Call the API with some code
  
  // Using data from the API, assign a value to lotsOfFilms  
  
  // Give the completion handler the variable, lotsOfFilms
  completionHandler(lotsOfFilms)
}
একটি ফাংশন যা একটি সম্পূর্ণ হ্যান্ডলার ব্যবহার করে

এখন আমরা ফাংশনটিকে কল করতে চাই fetchFilms . উল্লেখ্য কিছু জিনিস:

  • আপনাকে completionHandler উল্লেখ করার দরকার নেই আপনি যখন ফাংশন কল. শুধুমাত্র একবার আপনি completionHandler উল্লেখ করেন ফাংশন ঘোষণার ভিতরে আছে।
  • কমপ্লিশন হ্যান্ডলার ব্যবহার করার জন্য আমাদের কিছু ডেটা ফেরত দেবে। আমরা উপরে যে ফাংশনটি লিখেছি তার উপর ভিত্তি করে, আমরা [Film] ধরনের ডেটা আশা করতে জানি। . আমাদের ডেটার নাম দিতে হবে যাতে আমরা এটি উল্লেখ করতে পারি। নিচে আমি films নামটি ব্যবহার করছি , কিন্তু এটি randomData হতে পারে , অথবা অন্য কোনো পরিবর্তনশীল নাম যা আমি চাই।

কোডটি এরকম কিছু দেখাবে:

fetchFilms() { (films) in
  // Do something with the data the completion handler returns 
  print(films)
}
একটি সমাপ্তি হ্যান্ডলারের সাথে একটি ফাংশন বাস্তবায়ন করা

URL সেশন

URLSession একটি দলের ম্যানেজারের মত। ম্যানেজার নিজে থেকে কিছু করেন না। তার কাজ হল তার দলের লোকদের সাথে কাজ ভাগ করা, এবং তারা কাজটি সম্পন্ন করবে। তার দল হল dataTasks . প্রতিবার আপনার কিছু ডেটার প্রয়োজন হলে, বসকে লিখুন এবং URLSession.shared.dataTask ব্যবহার করুন .

আপনি dataTask দিতে পারেন আপনার লক্ষ্য অর্জনে সহায়তা করার জন্য বিভিন্ন ধরণের তথ্য। dataTask কে তথ্য দেওয়া হচ্ছে প্রাথমিককরণ বলা হয়। আমি আমার dataTasks আদ্যক্ষর করি ইউআরএল সহ। dataTasks এছাড়াও তাদের আরম্ভের অংশ হিসাবে সমাপ্তি হ্যান্ডলার ব্যবহার করুন। এখানে একটি উদাহরণ:

let url = URL(string: "https://www.swapi.co/api/films")

let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in 
  // your code here
})

task.resume()
কিছু ​​ডেটা আনতে URLSession কিভাবে ব্যবহার করবেন

dataTasks সমাপ্তি হ্যান্ডলার ব্যবহার করুন, এবং তারা সবসময় একই ধরনের তথ্য ফেরত দেয়:data , response এবং error . আপনি এই ডেটা টাইপের বিভিন্ন নাম দিতে পারেন, যেমন (data, res, err) অথবা (someData, someResponse, someError) . নিয়মের খাতিরে, নতুন পরিবর্তনশীল নামের সাথে দুর্বৃত্ত হওয়ার পরিবর্তে সুস্পষ্ট কিছুতে লেগে থাকা ভাল।

চলুন শুরু করা যাক error দিয়ে . যদি dataTask একটি error প্রদান করে , আপনি যে আগাম জানতে চাইবেন. এর অর্থ হল আপনি ত্রুটিটি সুন্দরভাবে পরিচালনা করতে আপনার কোডকে নির্দেশ করতে পারেন। এর অর্থ হল আপনি ডেটা পড়ার চেষ্টা করতে এবং এটির সাথে কিছু করতে বিরক্ত করবেন না কারণ ডেটা ফেরত দেওয়ার ক্ষেত্রে একটি ত্রুটি রয়েছে।

নীচে আমি কনসোলে একটি ত্রুটি প্রিন্ট করে এবং ফাংশন থেকে প্রস্থান করে সত্যিই ত্রুটিটি পরিচালনা করছি। আপনি চাইলে অন্য অনেক উপায়ে ত্রুটিটি পরিচালনা করতে পারেন। এই ডেটা আপনার অ্যাপের জন্য কতটা মৌলিক তা ভেবে দেখুন। উদাহরণস্বরূপ, যদি আপনার একটি ব্যাঙ্কিং অ্যাপ থাকে এবং এই API কল ব্যবহারকারীদের তাদের ব্যালেন্স দেখায়, তাহলে আপনি ব্যবহারকারীর কাছে একটি মডেল উপস্থাপন করে ত্রুটিটি পরিচালনা করতে চাইতে পারেন যাতে বলা হয়, "দুঃখিত, আমরা এখন একটি সমস্যা অনুভব করছি৷ অনুগ্রহ করে চেষ্টা করুন৷ আবার পরে।"

if let error = error {
  print("Error accessing swapi.co: /(error)")
  return
}
ত্রুটি পরিচালনা করুন

পরবর্তী আমরা প্রতিক্রিয়া তাকান. আপনি একটি httpResponse হিসাবে প্রতিক্রিয়া কাস্ট করতে পারেন৷ . এইভাবে আপনি স্ট্যাটাস কোডগুলি দেখতে পারেন এবং কোডের উপর ভিত্তি করে কিছু সিদ্ধান্ত নিতে পারেন। উদাহরণস্বরূপ, যদি স্ট্যাটাস কোড 404 হয়, তাহলে আপনি জানেন যে পৃষ্ঠাটি পাওয়া যায়নি।

নিচের কোডটি একটি guard ব্যবহার করে দুটি জিনিস বিদ্যমান কিনা তা পরীক্ষা করতে। যদি উভয়ই বিদ্যমান থাকে, তাহলে এটি কোডটিকে guard-এর পরে পরবর্তী বিবৃতিতে চালিয়ে যেতে দেয় ধারা যদি কোন একটি বিবৃতি ব্যর্থ হয়, তাহলে আমরা ফাংশন থেকে প্রস্থান করি। এটি একটি guard এর একটি সাধারণ ব্যবহারের ক্ষেত্রে ধারা আপনি আশা করেন যে একটি গার্ড ক্লজের পরে কোডটি সুখী দিনের প্রবাহ হবে (অর্থাৎ কোন ত্রুটি ছাড়াই সহজ প্রবাহ)।

  guard let httpResponse = response as? HTTPURLResponse,
            (200...299).contains(httpResponse.statusCode) else {
    print("Error with the response, unexpected status code: \(response)")
    return
  }

অবশেষে আপনি নিজেই ডেটা পরিচালনা করেন। লক্ষ্য করুন যে আমরা error-এর জন্য সম্পূর্ণ হ্যান্ডলার ব্যবহার করিনি অথবা response . কারণ সমাপ্তি হ্যান্ডলার API থেকে ডেটার জন্য অপেক্ষা করছে। যদি এটি কোডের ডেটা অংশে না যায়, তাহলে হ্যান্ডলারকে ডাকার দরকার নেই৷

ডেটার জন্য, আমরা JSONDecoder ব্যবহার করছি একটি সুন্দর উপায়ে ডেটা পার্স করতে। এটি বেশ নিফটি, তবে আপনার একটি মডেল প্রতিষ্ঠা করা প্রয়োজন৷ আমাদের মডেলটিকে FilmSummary বলা হয় . যদি JSONDecoder আপনার কাছে নতুন, তাহলে এটি কীভাবে ব্যবহার করবেন এবং কীভাবে Codable ব্যবহার করবেন তা অনলাইনে দেখুন। . সুইফ্ট 3 দিনের তুলনায় সুইফট 4 এবং তার উপরে এটি সত্যিই সহজ৷

নীচের কোডে, আমরা প্রথমে পরীক্ষা করছি যে ডেটা বিদ্যমান। আমরা মোটামুটি নিশ্চিত যে এটি থাকা উচিত, কারণ কোন ত্রুটি নেই এবং কোন অদ্ভুত HTTP প্রতিক্রিয়া নেই। দ্বিতীয়ত, আমরা পরীক্ষা করি যে আমরা আমাদের প্রত্যাশা অনুযায়ী প্রাপ্ত ডেটা পার্স করতে পারি। আমরা যদি পারি, তাহলে আমরা ফিল্মের সারাংশটি সমাপ্তি হ্যান্ডলারের কাছে ফিরিয়ে দিই। এপিআই থেকে ফেরত দেওয়ার মতো কোনো ডেটা না থাকলে, আমাদের খালি অ্যারের একটি ফল ব্যাক প্ল্যান আছে৷

if let data = data,
        let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
        completionHandler(filmSummary.results ?? [])
      }

সুতরাং API কলের জন্য সম্পূর্ণ কোডটি এইরকম দেখাচ্ছে:

func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
    let url = URL(string: domainUrlString + "films/")!

    let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
      if let error = error {
        print("Error with fetching films: \(error)")
        return
      }
      
      guard let httpResponse = response as? HTTPURLResponse,
            (200...299).contains(httpResponse.statusCode) else {
        print("Error with the response, unexpected status code: \(response)")
        return
      }

      if let data = data,
        let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
        completionHandler(filmSummary.results ?? [])
      }
    })
    task.resume()
  }

চক্র ধরে রাখুন

NB:ধরে রাখার চক্র বুঝতে আমি খুবই নতুন! আমি অনলাইনে যা গবেষণা করেছি তার সারাংশ এখানে।

মেমরি পরিচালনার জন্য চক্র ধরে রাখা গুরুত্বপূর্ণ। মূলত আপনি আপনার অ্যাপটি মেমরির বিটগুলি পরিষ্কার করতে চান যা এটির আর প্রয়োজন নেই। আমি অনুমান করি এটি অ্যাপটিকে আরও পারফরম্যান্স করে তোলে।

সুইফট আপনাকে স্বয়ংক্রিয়ভাবে এটি করতে সাহায্য করে এমন অনেক উপায় রয়েছে। তবে এমন অনেক উপায় রয়েছে যা আপনি ভুলবশত আপনার অ্যাপে সাইকেল ধরে রাখতে পারেন। একটি রিটেন সাইকেল মানে আপনার অ্যাপ সবসময় একটি নির্দিষ্ট কোডের জন্য মেমরি ধরে রাখবে। সাধারণত এটি ঘটে যখন আপনার কাছে দুটি জিনিস থাকে যা একে অপরের প্রতি শক্তিশালী নির্দেশক থাকে।

এটির কাছাকাছি যেতে, লোকেরা প্রায়শই weak ব্যবহার করে . যখন কোডের এক দিক weak হয় , আপনার কাছে রিটেন সাইকেল নেই এবং আপনার অ্যাপ মেমরি রিলিজ করতে সক্ষম হবে।

আমাদের উদ্দেশ্যে, একটি সাধারণ প্যাটার্ন হল [weak self] ব্যবহার করা API কল করার সময়। এটি নিশ্চিত করে যে একবার সম্পূর্ণ হ্যান্ডলার কিছু কোড ফেরত দিলে, অ্যাপটি মেমরি ছেড়ে দিতে পারে।

fetchFilms { [weak self] (films) in
  // code in here
}

DispatchQueue

Xcode সমান্তরালভাবে কোড চালানোর জন্য বিভিন্ন থ্রেড ব্যবহার করে। একাধিক থ্রেডের সুবিধার অর্থ হল আপনি পরেরটিতে যাওয়ার আগে একটি জিনিস শেষ করার জন্য অপেক্ষা করে আটকে থাকবেন না। আশা করি আপনি এখানে সম্পূর্ণ হ্যান্ডলারের লিঙ্কগুলি দেখতে শুরু করতে পারেন৷

এই থ্রেডগুলিকে প্রেরণ সারিও বলা হয় বলে মনে হচ্ছে। API কলগুলি একটি সারিতে পরিচালনা করা হয়, সাধারণত পটভূমিতে একটি সারি। একবার আপনার API কল থেকে ডেটা পেয়ে গেলে, সম্ভবত আপনি সেই ডেটা ব্যবহারকারীকে দেখাতে চাইবেন। তার মানে আপনি আপনার টেবিল ভিউ রিফ্রেশ করতে চাইবেন।

টেবিলের দৃশ্যগুলি UI-এর অংশ, এবং সমস্ত UI ম্যানিপুলেশনগুলি প্রধান প্রেরণের সারিতে করা উচিত৷ এর মানে হল আপনার ভিউ কন্ট্রোলার ফাইলের কোথাও, সাধারণত viewDidLoad এর অংশ হিসাবে ফাংশন, আপনার কিছু কোড থাকা উচিত যা আপনার টেবিল ভিউকে রিফ্রেশ করতে বলে।

এপিআই থেকে কিছু নতুন ডেটা পাওয়া গেলেই আমরা টেবিল ভিউ রিফ্রেশ করতে চাই। এর মানে হল আমরা আমাদের কাঁধে ট্যাপ করার জন্য একটি সমাপ্তি হ্যান্ডলার ব্যবহার করব এবং সেই API কল শেষ হলে আমাদের জানাব৷ আমরা টেবিল রিফ্রেশ করার আগে সেই ট্যাপ পর্যন্ত অপেক্ষা করব।

কোডটি এরকম কিছু দেখাবে:

fetchFilms { [weak self] (films) in
  self.films = films

  // Reload the table view using the main dispatch queue
  DispatchQueue.main.async {
    tableView.reloadData()
  }
}

ViewDidLoad বনাম viewDidAppear

অবশেষে আপনাকে সিদ্ধান্ত নিতে হবে কোথায় আপনার fetchfilms কল করবেন ফাংশন এটি একটি ভিউ কন্ট্রোলারের ভিতরে থাকবে যা API থেকে ডেটা ব্যবহার করবে। আপনি এই API কল করতে পারেন দুটি সুস্পষ্ট জায়গা আছে. একটি viewDidLoad এর ভিতরে আছে এবং অন্যটি viewDidAppear এর ভিতরে .

এগুলি আপনার অ্যাপের জন্য দুটি ভিন্ন রাজ্য। আমার উপলব্ধি হল viewDidLoad প্রথমবার যখন আপনি ফোরগ্রাউন্ডে সেই ভিউ লোড করেন তাকে বলা হয়। viewDidAppear আপনি যখনই সেই দৃশ্যে ফিরে আসেন তখনই কল করা হয়, উদাহরণস্বরূপ যখন আপনি ভিউতে ফিরে আসার জন্য পিছনের বোতাম টিপুন৷

আপনি যদি আশা করেন যে ব্যবহারকারীর সেই ভিউতে এবং সেখান থেকে নেভিগেট করার সময়গুলির মধ্যে আপনার ডেটা পরিবর্তন হবে, তাহলে আপনি আপনার API কল viewDidAppear এ রাখতে চাইতে পারেন। . তবে আমি মনে করি প্রায় সব অ্যাপের জন্য, viewDidLoad পর্যাপ্ত. Apple viewDidAppear সুপারিশ করে সমস্ত API কলের জন্য, তবে এটি ওভারকিলের মতো মনে হচ্ছে। আমি মনে করি এটি আপনার অ্যাপটিকে কম পারফরম্যান্স করবে কারণ এটি আরও অনেক API কল করছে যা এটির প্রয়োজন৷

সব ধাপ একত্রিত করা

প্রথম:API কল করে এমন ফাংশনটি লিখুন। উপরে, এটি fetchFilms . এটিতে একটি সমাপ্তি হ্যান্ডলার থাকবে, যা আপনার আগ্রহের ডেটা ফেরত দেবে৷ আমার উদাহরণে, সমাপ্তি হ্যান্ডলার ফিল্মগুলির একটি অ্যারে প্রদান করে৷

দ্বিতীয়:আপনার ভিউ কন্ট্রোলারে এই ফাংশনটি কল করুন। আপনি এখানে এটি করেন কারণ আপনি API থেকে ডেটার উপর ভিত্তি করে ভিউ আপডেট করতে চান। আমার উদাহরণে, API ডেটা ফেরত দিলে আমি একটি টেবিল ভিউ রিফ্রেশ করছি।

তৃতীয়:আপনার ভিউ কন্ট্রোলারে আপনি ফাংশনটি কোথায় কল করতে চান তা নির্ধারণ করুন। আমার উদাহরণে, আমি এটিকে viewDidLoad এ কল করি .

চতুর্থ:API থেকে ডেটা নিয়ে কী করবেন তা স্থির করুন। আমার উদাহরণে, আমি একটি টেবিল ভিউ রিফ্রেশ করছি।

ভিতরে NetworkManager.swift (আপনি চাইলে এই ফাংশনটি আপনার ভিউ কন্ট্রোলারে সংজ্ঞায়িত করা যেতে পারে, কিন্তু আমি MVVM প্যাটার্ন ব্যবহার করছি)।

func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
    let url = URL(string: domainUrlString + "films/")!

    let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
      if let error = error {
        print("Error with fetching films: \(error)")
        return
      }
      
      guard let httpResponse = response as? HTTPURLResponse,
            (200...299).contains(httpResponse.statusCode) else {
        print("Error with the response, unexpected status code: \(response)")
        return
      }

      if let data = data,
        let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
        completionHandler(filmSummary.results ?? [])
      }
    })
    task.resume()
  }

ভিতরে FilmsViewController.swift :

final class FilmsViewController: UIViewController {
  private var films: [Film]?

  override func viewDidLoad() {
    super.viewDidLoad()
    
    NetworkManager().fetchFilms { [weak self] (films) in
      self?.films = films
      DispatchQueue.main.async {
        self?.tableView.reloadData()
      }
    }
  }
  
  // other code for the view controller
}

ভগবান, আমরা এটা করেছি! আমার সাথে থাকার জন্য ধন্যবাদ।


  1. SwiftUI-তে কীভাবে একটি সাধারণ অ্যাসিঙ্ক GET REST API কল করবেন

  2. কিভাবে এক্সেলে একটি ডেটা টেবিল তৈরি করবেন (সবচেয়ে সহজ ৫টি পদ্ধতি)

  3. কিভাবে মুছে ফেলা ফাইলগুলিকে ম্যাকে অপুনরুদ্ধারযোগ্য করা যায়

  4. কিভাবে ব্যাক আপ করা ডেটাকে মূল্যবান করা যায়