৩-৩ঃ কালেকশনস (অ্যারে ও ডিকশনারী), ইনুম্যারেশন ও ক্লোজার

Standard

এই সিরিজের পুরো পোস্ট লিস্ট এবং সম্ভাব্য কাগুজে বই এর সূচি দেখতে ক্লিক করুনbangla-ios-objective-c-swift-nuhil

আগের চ্যাপ্টারঃ সুইফ্ট ল্যাঙ্গুয়েজে স্ট্রিং ও ক্যারেকটার টাইপ ভ্যারিয়েবল

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

কালেকশন টাইপঃ
ধরুন, আপনি একজন রেস্টুরেন্টের মালিক। আপনি আপনার রেগুলার কাস্টমারদের একটি তালিকা করতে চাচ্ছেন সুইফ্ট ল্যাঙ্গুয়েজে। একটু সহজ করার জন্য ধরে নিচ্ছি যে আপনি শুধু কাস্টমারদের নামের তালিকা করবেন। ভাবুন তো কিভাবে করবেন। যদি আপনার ১০০ জন কাস্টমার থাকে তাহলে ১০০ টা ভ্যারিয়েবল ডিক্লেয়ার করবেন। প্রত্যেকটিতে একজন করে কাস্টমারের নাম স্টোর করবেন।

var 1stCust = "First Customer's Name"
var 2ndCust = "2nd Customer's Name"
var 3rdCust = "3rd Customer's Name"
.
.
var 100thCust = "100th Customer's Name"

আচ্ছা, এবার বলুন তো আপনি যদি এই লিস্টটা দেখতে চান তাহলে কি করবেন?

println(1stCust)
println(2ndCust)
.
.
println(100thCust)

এভাবে করার সমস্যাটা হলঃ

  • আপনি নিশ্চয়ই জানেন না যে আপনার কাস্টমার সংখ্যা কত হতে পারে।
  • এভাবে ধরে ধরে এক-একটি ভ্যারিয়েবলে ডাটা স্টোর করতে হবে। আবার এক-একটি ধরে ডাটা ডিসপ্লে করতে হবে।
  • কোন রকমের সার্চিং টেকনিক (Searching Technique) অ্যাপ্লাই করা যাবে না। অর্থাৎ কোন নির্দিস্ট কাস্টমারের নাম খুজে বের করা যাবে না।
  • কোন রকমের সর্টিং টেকনিক (Sorting Technique) অ্যাপ্লাই করা যাবে না। অর্থাৎ লিস্টটিকে কোন নির্দিস্ট অর্ডারে সাজানো যাবে না।

এসব সমস্যার সমাধানের জন্য সুইফ্টে রয়েছে দুই ধরনের কালেকশন টাইপ (Collection Type) যথাঃ অ্যারে (Arrays) ও ডিকশনারী (Dictionaries)।

অ্যারে ও ডিকশনারী (Arrays and Dictionaries)ঃ
একই ডাটাটাইপের অনেকগুলো ভ্যারিয়েবলের কালেকশনকেই অ্যারে অথবা ডিকশনারী বলা হয়। অ্যারে ও ডিকশনারী উভয়ই একটি নির্দিষ্ট ডাটাটাইপের এক বা একাধিক ডাটা সংরক্ষন করতে পারে। অ্যারেতে ভ্যালুগুলো সবসময় একটি অর্ডারে সাজানো থাকে। 0 থেকে শুরু করে 1, 2, 3, …, n-1 পর্যন্ত অর্ডারে ডাটাগুলো থাকে। এই ০ থেকে n-1 কে অ্যারের ইনডেক্স বলা হয়। এখানে n মানে হল অ্যারের সাইজ বা অ্যারেতে থাকা ভ্যালুর সংখ্যা। এই ইনডেক্স ব্যবহার করেই অ্যারের ভ্যালু গুলো ম্যানিপুলেট করা হয়। অন্যদিকে ডিকশনারীতে ভ্যালুগুলো কোন নির্দিষ্ট অর্ডারে সাজানো থাকে না। এখানে ইনডেক্সকে বলা হয় কি (Key)। key হল নির্দিষ্ট ইউনিক আইডেন্টিফায়ার যা দিয়ে ডিকশনারীর ভ্যালুগুলোকে সংরক্ষন বা এক্সেস করা যায়। যেমন ঃ

restaurantArr[0] = 10; // Array
restaurantArr[1] = 15; // Array
restaurantDict["numberOfItem"] = 10  // Dictionary

অ্যারে (Array)
একটি অ্যারেতে একই ডাটাটাইপের একাধিক ভ্যালু থাকতে পারে। ০ থেকে শুরু করে ১, ২, ৩, … অর্ডারে সাজানো থাকে ভ্যালুগুলো। আবার একই ভ্যালু একাধিক বারও থাকতে পারে। অবজেকটিভ-সি এর NSArray এবং NSMutableArray তে যেকোন অবজেক্ট ভ্যালু হিসেবে রাখা যায়। এমনকি একটি অ্যারে তে বিভিন্ন টাইপের ভ্যালু বা অবজেক্ট থাকতে পারে। কিন্তু সুইফ্ট -এ অ্যারেতে একটি নির্দিষ্ট টাইপের ডাটা রাখা যাবে। উদাহরনস্বরুপ Int টাইপের অ্যারেতে শুধু Int টাইপের ডাটাই রাখা যাবে। অন্য কিছু না।

অ্যারে টাইপ ভ্যারিয়েবল ডিক্লেয়ার করাঃ
বিভিন্নভাবে অ্যারে টাইপ ভ্যারিয়েবল ডিক্লেয়ার বা ইনিশিয়ালাইজ করা যায়। নিচে দুই ধরনের ডিক্লেয়ারেশন এর উদাহরন দেওয়া হলঃ

 Array<Int>variableA
 var variableB: Int[] = [1, 2, 4]

উপরের কোডটিতে variableA এবং variableB নামের দুটি অ্যারে ডিক্লেয়ার করা হয়েছে। দুই ভাবেই অ্যারে ডিক্লেয়ার করা যায়। প্রথমটিকে ফুল ফর্ম আর দ্বিতীয়টিকে শর্টহ্যান্ড সিনট্যাক্স বলা হয়। শর্টহ্যান্ড সিনট্যাক্সই সবচেয়ে বেশী ব্যবহার করা হয়। এক্ষেত্রে var অথবা let লিখে তারপর অ্যারের নাম লিখতে হয়। এরপর কোলন (:) দিয়ে ডাটাটাইপ লিখে তারপর বন্ধনী ( [] ) চিহ্ন দিতে হয়।

অ্যারেতে সরাসরি [value1, value2,…..,valueN] অ্যাসাইন করে অ্যারে ইনিশিয়ালাইজ করা যায়। যেমনঃ

 var variableInt: Int[] = [12, 12, 34]
//variableInt is initialised with 12,12 and 34

 var variableString = ["Egg", "Fruits"] 
// variableString is initialised with Egg and Fruits with these two string

সুইফ্ট এর টাইপ ইনফারেন্স (Type Inference) সুবিধার জন্য অ্যারে ডিক্লেয়ার বা ইনিশিয়ালাইজেশনের সময় অ্যারের টাইপ না লিখলেও অ্যাসাইন করা ভ্যালুর টাইপই হবে অ্যারে টাইপ। তাই variableString একটি String টাইপ অ্যারে কারন দুটি String টাইপের ভ্যালু দিয়ে অ্যারেটি ইনিশিয়ালাইজ করা হয়েছে।

অ্যারে ম্যানিপুলেশন (এক্সেস ও মডিফিকেশন)
অ্যারের ভ্যালুগুলো পড়া (Access) বা আপডেট (Modification) করার জন্য সুইফ্ট ল্যাঙ্গুয়েজের অ্যারে টাইপ ভ্যারিয়েবলের জন্য কিছু প্রোপার্টি, মেথড আছে। এসব প্রোপার্টি বা মেথড দিয়ে অথবা সাবস্ক্রিপ্ট দিয়ে অ্যারে এক্সেস বা মডিফাই করা যায়। নিচে কিছু উদাহরন দিয়ে বিস্তারিত বর্ননা করা হলঃ
কোন অ্যারেতে কতগুলো ভ্যালু আছে তা জানা যাবে রিড-অনলি প্রোপার্টি count দিয়ে।

var menuItems = ["Eggs", "Potatoes", Chilis"]
println("menuItems array has \(menuItems.count) items.") // menuItems array has 3 items. 

প্রথমে menuItems অ্যারেটি ৩ টি স্ট্রিং টাইপ ভ্যালু দিয়ে ইনিশিয়ালাইজ করা হয়েছে। তারপর menuItems.count প্রোপার্টিতে অ্যারেতে কয়টি ভ্যালু আছে তা রিটার্ন করে। তাই menuItems.count ৩ রিটার্ন করবে।

বুলিয়ান isEmpty প্রোপার্টি দিয়ে কোন অ্যারে ফাঁকা কিনা তা চেক করা যায়। অর্থাৎ যদি কোন অ্যারের count ০ হয় বা অ্যারেতে কোন ইলিমেন্ট না থাকে তাহলে isEmpty প্রোপার্টি YES (true) রিটার্ন করে।

if menuItems.isEmpty {
    println("The menu list is empty.")
} else {
    println("The menu list is not empty.")
}
// prints "The menu list is not empty."

যেহেতু menuItems অ্যারেতে ৩টি স্ট্রিং ইলিমেন্ট আছে তাই menuItems.isEmpty প্রোপার্টি NO রিটার্ন করবে।

অ্যারের append মেথড ও += অপারেটর দিয়ে অ্যারেতে নতুন ভ্যালু যোগ করা যায়। আবার সরাসরি একটি নতুন অ্যারে কে += অপারেটর দিয়ে অ্যারের শেষে যোগ করা যায়। নিচে উদাহরন দেওয়া হলঃ

menuItems.append("Oils")
//Oils is appended on the menuItems array. Now menuItems has 4 strings.

menuItems += "Flour" 
// Flour is appended on the menuItems array. Now menuItems has 5 strings

menuItems += ["Baking powder", "Butter", "Chocolate" ]
//An array of 3 new items is appended on the menuItems array. Now menuItems has 8 strings.

আগেই বলেছি অ্যারেতে ভ্যালুগুলো ০, ১, ২, … অর্ডারে সাজানো থাকে। যেমন অ্যারের ৫ নম্বর ভ্যালু তথা ৫-১ বা ৪ তম ইনডেক্স এর ভ্যালু এক্সেস করার জন্য সাবস্ক্রিপ্ট সিনট্যাক্স ব্যবহার করে নিচের মত স্কয়ার ব্র্যাকেটের মধ্যে ইনডেক্স পাঠাতে হয়।

var 5thItem = menuItems[4]

এক্ষেত্রে অবশ্যই খেয়াল রাখতে হবে যাতে পাঠানো ইনডেক্সটি অ্যারেতে থাকা ভ্যালুর সংখ্যার সর্বোচ্চ ইনডেক্সের বেশী না হয়। ধরুন অ্যারেতে ০ থেকে ৪ ইনডেক্সে ৫ টি ভ্যালু রয়েছে। কিন্তু আপনি ৫ নম্বর ইনডেক্স তথা ৬ষ্ঠ ভ্যালু চাচ্ছেন। সেক্ষেত্রে “Array out of bound” টাইপের এরর হতে পারে।

একই ভাবে সাবস্ক্রিপ্ট সিনট্যাক্স দিয়ে কোন নির্দিষ্ট ইনডেক্স বা কোন রেন্জের মধ্যকার সকল ইনডেক্সের ভ্যালু আপডেট বা মডিফিকেশন করা যায়।

menuItems[4] = "Onion" // "Flour" is replaced by "Onion"
menuItems[1..3] = ["Bananas", "Apples"]
// Potatoes and Chilis are replaced by Bananas and Apples

এখানে, দ্বিতীয় উদাহরনটিতে 1..3 মানে 1 থেকে 3 রেন্জের (শেষ ইনডেক্স, এক্ষেত্রে ৩য় ইনডেক্স কন্সিডার হয় না) সকল ইনডেক্সের ভ্যালুকে একটি অ্যারে দিয়ে রিপ্লেস করা হচ্ছে যাতে দুটি ভ্যালু রয়েছে। অর্থাৎ সাবস্ক্রিপ্ট সিনট্যাক্স ব্যবহার করে যেকোন সংখ্যক ইনডেক্সের ভ্যালু অন্য যেকোন সংখ্যক ভ্যালু দিয়ে রিপ্লেস করা যাবে।
উল্লেখযোগ্য কথা হল সাবস্ক্রিপ্ট সিনট্যাক্স (স্কয়ার ব্র্যাকেটের ভিতর অ্যারে ইনডেক্স পাঠানো) ব্যবহার করে অ্যারের ভ্যালু রিড করা বা নতুন ভ্যালু দিয়ে রিপ্লেস করা যায় ঠিকই কিন্তু নতুন কোন ভ্যালু অ্যারের শেষে ইনসার্ট (Add/ Append/ Insert) করা যায় না।

var recipes = ["Chicken Resala", "Mutton Curry"]
recipes[2] = "another recipe"   // Throws run time error because index 2 doesn't have value
println(recipes[2])      // Throws run time error because index 2 doesn't have value

recipes অ্যারেতে ০, ১ ইনডেক্সে মোট দুটি ভ্যালু রয়েছে। যদি ইনডেক্স ২ এর ভ্যালু এক্সেস করার বা নতুন ভ্যালু সেট করার চেষ্টা করা হয় তাহলে রান টাইম এরর হয়। অর্থাৎ সাকসেসফুল কম্পাইলেশনের পর কোড রান করলে যখনই মেশিন recipes অ্যারের ২ নম্বর ইনডেক্সের ভ্যালু এক্সেস করতে চাইবে তখনি এরর হবে কারন এই অ্যারেতে ২ নম্বর ইনডেক্স নেই।

কোন নির্দিষ্ট ইনডেক্সে নতুন ভ্যালু ইনসার্ট করার জন্য রয়েছে অ্যারের Insert মেথড। Insert মেথডে দুটি আরগুমেন্ট পাঠানো হয়। প্রথমে নতুন ভ্যালু ও তারপর যে ইনডেক্সে ইনসার্ট করতে হবে তার ভ্যালু।

menuItems.insert("Maple Syrup", atIndex: 2)

menuItems অ্যারের ২ নম্বর ইনডেক্সে Maple Syrup ইনসার্ট হয়ে গেছে। পরবর্তী সকল ভ্যালুর ইনডেক্স ১ করে বেড়ে গেছে। অর্থাৎ এই ইনসার্ট অপারেশন চালানোর পুর্বে যে ভ্যালুটি ইনডেক্স ২ তে ছিল এখন সেটি ইনডেক্স ৩ এ আছে।

নিচের মত করে removeAtIndex() মেথড দিয়ে অ্যারের যেকোন ইনডেক্সের ভ্যালু রিমুভ করা যায়। সেক্ষেত্রে রিমুভ করার পর ওই ইনডেক্সের পরবর্তী সকল ভ্যালুর ইনডেক্স ১ করে কমে যাবে।

menuItems.removeAtIndex(2)

ইনডেক্স ২ এর ভ্যালু রিমুভ হয়ে পরবর্তীতে থাকা সকল ভ্যালুর ইনডেক্স ১ করে কমে যাবে।

removeLast() মেথড দিয়ে সহজেই যেকোন অ্যারের শেষ ভ্যালুটি রিমুভ করা যায়। ফলে অ্যারের count ও ১ কমে যায়।

menuItems.removeLast() // removes the last element

for-in লুপ ব্যবহার করে অ্যারের সবগুলো ইনডেক্স ইটেরেট করা যায়। নিচের উদাহরনটি দেখা যাকঃ

 for item in menuItems
 {   
    println(item)
 }

অ্যারে ইনিশিয়ালাইজেশনঃ
শুরুতে কোন ভ্যালু ছাড়াই ফাঁকা অ্যারে ডিক্লেয়ার করার জন্য অ্যারের ডাটাটাইপ লিখে তারপর স্কয়ার ব্র্যাকেট লিখতে হয় নিচের মত করে।

var someInts = Int[]()
println("someInts is of type Int[] with \(someInts.count) items.")

সুইফ্ট এর Array তে রয়েছে এমন একটি ইনিশিয়ালাইজার যা দিয়ে একটি নির্দিষ্ট সাইজের অ্যারে ডিক্লেয়ার করা যাবে এবং প্রতিটি ইনডেক্সে একটি ডিফল্ট ভ্যালু সেট করা যাবে। উদাহরনস্বরুপ ধরুন আপনি চাচ্ছেন এমন একটি অ্যারে ডিক্লেয়ার করতে যার সাইজ (count) হবে ১০০ (ইনডেক্স 0 – 99) এবং প্রতিটি ইনডেক্সের ডিফল্ট ভ্যালু হবে 0.0 (repeatedValue)।

var hundredDoubles = Double[](count: 100, repeatedValue: 0.0)
// hundredDoubles is of type Double[], and equals [0.0, 0.0, 0.0, ...]

hundredDoubles অ্যারেটি ডিক্লেয়ার করার সময় ব্র্যাকেটের মধ্যে দুটি আর্গুমেন্ট পাঠানো হয়। প্রথমটি count যা অ্যারের সাইজ কত হবে তা ডিফাইন করে। দ্বিতীয়টি repeatedValue যা অ্যারের সকল ইনডেক্সের ডিফল্ট ভ্যালু কত হবে তা সেট করে। আবার Double[] লিখে অ্যারের ভ্যালুগুলোর ডাটাটাইপ কি হবে তাও বলে দেওয়া হয়েছে।
সুইফ্ট এর টাইপ ইনফারেন্স সুবিধার জন্য ডাটাটাইপ না বলে দিয়ে নিচের মত করে অ্যারে ইনিশিয়ালাইজ করা যায়।

var anotherThreeDoubles = Array(count: 3, repeatedValue: 2.5)
// anotherThreeDoubles is inferred as Double[], and equals [2.5, 2.5, 2.5]

একথা বলাই বাহুল্য যে দুটি অ্যারে কে “+” অপারেটর দিয়ে যোগ করলে প্রথম অ্যারের শেষে দ্বিতীয় অ্যারেটি অ্যাপেন্ড হয়।

var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles is inferred as Double[], and equals [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]

ডিকশনারী (Dictionaries) ঃ
অ্যারের মতই ডিকশনারীতে একই টাইপের একাধিক ভ্যালু স্টোর করা যায়। প্রত্যেকটি ভ্যালুর জন্য একটি করে ইউনিক কি (key) বা আইডেন্টিফায়ার থাকে। অ্যারেতে ০ থেকে শুরু করে ১, ২, ৩, … ইনডেক্স গুলোতে ভ্যালু গুলো থাকে কিন্তু ডিকশনারীতে ভ্যালুগুলো অ্যারের মত ইনডেক্সিং করে সাজানো থাকে না। ঠিক বাস্তব ডিকশনারী তে যেমন নির্দিষ্ট শব্দের জন্য নির্দিষ্ট সংজ্ঞা থাকে তেমনি সুইফ্ট এর ডিকশনারীতে প্রত্যেকটি ভ্যালু নিজের ইউনিক আইডেন্টিফায়ার দিয়ে স্টোর করা থাকে। এই আইডেন্টিফায়ার বা ভ্যালু উভয়টি যেকোন টাইপের হতে পারে। তবে একটি ডিকশনারীর সকল ভ্যালুর টাইপ একই হবে এবং সকল আইডেন্টিফায়ারের টাইপ একই হবে।

অবজেকটিভ-সি এর NSDictionary ও NSMutableDictionary তে একই ডিকশনারীতে বিভিন্ন টাইপের ভ্যালু বা আইডেন্টিফায়ার (key) থাকতে পারে। ফলে ডিকশনারীর টাইপ নির্দিষ্ট থাকে না। অন্যদিকে যেহেতু সুইফ্টের ডিকশনারীর সকল ইলিমেন্ট এর টাইপ একই হয় তাই ডিকশনারীর ভ্যালুর টাইপ সম্পর্কে নিশ্চিন্ত থাকা যায়।

ডিকশনারী (Dictionaries) ডিক্লেয়ারেশন ঃ
সুইফ্টে ডিকশনারী টাইপ ডিক্লেয়ার করার জন্য Dictionary লিখতে হয়। এখানে KeyType হল সেই আইডেন্টিফায়ার বা কি (key) এর টাইপ এবং ValueType হল ডিকশনারীতে স্টোর করা ভ্যালুর টাইপ। যেমনঃ

var airports: Dictionary<String, String> = ["TYO": "Tokyo", "DUB": "Dublin"]

ডিকশনারী ইনিশিয়ালাইজ করার জন্য অ্যারের মতই শর্টহ্যান্ড সিনট্যাক্স ব্যবহার করা হয়। অর্থাৎ ভ্যারিয়েবল ডিক্লেয়ার করার সময় স্কয়ার ব্র্যাকেটের ভিতর ভ্যালুগুলো লিখে দেওয়া। কিন্তু ডিকশনারী তে যেহেতু অ্যারের মত ইনডেক্সিং হয় না, তাই এখানে কি-ভ্যালু পেয়ার (key-value pairs) দিয়ে ডিকশনারী ইনিশিয়ালাইজ করতে হয়। উপরের উদাহরনটিতে ডিকশনারীর ইলিমেন্টগুলো কমা (,) দিয়ে লেখা হয়েছে। এবং প্রত্যেক ইলিমেন্ট এর কোলন (:) দিয়ে কি-ভ্যালু পেয়ার লেখা হয়েছে। এখানে কোলনের ডানদিকের অংশটি হল ভ্যালু ও বামদিকের অংশটি হল এই ভ্যালুর জন্য ইউনিক আইডেন্টিফায়ার বা কি (key)।

নোট ঃ আমরা বার বার বলছি যে ডিকশনারীর কি (key) গুলো অবশ্যই ইউনিক হতে হবে। তা না হলে একই কি(key) এর জন্য যদি একাধিক ভ্যালু স্টোর করা হয় তাহলে তা Ambiguity তৈরী করবে। এজন্যই সুইফ্টে কি(key) এর জন্য এমন ডাটাটাইপ ব্যবহার করতে হবে যেগুলোকে হ্যাশ করা যায়। String, Int, Double, Bool ইত্যাদি ডাটাটাইপ বাই-ডিফল্ট হ্যাশ করা যায়। তাই এসবগুলো ডাটাটাইপই ডিকশনারীর কি(key) হিসেবে ব্যবহার করা যাবে।

ডিকশনারী ম্যানিপুলেশন (এক্সেস ও মডিফিকেশন)ঃ
ডিকশনারীর ভ্যালুগুলো পড়া (Access) বা আপডেট (Modification) করার জন্য সুইফ্ট ল্যাঙ্গুয়েজের ডিকশনারী টাইপ ভ্যারিয়েবলের জন্য আছে কিছু প্রোপার্টি, মেথড। এসব প্রোপার্টি বা মেথড দিয়ে অথবা সাবস্ক্রিপ্ট দিয়ে ডিকশনারী এক্সেস বা মডিফাই করা যায়। নিচে কিছু উদাহরন দিয়ে বিস্তারিত বর্ননা করা হলঃ

কোন ডিকশনারীতে কতগুলো ভ্যালু আছে তা জানা যাবে রিড-অনলি প্রোপার্টি count দিয়ে।

var airports: Dictionary<String, String> = ["TYO": "Tokyo", "DUB": "Dublin"]
println("airports array has \(menuItems.count) items.") // airports dictionary has 2 items. 

প্রথমে airports ডিকশনারীটি 2 টি স্ট্রিং টাইপ ভ্যালু দিয়ে ইনিশিয়ালাইজ করা হয়েছে যাদের কি-ভ্যালু পেয়ার হল String : String। তারপর airports.count প্রোপার্টিতে ডিকশনারীতে কয়টি ভ্যালু আছে তা রিটার্ন করে। তাই airports.count 2 রিটার্ন করবে।

সাবস্ক্রিপ্ট সিনট্যাক্স দিয়ে সহজেই ডিকশনারীতে নতুন ভ্যালু ইনসার্ট বা আগের কোন ভ্যালু আপডেট করা হয়। সাবস্ক্রিপ্ট এ ইনডেক্স হিসেবে কি (key) লিখে তাতে নতুন ভ্যালু অ্যাসাইন করলে প্রথমেই চেক করা হয় যে এই কি (key) দিয়ে কোন ভ্যালু ডিকশনারীতে আছে কিনা। যদি থাকে তাহলে নতুন ভ্যালু দিয়ে আপডেট হবে। আর যদি না থাকে তাহলে নতুন ইলিমেন্ট টি ডিকশনারীতে ইনসার্ট হবে।

airports["LHR"] = "London"  // new item with "LHR" : "London" key-pair is inserted
// the airports dictionary now contains 3 items

airports["LHR"] = "London Heathrow" // existing item of key "LHR" is updated by ""London Heathrow""
// the value for "LHR" has been changed to "London Heathrow"

সাবস্ক্রিপ্ট সিনট্যাক্স ব্যবহার না করে ডিকশনারীর updateValue(forKey:) মেথড দিয়ে কোন নির্দিষ্ট কি (key) এর ভ্যালু আপডেট করা যায় আবার নতুন কি-পেয়ার ভ্যালু ইনসার্ট করা যায়।

if let oldValue = airports.updateValue("Dublin International", forKey: "DUB") {
    println("The old value for DUB was \(oldValue).")
}
// prints "The old value for DUB was Dublin."

উপরের কোডটিতে দেখা যাচ্ছে updateValue মেথডে দুটি আর্গুমেন্ট পাঠানো হচ্ছে। প্রথমটিতে ভ্যালু এবং দ্বিতীয়টিতে কি(key)। এখানে ভ্যালু হিসেবে “Dublin International” এবং forKey:”DUB” এ কি (key) হিসেবে “DUB” পাঠানো হচ্ছে। প্রথমে চেক করা হচ্ছে যে DUB কি দিয়ে কোন ইলিমেন্ট ডিকশনারীতে আছে কিনা। যেহেতু airports ডিকশনারীতে এই কি দিয়ে একটি ভ্যালু আছে তাই এই কি এর পুর্বের ভ্যালুটি নতুন পাঠানো Dublin International দিয়ে আপডেট হয়ে গেছে এবং পুর্বের ভ্যালুটি রিটার্ন করে যা oldValue ভ্যারিয়েবলে অ্যাসাইন হয়। যদি airports ডিকশনারীতে এই কি দিয়ে কোন ভ্যালু না থাকত, তাহলে নতুন ইলিমেন্ট হিসেবে “DUB” : “Dublin International” কি-পেয়ার ডিকশনারীতে যুক্ত হত।

সাবস্ক্রিপ্ট সিনট্যাক্সে স্কয়ার ব্র্যাকেটের মধ্যে কি(key) লিখে ওই কি এর ভ্যালু রিড বা এক্সেস করা যায়।

var airport = airports["DUB"]
println(airport) // Dublin International

কোন নির্দিষ্ট কি এর ভ্যালু রিমুভ করতে চাইলে তার সাবস্ক্রিপ্ট এ nil অ্যাসাইন করলেই তা ডিকশনারী থেকে রিমুভ হয়ে যাবে।

  airports["DHK"] = "Shahjalal International Airport" 
// airports contains 3 items right now.

  airports["DHK"] = nil
// Value for "DHK" key is removed from airports and this dictionary contains two items

আবার সাবস্ক্রিপ্ট ব্যবহার না করে removeValueForKey() মেথড দিয়ে কোন নির্দিষ্ট কি-ভ্যালু পেয়ার ডিকশনারী থেকে রিমুভ করা যায়। এক্ষেত্রে মেথডটি রিমুভ হওয়া ভ্যালুটি রিটার্ন করে।

if let removedValue = airports.removeValueForKey("DUB") {
    println("The removed airport's name is \(removedValue).")
} else {
    println("The airports dictionary does not contain a value for DUB.")
}
// prints "The removed airport's name is Dublin International."

অ্যারের মতই for-in লুপ দিয়ে ডিকশনারীতে থাকা সকল ভ্যালুতে ইটেরেট করা যায়। ডিকশনারী তে কি-ভ্যালু পেয়ার হিসেবে ভ্যালু থাকে তাই এখানে অ্যারের মতই একই সাথে কি এবং ভ্যালু এক্সেস করা যায়।

for (airportCode, airportName) in airports {
    println("\(airportCode): \(airportName)")
}
// TYO: Tokyo
// LHR: London Heathrow

আবার চাইলে শুধু keys অথবা শুধু values প্রোপার্টি ইটেরেট করে শুধু কি বা শুধু ভ্যালুগুলো এক্সেস করা যায়।

for airportCode in airports.keys {
    println("Airport code: \(airportCode)")
}
// Airport code: TYO
// Airport code: LHR
 
for airportName in airports.values {
    println("Airport name: \(airportName)")
}
// Airport name: Tokyo
// Airport name: London Heathrow

একটি ফাঁকা ডিকশনারী (Empty Dictionary) ইনিশিয়ালাইজ করা ঃ
ইনিশিয়ালাইজার সিনট্যাক্স ব্যবহার করে অ্যারের মতই ফাকা ডিকশনারী ইনিশিয়ালাইজ করা যায়।

var namesOfIntegers = Dictionary<Int, String>()
// namesOfIntegers is an empty Dictionary<Int, String>

উদাহরনটিতে nameofIntegers নামের একটি ডিকশনারী ইনিশিয়ালাইজ করা হয়েছে যার কি গুলো Integer টাইপ, কিন্তু ভ্যালুগুলো হবে String টাইপ।

কোন ডিকশনারী ইনিশিয়ালাইজ করার সময় যদি টাইপ বলে দেওয়া হয় অথবা কোন ভ্যালু ইনিশিয়ালাইজ করার ফলে টাইপ ইনফারেন্স ঘটে তাহলে ডিকশনারী টি ফাঁকা করলেও টাইপ পরিবর্তন হয় না। এই বৈশিষ্ট্য অ্যারের ক্ষেত্রেও প্রযোজ্য।

namesOfIntegers[16] = "sixteen"
// namesOfIntegers now contains 1 key-value pair
namesOfIntegers = [:]
// namesOfIntegers is once again an empty dictionary of type Int, String

কোন ডিকশনারী তে [:] এভাবে স্কয়ার ব্র্যাকেটের ভিতর শুধু কোলন (:) দিয়ে অ্যাসাইন করলে তা ফাকা ডিকশনারীতে পরিনত হয়।

ইনুম্যারেশন ও ক্লোজার (Enumerations and Closures) ঃ
ইনুম্যারেশন দিয়ে একই ধরনের একগুচ্ছ ভ্যালু টাইপকে প্রকাশ করা হয়। আপনি যদি সি/সি++ এর সাথে পরিচিত থাকেন তাহলে নিশ্চয়ই জানেন যে, সি তে একটি এনাম (enum) টাইপ গ্রুপের সকল ভ্যারিয়েবলকে ইনটিজার(Integer) ভ্যালু দিয়ে অ্যাসাইন করা হয়। সুইফ্ট এ ইনুম্যারেশন অনেক বেশী ফ্লেক্সিবল এবং সি এর মত গ্রুপের ভ্যালুগুলো শুধু ইনটিজার ভ্যালু হয় না। যেকোন ডাটাটাইপ যেমন String, Character, Integer বা Float টাইপ হতে পারে।

enum CompassPoint {
    case North
    case South
    case East
    case West
}

সব ল্যাঙ্গুয়েজেই enum কিওয়ার্ড দিয়ে ইনুম্যারেশন টাইপ ডিক্লেয়ার করা হয়। উপরের উদাহরনটিতে CompassPoint নামে একটি enum টাইপ ডিক্লেয়ার করা হয়েছে যার চারটি সদস্য (member) যথাক্রমে North, South, East এবং West রয়েছে। সি/সি++ বা অন্য যেকোন ল্যাঙ্গুয়েজে enum টাইপের প্রত্যেকটি সদস্য (member) এ ০ থেকে ১, ২, ৩, … integer ভ্যালু অ্যাসাইন হয়। কিন্তু সুইফ্ট এর enum টাইপ এর member দের ভ্যালুতে এরকম কোন ডিফল্ট ভ্যালু অ্যাসাইন করা হয় না।

কোন নির্দিষ্ট ফাংশনালিটি সহ একটি কোড ব্লককে ক্লোজার (Closure) বলা হয় ({ })। সুইফ্টের ক্লোজার ঠিক অবজেকটিভ-সি এর ব্লক (Block) এর মতই কাজ করে। নিচে ক্লোজারের একটি জেনারেল ফর্ম লেখা হল।

{ (param: Type) -> ReturnType in
    expression_using_params
}

উপরের “->” চিহ্নটি আর্গুমেন্ট ও রিটার্ন টাইপকে আলাদা করে এবং “in” ক্লোজার এর হেডার থেকে ক্লোজার এর বডি আলাদা করে। নিচে একটি উদাহরণ,

var numbers = [1, 2, 3, 4, 5]
numbers.map({
    (number: Int) -> Int in
    let result = 3 * number
    return result
    })

যদি টাইপ জানা থাকে (যেমন উপরে), তাহলে নিচের মত করেও এই ক্লোজারটি ব্যবহার করা যেতে পারে,

var numbers = [1, 2, 6]
numbers = numbers.map({ number in 3 * number })
println(numbers) // [3, 6, 18]

পরিসমাপ্তিঃ
এ অধ্যায়ে কালেকশন টাইপ নিয়ে বিস্তারিত আলোচনা হয়েছে। আমরা দেখেছি কিভাবে অ্যারে ও ডিকশনারী ইনিশিয়ালাইজ করা ও ম্যানিপুলেট করতে হয়। এছাড়া জেনেছি অ্যারে ও ডিকশনারীর মধ্যকার পার্থক্য ও সুবিধা-অসুবিধা। এছাড়া ইনুম্যারেশন ও ক্লোজার সম্পর্কে নামমাত্র আলোচনা হয়েছে। এ অধ্যায় সম্পর্কিত আরও বিস্তারিত থাকবে আমাদের কাগুজে বইয়ে।

বইয়ের আপডেট পেতে চোখ রাখুন আমাদের ফ্যান পেজে

পরের চাপ্টারঃ 
পরের চাপ্টারে Branching ও Lopping নিয়ে বিস্তারিত থাকবে। ওই অধ্যায়ে if-else, switch, for-in ইত্যাদি নিয়ে বিস্তারিত আলোচনা থাকবে।

২-২ঃ iOS অ্যাপে ব্যাসিক ইনপুট আউটপুট ও কিবোর্ড হ্যান্ডেলিং

Standard

এই সিরিজের পুরো পোস্ট লিস্ট এবং সম্ভাব্য কাগুজে বই এর সূচি দেখতে ক্লিক করুনbangla-ios-objective-c-swift-nuhil

আগের চ্যাপ্টারঃ  ২-১ঃ টুলস সেটআপ এবং একটি সাধারণ হ্যালো ওয়ার্ল্ড অ্যাপ তৈরি

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

প্রোজেক্ট ও ইউজার ইন্টারফেস তৈরিঃ
প্রথমে Xcode ওপেন করে একটি নতুন প্রোজেক্ট তৈরি করুন File -> New -> Project… এ ক্লিক করে। এক্ষেত্রে টাইপ হিসেবে সিলেক্ট করুন iOS -> Application -> Single View Application. নিচের মত করে,
2-2-1
তারপরের স্ক্রিনে এভাবে,
2-2-2

প্রোজেক্ট তৈরি হবার পর Xcode এর বাম পাশ থেকে Main.storyboard ফাইলটি সিলেক্ট করুন। ডান পাশে একটিই মাত্র ভিউ ফাইল দেখা যাবে যেহেতু আমাদের অ্যাপ এর টাইপ সিঙ্গেল ভিউ। এরপর Xcode এর ডান পাশের নিচের ইন্টারফেস বিল্ডার প্যানেল থেকে মেইন ভিউ ফাইল বা স্ক্রিনের উপর একটি Label টাইপ এলিমেন্ট নিন,
Screen Shot 2014-06-19 at 2.11.20 AM
এবং সেটা সিলেক্ট থাকা অবস্থায় Xcode এর ডান পাশের Attribute Inspector ব্যবহার করে সেটার বিভিন্ন ভিজুয়াল অ্যাপেয়ারেন্স পরিবর্তন করে নিন ইচ্ছা মত।
Screen Shot 2014-06-19 at 2.12.25 AM
এরপর আমাদের একমাত্র ভিউ এর উপর একটি Text Field টাইপ এলিমেন্ট নিন এবং সেটারও বিভিন্ন প্রোপার্টি যেমন সাইজ, ফন্ট ইত্যাদি পরিবর্তন করে নিন ডান পাশের Attribute Inspector ব্যবহার করে। এভাবে ঠিক নিচের ছবির মত একটি লে-আউট তৈরি করে নিন। ধরে নিচ্ছি উপরে বলা কথা গুলো বুঝতে পেরেছেন। না বুঝলে এই সেকশনের ১ম চ্যাপ্টার ঘুরে আসুন
2-2-3

আউটলেট তৈরিঃ
আমাদের ইউজার ইন্টারফেস ডিজাইন হয়ে গেছে দুটি ইউআই UI এলিমেন্ট দিয়ে। এখন এগুলোর জন্য আউটলেট তৈরি করতে হবে যাতে করে আমাদের View Controller কোডের মধ্যে থেকে এগুলোর রেফারেন্স পাওয়া যায়। এজন্য প্রথমে Xcode এর ডান দিকের উপর পাশ থেকে Assistant Editor বাটনটি এনাবেল করে নিন (নিচের ছবিতে 1 চিহ্নিত)। এনাবেল হলে ভিউ ফাইলের পাশেই আরও একটি এরিয়া তৈরি হবে যেখানে ViewController.h ফাইলটি ওপেন অবস্থায় থাকার কথা। ওই ফাইল ওপেন না থাকলে নিচের ছবিতে 2 চিহ্নিত জায়গাটায় ক্লিক করে ViewController.h ফাইলকে ওখানে ওপেন করতে পারেন। এখন কিবোর্ডের Control কি চেপে ধরে আমাদের ভিউ ফাইলের টেক্সট ফিল্ডের উপর থেকে মাউস ক্লিক চেপে ধরে ডান পাশের ফাইলের @interface এর নিচে যেকোনো জায়গায় ছেড়ে দিন। নিচের মত একটি ছোট পপ-আপ আসবে যেখানে এটার প্রোপার্টি টাইপ, নাম ইত্যাদি ঠিক করে দিতে পারবেন।
2-2-5

এভাবে সুইচ এলিমেন্ট, ম্যাসেজ দেখানোর লেবেল এবং বাটনটির প্রোপার্টিও ঠিক করে দিন নিচের কোডের মত,

// ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
@property (strong, nonatomic) IBOutlet UITextField *courseTitle;
@property (strong, nonatomic) IBOutlet UISwitch *continueCourse;
@property (strong, nonatomic) IBOutlet UILabel *messageBox;
@property (strong, nonatomic) IBOutlet UIButton *showButton;

@end

কিবোর্ড হ্যান্ডেলিংঃ
এখন পর্যন্ত ইন্টারফেস এলিমেন্ট গুলো এবং তাদের আউটলেট গুলো রেডি। এই অবস্থায় অ্যাপটি রান করে দেখতে পারেন। যদি সব ঠিক ঠাক ঠাকে তাহলে টেক্সট ফিল্ডটিতে কিছু লেখার জন্য ট্যাপ/ক্লিক করলে সিমুলেটরের কিবোর্ডটী চলে আসবে এবং সেটা ব্যবহার করে কিছু লিখতে পারবেন। কিন্তু লেখা শেষে দেখবেন কিবোর্ডটী সিমুলেটর থেকেই যাচ্ছে। আড়ালে চলে যাচ্ছে না।
এখন আমরা কিবোর্ড হ্যান্ডেলিং এর এই সমস্যার একটা সমাধান করবো। এ জন্য প্রথমে ViewController.h ফাইলে textFieldReturn: নামের একটি ফাংশন ডিক্লেয়ার করবো। মূলত এই ফাংশনের মাধ্যমে আমরা কিবোর্ড এর রিটার্ন কি চেপে কিবোর্ডকে হাইড করে ফেলার একটা উপায় প্রয়োগ করবো।

// ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
@property (strong, nonatomic) IBOutlet UITextField *courseTitle;
@property (strong, nonatomic) IBOutlet UISwitch *continueCourse;
@property (strong, nonatomic) IBOutlet UILabel *messageBox;
@property (strong, nonatomic) IBOutlet UIButton *showButton;

-(IBAction)textFieldReturn:(id)sender;

@end

এখন এই ফাংশনটির ইমপ্লিমেন্টেশন লিখবো ViewController.m ফাইলে নিচের মত করে,

// ViewController.m

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
}

-(IBAction)textFieldReturn:(id)sender
{
    [sender resignFirstResponder];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

উপরের textFieldReturn: মেথডের মধ্যে থেকে আমরা যা করছি তা হল, ইভেন্টটি যে অবজেক্ট দারা ট্রিগারড হয়েছে সেই অবজেক্টের resignFirstResponder মেথডকে কল করছি। First Responder হচ্ছে সেই অবজেক্ট যেটা এই মুহূর্তে ইউজারের সাথে ইন্টারঅ্যাক্ট করছে, এ ক্ষেত্রে কিবোর্ডটাই ফার্স্ট রেস্পন্ডার।

এখন textFieldReturn: মেথডটি যাতে সঠিক সময় কল হয় সেজন্য কিছু কাজ করতে হবে। ভিউ কন্ট্রোলার ফাইলের টেক্সট ফিল্ডটিকে সিলেক্ট করুন এবং Xcode এর ডান পাশ থেকে Connection Inspector সিলেক্ট করুন। এরপর Did End on Exit নামের সার্কেল থেকে মাউস ক্লিক করে টেনে এনে ভিউ ফাইলের নিচের View Controller আইকনের উপর ছেড়ে দিন এবং সেখান থেকে textFieldReturn সিলেক্ট করুন। নিচের ছবির মত করে,
Screen Shot 2014-06-19 at 2.20.22 AM

এ অবস্থায় সব সেভ করে যদি অ্যাপটি রান করেন এবং টেক্সট ফিল্ডে কিছু লিখে কিবোর্ডের Return বাটনে ক্লিক/ট্যাপ করেন তাহলে কিবোর্ডটি হাইড হয়ে যাবে, যা আমরা করতে চাচ্ছিলাম।
আরও একটা সিচুয়েশনে কিবোর্ড হাইড করা ভালো ইউজার এক্সপেরিএন্স যেমন, টেক্সট ফিল্ড বাদে ভিউ এর অন্য কোথাও ট্যাপ করলেও যাতে কিবোর্ডটি হাইড হয়ে যায়। চলুন সেই ব্যবস্থা করি।
এর জন্য আমরা touchesBegan: নামক ইভেন্ট হ্যান্ডেলার মেথডটি ইমপ্লিমেন্ট করবো অর্থাৎ স্ক্রিনে টাচ হলেই এটি সক্রিয় হবে। কিন্তু এই মেথডের মধ্যে আবার এটিও চেক করতে হবে যাতে কেবল মাত্র আমাদের টেক্সট ফিল্ড বাদে অন্য কোথাও টাচ হলেই কিবোর্ড হাইডের মেথড কল করতে পারি। নিচের মত করে মেথডটি ViewController.m ফাইলে লিখে ফেলুন। তাহলে আপডেটেড ফাইলটি হল,

// ViewController.m

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
}

-(IBAction)textFieldReturn:(id)sender
{
    [sender resignFirstResponder];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    UITouch *touch = [[event allTouches] anyObject];
    if ([self.courseTitle isFirstResponder] && [touch view] != self.courseTitle) {
        [self.courseTitle resignFirstResponder];
    }
    [super touchesBegan:touches withEvent:event];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

এখন আবার অ্যাপটি রান করুন এবং টেক্সট ফিল্ডে ক্লিক করুন। স্বভাবতই কিবোর্ড চলে আসবে। এখন হয় কিবোর্ডের return বাটন চাপুন নয়ত স্ক্রিনের যেকোনো জায়গায় ক্লিক/ট্যাপ করুন, কিবোর্ড হাইড হয়ে যাবে।

ইনপুট আউটপুটঃ
ওকে, এবার আসুন ডাটা ইনপুট এবং সেটা স্ক্রিনে আউটপুটের ব্যবস্থা করা যাক। ViewController.h ফাইলে নতুন একটি ফাংশন ডিক্লেয়ার করুন যাতে আপডেটেড ফাইলটি দেখতে নিচের মত হয়,

// ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
@property (strong, nonatomic) IBOutlet UITextField *courseTitle;
@property (strong, nonatomic) IBOutlet UISwitch *continueCourse;
@property (strong, nonatomic) IBOutlet UILabel *messageBox;
@property (strong, nonatomic) IBOutlet UIButton *showButton;

-(IBAction)textFieldReturn:(id)sender;
-(IBAction)outputData;

@end

এবার এই outputData মেথডটির ইমপ্লিমেন্টেশন লিখে ফেলুন ViewController.m ফাইলে যাতে পুরো ফাইলটি দেখতে নিচের মত হয়,

// ViewController.m

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
}

-(IBAction)textFieldReturn:(id)sender
{
    [sender resignFirstResponder];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    UITouch *touch = [[event allTouches] anyObject];
    if ([self.courseTitle isFirstResponder] && [touch view] != self.courseTitle) {
        [self.courseTitle resignFirstResponder];
    }
    [super touchesBegan:touches withEvent:event];
}

- (IBAction)outputData {
    NSString *isCourseOngoing = (self.continueCourse.on)? @"Ongoing" : @"Not Ongoing";
    
    self.messageBox.text = [NSString stringWithFormat:@"%@ is %@", self.courseTitle.text, isCourseOngoing];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

এক্সট্রা টিপস (Apple ডকুমেন্টেশন ফলো করা) ঃ
উপরে খেয়াল করুন, outputData মেথডের মধ্যে প্রথমে আমরা isCourseOngoing -এ একটা স্ট্রিং ভ্যালু সেট করছি যেটা নির্ভর করছে continueCourse নামের আউটলেট প্রোপার্টির অর্থাৎ সুইচ এলিমেন্টটির বর্তমান অবস্থার উপর। এখন কথা হচ্ছে এই যে এখানে continueCourse.on লিখে ওই অবজেক্টটির স্ট্যাটাস অ্যাক্সেস করলাম সেটা হঠাৎ আমরা জেনে নিতে পারি কই থেকে?

খেয়াল করলে দেখবেন ওই অবজেক্টটি একটি UISwitch টাইপের। অর্থাৎ এর বিস্তারিত জানা যাবে UISwitch ক্লাসের রেফারেন্স ঘেঁটে দেখলেই। আপনি ঠিকি ধরেছেন, এরকম যেকোনো ক্লাসের রেফারেন্স দেখে নিয়ে সেটার অবজেক্টের উপর আমরা বিভিন্ন অপারেশন করতে পারি। যেমন এই ক্লাসের রেফারেন্স থেকে আমরা জানতে পারি এর কি কি প্রোপার্টি ও মেথড আছে এবং সেরকম একটা প্রোপার্টি হচ্ছে on প্রোপার্টি। এই লিঙ্কে গেলেই দেখতে পারবেন লেখা আছে নিচের মত,
Screen Shot 2014-06-19 at 1.25.56 AM

অর্থাৎ আমরা continueCourse.on এর মাধ্যমে ওই প্রোপার্টির ভ্যালু অ্যাক্সেস করে ঠিক করতে পারি সুইচটি কি অন নাকি অফ অবস্থায় আছে।
তার ঠিক পরেই আমরা messageBox লেবেল এর text প্রোপার্টি হিসেবে একটি ফরম্যাটেড স্ট্রিং সেট করছি; courseTitle এর text প্রোপার্টি এবং isCourseOngoing এর ভ্যালু মিলিয়ে।

এবার শেষ বারের মত অ্যাপটি রান করুন আর টেক্সট ফিল্ডে যেকোনো ভ্যালু এবং তার নিচের সুচটি অন/অফ করে Show Me Back বাটনে ক্লিক করে দেখুন বাটনের ঠিক উপরের জায়গায় আপনার মন মত আউটপুট দেখাচ্ছে কিনা।

বইয়ের আপডেট পেতে চোখ রাখুন আমাদের ফ্যান পেজে

পরের চ্যাপ্টারঃ
পরের চ্যাপ্টারে অটো লে-আউট নিয়ে বিস্তারিত আলোচনা থাকবে এবং তার উপর ভিত্তি করে একটি পুরো উদাহরণ থাকবে আর তার পর পরই আসবে টেবিল ভিউ নিয়ে চ্যাপ্টার।

৬-১ – অবজেক্টিভ-সি (Objective-C) নাকি সুইফ্ট (Swift) ?

Standard

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

ভূমিকাঃ
আপনি যদি Apple এর WWDC (Worldwide Developer’s Conference) সম্পর্কে মোটা মুটি অবগত থাকেন অথবা ইনফরমেশন টেকনোলজি সম্পর্কিত  আন্তর্জাতিক খবর গুলো খেয়াল করে থাকেন, তাহলে জেনে থাকবেন যে Apple তাদের WWDC 2014 ইভেন্টে সবচেয়ে চমকপ্রদ যে আবিষ্কারটির ঘোষণা দিয়েছে তা হচ্ছে তাদের তৈরি সম্পূর্ণ নতুন একটি প্রোগ্রামিং ল্যাঙ্গুয়েজের খবর। যার নাম Swift. তারা চায় তাদের ভবিষ্যৎ iOS এবং OSX অ্যাপ্লিকেশন গুলো এই ল্যাঙ্গুয়েজ দিয়েই ডেভেলপ করা হোক যাতে করে এই প্ল্যাটফর্মের অ্যাপ গুলোর পারফরমেন্স আরও ভালো হয়।
এটাকে তারা বলছে, দ্রুতগতি সম্পন্ন, আধুনিক, নিরাপদ ও ইন্টার‌অ্যাক্টিভ একটি ল্যাঙ্গুয়েজ। অন্যান্য ল্যাঙ্গুয়েজের মত অনেক অনেক জনপ্রিয় ফিচার এই ল্যাঙ্গুয়েজে যুক্ত আছে। এর ডিজাইন এমন ভাবে করা হয়েছে যাতে সিনট্যাক্স আরও সহজ হয় এবং iOS ও OSX ডেভেলপমেন্ট শুরু করতে নতুনদের বাধা আরও কম হয়। এমনকি আসছে সেপ্টেম্বর, ২০১৪ তে যে Xcode 6 লঞ্চ হতে যাচ্ছে তার সঙ্গে Playground নামের একটি ফিচার থাকছে যার মাধ্যমে বিভিন্ন কোড, প্রোগ্রামিং লজিক এবং ক্যালকুলেশনের লাইভ প্রিভিউ দেখা যাবে পুরো প্রোগ্রাম রান না করেই। অর্থাৎ Apple বরাবরই ডেভেলপার ফ্রেন্ডলি একটা ডেভেলপমেন্ট প্ল্যাটফর্ম দেয়ার ব্যাপারে সবসময় গুরুত্ব দিয়েছে যারই বহিঃপ্রকাশ হিসেবে Swift এর জন্ম বলতে পারেন। অতএব, ভয় না পেয়ে এর কাছ থাকে ভালো কিছুই আশা করতে পারেন নতুন এবং পুরনো iOS এবং OSX ডেভেলপারেরা।

আমি এই প্ল্যাটফর্মে নতুন, আমার কি এখন অব্জেক্টিভ-সি অথবা সুইফ্ট নাকি দুটো ল্যাঙ্গুয়েজ-ই শেখা উচিত?
প্রথমত, সুইফ্ট (Swift) একটি নতুন প্রোগ্রামিং ল্যাঙ্গুয়েজ, আর তাই এটাতে আরও নতুন নতুন ফিচার যুক্ত হওয়া থেকে শুরু করে বিভিন্ন বাগ ফিক্সিং চলতেই থাকবে সামনের অন্তত এক দুই বছর। আর তাই Apple এটার ব্যাপারে প্রচার চালিয়ে যাবে ঠিকই কিন্তু আপনাকে বাধ্য করবে না iOS এর অ্যাপ শুধুমাত্র Swift এ করার জন্য। আর অন্যদিকে অবজেক্টিভ-সি রাতারাতি বন্ধও হয়ে যাবে না।
দ্বিতীয়ত, ইতোমধ্যে Apple অ্যাপ স্টোরে ১০ লাখেরও বেশি অ্যাপ্লিকেশন আছে যেগুলো অবজেক্টভ-সি তে করা এবং ওয়েবে কয়েক লাখ জনপ্রিয় লাইব্রেরি, ফ্রেমওয়ার্ক ওপেন সোর্স টুলস ও প্রজেক্ট আছে যেগুলোও অবজেক্টিভ-সি তে ডেভেলপ করা। আর তাই এগুলোর এনহ্যান্সমেন্ট, বাগ ফিক্সিং এবং আপগ্রেড চলবে আরও অনেক দিন আর তার জন্য অবশ্যই অব্জেক্টিভ-সি তে অভিজ্ঞ ডেভেলপার বা প্রোগ্রামারের প্রয়োজন থাকছেই।
তৃতীয়ত, Swift এবং iOS 7,8 সাথে Xcode 6 এমন ভাবে প্রস্তুত আছে যে আপনি একটি প্রোজেক্টে একি সাথে অবজেক্টিভ-সি এবং সুইফ্ট ল্যাঙ্গুয়েজ ব্যবহার করতে পারেন কোন রকম বাড়তি ঝামেলা ছাড়াই। আর এই যুগপৎ বিদ্যমানতা এটাই প্রমাণ করে যে, সুইফ্ট একবারেই অবজেক্টিভ-সি এর জায়গা দখল করে নিচ্ছে না। আরও দেখতে পারেন এখানে

আর তাই, যদি আপনি কোন iOS ডেভেলপার কোম্পানিতে জয়েন করতে চান অথবা নিজে থেকেই এই মার্কেটে অ্যাপ লঞ্চ করতে চান আপনাকে দুটো ল্যাঙ্গুয়েজেই সম্যক ধারনা নেয়া খুবি গুরুত্বপূর্ণ।

আমি বেশকিছু দিন ধরেই অবজেক্টিভ-সি তে অ্যাপ ডেভেলপমেন্ট এর কাজ করে আসছি কিন্তু এখন কি আমি একজন কেবলই নতুন শিক্ষানবিস?
একদম না। চিন্তা করে দেখুন, আপনি ইতোমধ্যে Xcode, Cocoa এবং Cocoa Touch এর বিভিন্ন API এবং অবজেক্টিভ-সি তে অভিজ্ঞতা অর্জন করেছেন যার মাধ্যমে চলছে কয়েক লাখ অ্যাপ – তার তুলনায় Swift শেখা কিছুই না। বরং আপনি আপনার অভিজ্ঞতার ভাণ্ডারে নতুন একটি জিনিষ যুক্ত করতে যাচ্ছেন মাত্র। অন্যদের থেকে তার মানে আপনি সিংহ ভাগ এগিয়ে থাকছেনই সব সময়।

সুইফ্ট দিয়ে ডেভেলপমেন্টের সুবিধা কি?
Apple এর মতে এটা ৩০ বছর বয়সী Objectiv-C এর চেয়ে অনেকটাই আধুনিক। আর তাই এতে প্রোগ্রামারদের অনেক প্রিয় কিছু ফিচার যেমন namespacing, optionals, tuples, generics, type inference ইত্যাদি থাকছে যা অবশ্যই সফটওয়্যার ডেভেলপমেন্টকে আরও বেশি যুগোপযোগী আর গুনমান সম্পন্ন করবে।
অন্যদিকে এই ল্যাঙ্গুয়েজের অবজেক্ট সর্টিং, এক্সিকিউশন সহ আরও কিছু বিষয়ে টাইম কমপ্লেক্সিটি অনেক কম।

কোথায় শেখা শুরু করবো?
সবসময় নতুন কিছু শুরু করতে বা ওই বিষয়ে জানতে সেটার অফিসিয়াল সোর্স থেকেই দেখে নেয়া উচিত। যেমন নিচের সোর্স দুটি হতে পারে সঠিক দিক নির্দেশনাঃ

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

আমাদের ব্লগ পোস্ট গুলোর চেয়ে অনেক বেশি বিস্তারিত আলোচনা, বিশ্লেষণ এবং কোড এক্সাম্পল থাকবে প্রিন্টেড বইয়ে।

পরের চ্যাপ্টারঃ পরের চ্যাপ্টারে থাকবে একটি সাধারণ প্রশ্ন যেটা অনেকেরই মনে জমে থাকে, “iOS এবং OSX এর অ্যাপ ডেভেলপমেন্টের জন্য Macbook, iMac, Mac mini অর্থাৎ Apple গ্যাজেট বাধ্যতামূলক কিনা” এর উপর আলোচনা এবং কিছু বিশ্লেষণ ও অবশ্যই কিছু বিকল্প ব্যবস্থার কথা।

৯- অবজেক্টিভ-সি (Objective-C) তে ব্লক (Block) এর ব্যবহার

Standard

আগের চ্যাপ্টারঃ অবজেক্টিভ-সি এর ক্যাটাগরি (CATEGORY) এবং এর বিস্তারিত

ভূমিকাঃ
সহজভাবে বলতে গেলে ব্লক হচ্ছে অবজেক্টিভ-সি এর anonymous function. Google ডেফিনেশন অনুযায়ী অ্যানোনিমাস ফাংশন হচ্ছে-
An anonymous function is a function that is not stored in a program file, but is associated with a variable whose data type is function_handle. Anonymous functions can accept inputs and return outputs, just as standard functions do. However, they can contain only a single executable statement.
এর মাধ্যমে আপনি বিভিন্ন অবজেক্টের মধ্যে ইচ্ছামত স্টেটমেন্ট (Satement) পাস তথা আদান-প্রদান করতে পারবেন যেমনভাবে সাধারণ ডাটা আদান প্রদান করে থাকেন। উপরন্তু ব্লককে ক্লোজার (Closure) হিসেবে ব্যবহার করা হয় যাতে করে তার পক্ষে তার আসে পাশের ডাটা/অবস্থা নিয়ে কাজ করাও সম্ভব হয়।
আস্তে আস্তে আমরা এ ব্যপারে বিস্তারিত জানতে চেষ্টা করব।

সিনট্যাক্সঃ
ডিক্লেয়ারেশন-

returntype (^blockName) (argumentType);

ইমপ্লিমেন্টেশন-

returntype (^blockName) (argumentType) = ^{
    // Statements
};

এখানে “=” চিহ্নের বাম পাশের অংশটি হচ্ছে একটি ব্লক ভেরিয়েবল এবং ডান পাশের অংশ হচ্ছে ব্লকটি।

উদাহরণ-

void (^simpleBlock) (void) = ^{
    NSLog(@"This is a block that does not return anything and also has no argument!");
};

উপরের এই ব্লককে আমরা simpleBlock(); এভাবে কল করতে পারি।

ব্লক (Block) তৈরিঃ
ব্লক তৈরির ব্যাপারটা মোটা মুটি ফাংশন তৈরির মতই। যেভাবে আপনি একটি ফাংশন ডিক্লেয়ার করে থাকেন ঠিক সেভাবেই একটি ব্লক ভেরিয়েবল ডিক্লেয়ার করতে পারেন আবার যেভাবে একটি ফাংশন এর ইমপ্লিমেন্ট করেন সেভাবে একটি ব্লককে ডিফাইন করতে পারেন। এমনকি যেভাবে একটি ফাংশনকে কল করেন ঠিক সেভাবেই একটি ব্লককেও কল করতে পারেন।
নিচে একটি সহজ উদাহরণ দেখানো হল,

// main.m

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        // Declare and define the isPlaceOpen block
        BOOL (^isPlaceOpen)(void) = ^ {
            return YES;
        };

        // Use the block
        NSLog(@"Open state of the place is: %hhd ", isPlaceOpen());
    }
    return 0;
}

“^” চিহ্নটি ব্যবহার করে isPlaceOpen কে একটি ব্লক হিসেবে চিহ্নিত করা হয়। এটাকে অবজেক্টিভ-সি এর “*” পয়েন্টার চিহ্নের মতই মনে করতে পারেন অর্থাৎ শুধুমাত্র ডিক্লেয়ার করার সময় এটা গুরুত্বপূর্ণ কিন্তু এর পরে এটাকে সাধারণ ভেরিয়েবলের মতই মনে করে ব্যবহার করতে পারেন।
নিচে আরেকটি উদাহরণ দেখি যেখানে আমাদের ব্লকটি দুইটি double টাইপের প্যারামিটারও গ্রহণ করে এবং সেগুলো ব্যবহার করে অল্প কিছু হিসাব করার পর একটি double টাইপ ডাটা রিটার্ন করে।

// main.m

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        // Declare the block variable
        double (^priceAfterTax)(double rawPrice, double totalTax);

        // Create and assign the block to the variable
        priceAfterTax = ^(double price, double tax) {
            return price + tax;
        };

        // Call the block
        double total = priceAfterTax(400, 40);

        NSLog(@"Total price after considering tax is: " @"%.2f Tk.", total);
    }
    return 0;
}

খেয়াল করুন, প্রথমে আমরা একটি ব্লক ভেরিয়েবল ডিক্লেয়ার করেছি তারপর আসল ব্লকটিকে সেই ভেরিয়েবলে অ্যাসাইন করেছি এবং তারপর ভেরিয়েবলের নাম ধরে সেই ব্লককে ব্যবহার বা কল করেছি। প্রোগ্রামটি রান করালে নিচের মত আউটপুট আসবে।
Screen Shot 2014-06-11 at 1.20.13 PM

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

// main.m

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        NSString *foodGenre = @"Italian";

        NSString *(^getFullItemName)(NSString *) = ^(NSString *itemName) {
            return [foodGenre stringByAppendingFormat:@" %@", itemName];
        };

        NSLog(@"%@", getFullItemName(@"Pizza"));    // Honda Accord
    }
    return 0;
}

আবারও বলি, ফাংশনের মতই একটি ব্লকের মধ্যে থেকে আপনি সেটার লোকাল ভেরিয়েবলগুলো, তার কাছে যে প্যারামিটারগুলো পাস করা হয়েছে সেগুলো এবং যেকোনো গ্লোবাল ভেরিয়েবল অ্যাক্সেস করতে পারবেন। কিন্তু ক্লোজার হিসেবে ব্লকের ব্যবহারের ফলে আপনি কিছু নন-লোকাল ভেরিয়েবলকেও ব্লকের মধ্যে থেকে অ্যাক্সেস করতে পারবেন। নন-লোকাল ভেরিয়েবল হচ্ছে ব্লকের বাইরের কোন ভেরিয়েবল কিন্তু ব্লকের Lexical Scope এর আওতাভুক্ত। উপরের উদাহরণে foodGenre সেরকম একটি নন-লোকাল ভেরিয়েবল যেটাকে getFullItemName ব্লকের মধ্যে থেকেও ব্যবহার করা গেছে।

মেথড প্যারামিটার হিসেবে ব্লক ব্যবহারঃ
একটি পূর্ণ ব্লককে যেকোনো মেথডের প্যারামিটার হিসেবেও ব্যবহার করা যায়। ওই ব্লক যে কাজটা করে মূলত সেই কাজটিকেই একটা প্যাকেজ সরূপ একটি প্যারামিটার হিসেবে যেকোনো মেথডের কাছে দেয়া যায়। নিচে সেরকম একটা উদাহরণ দেখবো আমরা।
প্রথমে আপনার প্রজেক্টকে এমনভাবে সাজিয়ে নিন যাতে সেখানে Food.h, Food.m এবং main.m এই ৩টি ফাইল থাকে নিচের মত করে,

// Food.h

#import <Foundation/Foundation.h>

@interface Food : NSObject

- (void)performActionWithCompletion: (void (^) ()) completionBlock;

- (void)calculatePriceWithTax: (double (^) (double price)) taxCalculatorBlock;

@end
// Food.m

#import "Food.h"

@implementation Food

- (void)performActionWithCompletion:(void (^) ()) completionBlock {

    NSLog(@"Started cooking...");
    completionBlock();
}

- (void)calculatePriceWithTax: (double (^) (double price)) taxCalculatorBlock {
    NSLog(@"Total price is: %f", taxCalculatorBlock(400));
}

@end
// main.m

#import <Foundation/Foundation.h>
#import "Food.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        Food *process = [[Food alloc]init];

        [process performActionWithCompletion: ^{
            NSLog(@"Cooking is finished and this block has been called to intimate action is performed.");
        }];

        [process calculatePriceWithTax: ^(double totalPrice) {
            return totalPrice + 40;
        }];

    }
    return 0;
}

উপরের কোড গুলো দেখুন এবং বোঝার চেষ্টা করুন, প্রথমেই আমরা Food ক্লাসের ইন্টারফেসে দুইটি মেথড ডিক্লেয়ার করেছি যে দুটো মেথড প্যারামিটার হিসেবে দুই ধরনের দুইটি ব্লক গ্রহণ করে। প্রথম মেথডটির নাম performActionWithCompletion: এবং এটি এমন একটি ব্লককে প্যারামিটার হিসেবে গ্রহণ করে যে ব্লকটির কোন কিছু রিটার্ন করে না এবং যার কোন আর্গুমেন্টও নাই। কিন্তু দ্বিতীয় মেথডটি একটি ব্লককে তার প্যারামিটার হিসেবে গ্রহণ করে যে ব্লক একটি double টাইপ ডাটা রিটার্ন করে এবং যার একটি আর্গুমেন্টও আছে।
এখন এই দুইটি মেথড এর ইমপ্লিমেন্টেশন লিখেছি Food.m ফাইলে। অর্থাৎ এই দুইটি মেথড কি কাজ করবে তার ডেফিনেশন। প্রথম মেথডটি শুরুতেই একটি ম্যাসেজ প্রিন্ট করবে এবং তারপর ওর কাছে প্যারামিটার হিসেবে আসা ব্লকটিকে কল করবে। তো, এক্ষেত্রে কি ঘটবে? ওই completionBlock নামক ব্লকের মধ্যে যা করতে বলা হয়েছে তাই ঘটবে। অর্থাৎ ওখানে লিখে রাখা আরও একটি ম্যাসেজ প্রিন্ট হবে।
দ্বিতীয় মেথড এর কাজ হচ্ছে, তার কাছে আসা ব্লকটিকে কল করবে। কিন্তু যেহেতু ওই ব্লকটির প্যারামিটার আছে তাই ওকে কল করার সময় একটি ভ্যালুও পাস করে দিবে। আর ব্লক কল করা মানে কি? ওই ব্লকের মধ্যে যা করতে বলা হয়েছে তাই করবে। এক্ষেত্রে ওই ব্লকটি একটি ডাটা রিটার্ন করে এবং আসলে calculatePriceWithTax: মেথডটি সেই রিটার্ন করা ডাটাকেই নিজের মধ্যে থেকে প্রিন্ট করে।
main.m ফাইলে আমরা উপরে উল্লেখিত মেথড দুইটি কল করেছি এবং তাদের প্যারামিটার হিসেবে দুইটি ভিন্ন রকম ব্লক কে পাঠিয়ে দিয়েছি। প্রোগ্রামট রান করালে নিচের মত আউটপুট আসবে,
Screen Shot 2014-06-11 at 2.23.24 PM

ব্লক টাইপ ডিফাইন করাঃ
typedef এর মাধ্যমে একটি ব্লক টাইপ তৈরি করা যায় যাতে করে ব্লক ব্যবহারের সময় এর সিনট্যাক্স এলোমেলো না হয়ে যায় এবং ওই ব্লকটিকে আরও সহজ এবং সংক্ষেপ নামে ব্যবহার করা যায়। এ ব্যাপারে এবং উপরের টপিক গুলোর আরও বিস্তারিত থাকবে আমাদের সম্ভাব্য কাগুজে বইয়ে। বই এর আপডেট পেতে লাইক দিয়ে রাখতে পারেন এখানে

পরের চ্যাপ্টারঃ অবজেকটিভ সি তে এক্সেপশন ও এরর হ্যান্ডেলিং

৮- অবজেক্টিভ-সি এর ক্যাটাগরি (Category) এবং এর বিস্তারিত

Standard

আগের চ্যাপ্টারঃ অবজেক্টিভ সি (OBJECTIVE C) এর প্রোটোকল ও এর ব্যবহার

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

category
উপরের ছবিটিতে এটাই প্রকাশ করা হয়েছে যে, একাধিক ফাইল অর্থাৎ Cook.m এবং Cook+Costing.m মিলে Cook ক্লাসের পূর্ণাঙ্গ ইমপ্লিমেন্টেশন সম্পন্ন হয়েছে। নিচের উদাহরণের মাধ্যমে আমরা আরও পরিষ্কার ধারনা পাবো যেখানে, একটি ক্যাটাগরি ব্যবহার করে একটি বেজ ক্লাসকে এক্সটেন্ড করব অর্থাৎ বেজ ক্লাসের অ্যাসেট হিসেবে আরও কিছু মেথড যুক্ত করব কিন্তু বেজ ক্লাসটির সোর্স বা কোন কিছু পরিবর্তন না করেই।

উদাহরণ এর পটভূমি তৈরিঃ
কথামত, ক্যাটাগরির ব্যবহার বুঝতে হলে আমাদের এক সেট বেজ ক্লাস দরকার পরবে। আর তাই নিচের মত করে একটি ক্লিন প্রজেক্টে Cook ক্লাস তৈরি করে নিন যেটা এক্ষেত্রে আমাদের বেজ ক্লাস।

Cook.h

#import <Foundation/Foundation.h>

@interface Cook : NSObject

@property (copy) NSString *item;

- (void)placeOrder;
- (void)startCooking;

@end
Cook.m

#import "Cook.h"

@implementation Cook

-(void) placeOrder {
    NSLog(@"Order placed for a %@", self.item);
}

-(void) startCooking {
    NSLog(@"Started cooking %@", self.item);
}

@end

আশা করি উপরের সাধারণ ক্লাস ডেফিনেশনটা বুঝতে পেরেছেন। এভাবে আগে আমরা অনেক বারই ক্লাস তৈরি করেছি যেখানে একটি ইন্টারফেস ফাইল এবং একটি ইমপ্লিমেন্টেশন ফাইল মিলে একটি পূর্ণ ক্লাস ডেফিনেশন সম্পন্ন হয়। এখন মনে করুন যে, আপনি রান্না (Cook) সম্পর্কিত আরও কিছু মেথড যুক্ত করতে চাচ্ছেন কিন্তু Cook ক্লাস এর কোন কিছু পরিবর্তন না করে বা সেটার গঠন নষ্ট না করে। এই ক্ষেত্রে আপনি ওই নতুন মেথড গুলো একটি ক্যাটাগরিতে যুক্ত করতে পারেন। ধরুন, আমরা চাই Cooking সম্পর্কিত খরচ এর হিসাবের একটি নতুন মেথড যুক্ত করতে।

ক্যাটাগরি তৈরিঃ
ক্যাটাগরির ডেফিনিনেশন এবং ক্লাসের ডেফিনেশন মোটা মুটি একি রকম অর্থাৎ এরও ইন্টারফেস এবং ইমপ্লিমেন্টেশন মিলেই সম্পূর্ণতা হয়। আমাদের প্রজেক্টে নতুন Cook+Costing নামের ক্যাটাগরি যুক্ত করতে হলে Xcode এর New File অপশন থেকে নিচের মত করে এই ক্যাটাগরিটি তৈরি ও প্রজেক্টে যুক্ত করুন। ক্যাটাগরির নাম দিচ্ছি Costing এবং Category On হিসেবে লিখতে/সিলেক্ট করতে হবে Cook.
Screen Shot 2014-06-09 at 9.15.28 PM

Screen Shot 2014-06-09 at 9.15.59 PM

তাহলে আমাদের প্রজেক্টে ফাইল স্ট্রাকচার হবে নিচের মত,
Screen Shot 2014-06-09 at 9.16.19 PM

এখন একটু খেয়াল করলেই দেখবেন ক্যাটাগরির ইন্টারফেস ফাইল এবং সাধারণ ক্লাসের ইন্টারফেস ফাইল একি রকম দেখতে শুধু @interface এর পর ক্লাসের নাম এবং তারপর ব্রাকেটের মধ্যে ক্যাটাগরির নাম উল্লেখ করতে হয়। নিচে আমাদের Cook+Costing এর ইন্টারফেস,

Cook+Costing.h

#import "Cook.h"

@interface Cook (Costing)

- (BOOL)isFreeOfferOngoing;
- (void)calculatePrice;

@end

রানটাইমে এই মেথড দুটো Cook ক্লাসের অংশ হিসেবেই কাজ করে। যদিও এগুলো আলাদা একটা ফাইলে ডিফাইন করা হল তবুও এগুলোকে এমন ভাবে এক্সেস করা যাবে যেন মনে হবে এগুলো Cook ক্লাসের মেথড।
আর আগেই বলা হয়েছে ক্যাটাগরির ইমপ্লিমেন্টেশনও লাগবে (সাধারণ ক্লাসের মতই)। তাই নিচের মত করে Cook+Costing.m ফাইলটি পরিবর্তন করুন,

Cook+Costing.m

#import "Cook+Costing.h"

@implementation Cook (Costing)

-(BOOL) isFreeOfferOngoing {
    return NO;
}

-(void)calculatePrice {
    NSLog(@"Price of %@ would be 350 tk.", self.item);
}

@end

ব্যবহারঃ
যেকোনো ফাইল কোন একটি ক্যাটাগরির যেকোনো একটি API (ইন্টারফেস লেয়ার) ব্যবহার করতে চাইলে তাকে অবশ্যই ওই ক্যাটাগরির হেডার ফাইলে যুক্ত করে নিতে হবে। অর্থাৎ নিচের মত করে main.m ফাইলে প্রথমে আমাদেরকে Cook ক্লাসের হেডার এবং তারপরে Cook+Costing ক্যাটাগরির হেডার যুক্ত করতে নিতে হবে,

main.m

#import <Foundation/Foundation.h>
#import "Cook.h"
#import "Cook+Costing.h"

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {
        Cook *forMe = [[Cook alloc] init];
        forMe.item = @"Italian Pizza";
        
        // "Standard" functionality from Cook.h
        [forMe placeOrder];
        [forMe startCooking];
        
        // Additional methods from Cook+Costing.h
        if (![forMe isFreeOfferOngoing]) {
            [forMe calculatePrice];
        }
        
    }
    return 0;
}

উপরে খেয়াল করুন, খুব সুন্দর ভাবে Cook ক্লাসের ফাংশনালিটি গুলো আমরা এক্সেস করতে পারছি এবং সাথে ওই ক্লাসের অবজেক্ট বা ইন্সট্যান্স দিয়েই কিন্তু Costing ক্যাটাগরির মাধ্যমে Cooking এর জন্য অতিরিক্ত প্রয়োজনীয় মেথড যেগুলো নতুন ক্যাটগরি হিসেবে ডিফাইন করা হয়েছে সেগুলোও এক্সেস করতে পারছি ১৯-২১ নাম্বার লাইনে। প্রোজেক্টটি রান করালে নিচের মত আউটপুট আসবে।

Screen Shot 2014-06-09 at 9.51.49 PM

প্রোটেক্টেড মেথড হিসেবে ক্যাটাগরিঃ
আশা করি এই বিষয়ে আমাদের সম্ভাব্য কাগুজে বইয়ে বিস্তারিত আলোচনা থাকবে।

এক্সটেনশনঃ
আশা করি এই বিষয়ে আমাদের সম্ভাব্য কাগুজে বইয়ে বিস্তারিত আলোচনা থাকবে।

পরের চ্যাপ্টারঃ
পরের চ্যাপ্টারে থাকবে অব্জেক্টিভ-সি এর ব্লক (Block) এবং সেটার ব্যবহার ও সুবিধাগত আলোচনা। তারপরেই থাকবে অবজেক্টিভ-সি তে এক্সেপশন ও এরর হ্যান্ডেলিং নিয়ে বিস্তারিত আলোচনা। আর উক্ত চ্যাপ্টারের মাধ্যমেই ব্যাসিক অবজেক্টিভ-সি লার্নিং সেকশন শেষ হয়ে পরবর্তী রিয়াল অ্যাপ ডেভেলপমেন্ট সেকশন শুরু হবে ১০ চ্যাপ্টার সম্বলিত।

আপডেট পেতে লাইক করুন এখানে অথবা এই ব্লগে সাবস্ক্রাইব করতে পারেন।

৭- অবজেক্টিভ সি (Objective C) এর প্রোটোকল ও এর ব্যবহার

Standard

আগের চ্যাপ্টারঃ অবজেক্টিভ সি (OBJECTIVE C) তে মেথড (METHOD) এর বিস্তারিত

ভূমিকাঃ
অবজেক্টিভ সি তে আপনি প্রোটোকল নামের একধরনের ফিচার ডিফাইন করতে পারবেন যেখানে কিছু মেথড ডিক্লেয়ার করা যেতে পারে যে মেথডগুলো বিশেষ পরিস্থিতিতে ব্যবহার করা যায়। যে ক্লাস ওই প্রোটোকলকে মেনে চলবে সেই ক্লাসে ওই প্রোটোকলের মেথড গুলোকে ইমপ্লিমেন্ট করা হয়।
আমরা নিচে যে উদাহরণ দেখবো সেখানে Food ক্লাসটি MakeProtocol কে মেনে চলবে, যে প্রোটোকলে processCompleted মেথডটি ডিক্লেয়ার করা আছে, যে মেথডের কাজ হচ্ছে খাবার তৈরি শেষ হয়ে গেলে ওই Food ক্লাসকে তা জানিয়ে দেয়া। অর্থাৎ, যেকোনো জায়গা থেকে Food ক্লাসের মধ্যে ইমপ্লিমেন্ট/ডিফাইন করা processCompleted মেথডকে কল করা। processCompleted মেথডটিকে অবশ্যই Food ক্লাসে ইমপ্লিমেন্ট করতে হবে। আর তাই এই ঘটনা অবশ্যই ঘটতে বাধ্য যে, Food ক্লাসের processCompleted মেথডটিকে এক্সিকিউট করা যাবেই।

সিনট্যাক্সঃ

@protocol ProtocolName
@required
// list of required methods
@optional
// list of optional methods
@end

@required কিওয়ার্ডের নিচে থাকা মেথড/মেথডগুলোকে কে অবশ্যই সেই ক্লাসে ইমপ্লিমেন্টেড থাকতে হবে যে ক্লাস এই প্রোটোকলকে মেনে চলবে (conform)। আর @optional কিওয়ার্ডের নিচে থাকা মেথড/মেথডগুলোকে কে সেই ক্লাসে ইমপ্লিমেন্ট না করলেও চলবে। নিচের মত করে একটি ক্লাস যেকোনো একটি প্রোটোকলকে মেনে চলার ব্যপারে অঙ্গীকারাবদ্ধ হতে পারে।

@interface MyClass : NSObject <MyProtocol>
...
@end

প্রোটোকল তৈরিঃ
যেকোনো প্রজেক্টে নিউ ফাইল হিসেবে প্রোটোকল ফাইলও যুক্ত করা যায়। Xcode এর File মেনু থেকে New->File ক্লিক করে নিচের মত করে একটি প্রোটোকল যুক্ত করে নিতে পারেন,
Screenshot 2014-05-25 18.53.41
তারপর সেটির নাম ঠিক করে দিতে পারেন এভাবে,
Screenshot 2014-05-25 18.56.44

প্রোটোকল এর প্রয়োগঃ
এখন আপনার প্রজেক্ট এর সাথে নিচের ফাইলগুলো মিলিয়ে একটি পরিষ্কার প্রজেক্ট সাজিয়ে ফেলুন। তারপর আমরা ওই প্রজেক্টে প্রোটোকল এর ব্যবহারটা বুঝতে চেষ্টা করব।

// MakeProtocol.h

#import <Foundation/Foundation.h>

@protocol MakeProtocol <NSObject>

- (void)processCompleted;

@end

এই আমাদের বানানো processCompleted মেথড বিশিষ্ট একটি প্রোটোকল। যে ক্লাস এই প্রোটোকলকে মেনে চলবে তাকে অবশ্যই processCompleted মেথড ইমপ্লিমেন্ট করতে হবে।

// Make.h

#import <Foundation/Foundation.h>

@interface Make : NSObject

@property (unsafe_unretained) id delegate;

- (void) startCooking;

@end

এটি হচ্ছে main এবং Food এর কর্মকাণ্ডের মধ্যবর্তী কিছু কাজের জন্য একটি ক্লাস এর ইন্টারফেস যেখানে একটি প্রোপার্টি এবং একটি মেথড ডিক্লেয়ার করা আছে। delegate প্রোপার্টিটিতে কোন ক্লাসের প্রতিনিধিত্ব সেট করা যায়। startCooking একটি সাধারণ মেথড যার বিস্তারিত একটু পরে Make এর ইমপ্লিমেন্টেশন থেকেও বোঝা যাবে।

// Make.m

#import "Make.h"
#import "MakeProtocol.h"

@implementation Make

- (void)startCooking{
    NSLog(@"Cooking Started!");
    
    // The following delegate property refers to
    // the Food class's object which was set from inside
    //// placeOrder
    // method of Food.
    
    // Then, a fact is also assured here that, the
    //// processCompleted
    // method will must be called from here which will
    // let the calling object know about its completion.
    
    [_delegate processCompleted];
}

@end

startCooking মেথডের শুরুতে আমরা একটি ম্যাসেজ প্রিন্ট করছি যে কুকিং শুরু হয়ে গেছে। তারপর আমরা এই ক্লাসের ডেলিগেট প্রোপার্টি ব্যবহার করে processCompleted ডেলিগেট মেথডকে কল করছি। এটা মূলত Food ক্লাসের মধ্যে থাকা processCompleted কেই কল করছে কারন একটু নিচেই আমরা দেখবো যে, Food ক্লাসের মধ্যের একটি মেথডের মাধ্যমে আমরা এই Make ক্লাসের ডেলিগেটটি সেট করছি অর্থাৎ Make ক্লাসের ডেলিগেট মূলত Food ক্লাসের অবজেক্ট হিসেবেই কাজ করছে।

// Food.h

#import <Foundation/Foundation.h>
#import "MakeProtocol.h"

@interface Food : NSObject <MakeProtocol>

- (void)placeOrder;

@end

উপরের কোড ব্লকে দেখতে পাচ্ছেন কিভাবে Food ক্লাসটি MakeProtocol নামের প্রোটোকলকে মেনে চলার ব্যাপারে নির্ধারিত হচ্ছে। সুপার ক্লাসের নামের পরে < MakeProtocol > এভাবে প্রোটোকল এর নাম ঠিক করে দেয়া যায়। যদি এই ক্লাসটি একাধিক প্রোটোকলকে মেনে চলতে চায় তাহলে < MakeProtocol, AnotherProtocol, … > এভাবে প্রোটোকল লিস্ট ঠিক করে দেয়া যায়। এই ক্লাসে placeOrder নামের একটি সাধারণ মেথডও আছে যেখান থেকেই আসলে খাবারের অর্ডার নিয়ে কুকিং শুরু সহ বাকি কাজগুলো হয়।

// Food.m

#import "Food.h"
#import "Make.h"

@implementation Food

- (void)placeOrder{
    Make *make = [[Make alloc]init];
    make.delegate = self;
    [make startCooking];
}

-(void)processCompleted{
    NSLog(@"Cooking Process Completed & this method has been called!");
}

@end

Food এর ইমপ্লিমেন্টেশনে প্রথমত placeOrder মেথড এর কার্যক্রম ঠিক করে দেয়া হয়েছে যেখানে বলা হয়েছে যে, প্রথমে Make ক্লাস ইন্সট্যান্সিয়েট করে সেটির ডেলিগেট প্রোপার্টি সেট করা হচ্ছে self (Food) হিসেবে। তারপর Make ক্লাসের startCooking মেথডকে কল করা হচ্ছে। আর startCooking মেথড কি করে সেটাতো একটু উপরেই বলা হয়েছে।
দ্বিতীয়ত, এখানেই Food ক্লাস বাধ্যগতভাবে processCompleted মেথডকে ইমপ্লিমেন্ট করছে। মনে করিয়ে দেই, এই মেথডটি কিন্তু Make ক্লাসের startCooking মেথডের মধ্যে থেকে কল হয় এবং আমরা বুঝতে পারি যে কুকিং কমপ্লিট হয়েছে আর এখানে সেই অনুযায়ী বাকি কাজ করি (এক্ষেত্রে কমপ্লিট হবার একটি ম্যাসেজ প্রিন্ট করছি)।
যদি এখানে (Food ক্লাসে) প্রোটোকল মেথডটিকে ইমপ্লিমেন্ট না করতাম তাহলে Xcode নিচের মত ওয়ার্নিং দিত,
Screen Shot 2014-06-02 at 9.48.46 PM

// main.m

#import <Foundation/Foundation.h>
#import "Food.h"

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {

        Food *choice = [[Food alloc] init];
        [choice placeOrder];
        
    }
    return 0;
}

বুঝতেই পারছেন, পুরো প্রোগ্রামটির শুরু এখান (main()) থেকেই। প্রথমে Food ক্লাস ইন্সট্যান্সিয়েট করা হচ্ছে। তারপর এর placeOrder মেথড কল করে অর্ডার সাবমিট করা হচ্ছে এবং পরবর্তীতে কুকিং শুরু, শেষ এবং Food ক্লাসকে জানিয়ে দেয়ার কাজ গুলো হচ্ছে যেসব উপর থেকে আমরা বর্ণনা করে এসেছি।

main থেকে শুরু করে Food, তারপর Make, মাঝখানে MakeProtocol এভাবে যদি আপনি নিজের মাথা দিয়ে কোড কম্পাইল করে করে উপরের দিকে আগান তাহলে আরেকবার পুরো উদাহরণটি বুঝতে উঠতে পারবেন বলে আশা করছি।

প্রোগ্রামটি রান করালে নিচের মত আউটপুট আসবে,
Screen Shot 2014-06-02 at 10.14.39 PM

iOS অ্যাপে প্রোটোকল এর প্রয়োগঃ
রিয়াল লাইফ iOS অ্যাপ্লিকেশন ডেভেলপমেন্টে প্রোটোকল এর ব্যবহার খুব সাধারণভাবেই চলে আসে। যেমনঃ “application delegate” অবজেক্ট সেরকম একটি ব্যাপার যেটি মূলত ওই অ্যাপ রান হওয়া থেকে শুরু করে যতক্ষণ চলবে ততক্ষণ বিভন্ন ইভেন্ট হ্যান্ডেল করে থাকে।
রিয়াল অ্যাপ ডেভেলপ করার সময় নিচের কোড ব্লক টুকু আপনার নজরে চলে আসবে,

@interface YourAppDelegate : UIResponder <UIApplicationDelegate>

যতক্ষণ পর্যন্ত এটা (আপনার “application delegate” অবজেক্ট) UIApplicationDelegate এর মধ্যে ডিফাইন করা মেথডগুলোকে রেসপন্স করবে ততক্ষণ পর্যন্ত যেকোনো অবজেক্টকেই আপনার অ্যাপ্লিকেশন ডেলিগেট হিসেবে ব্যবহার করতে পারবেন। এতে করে অ্যাপ ইভেন্ট গুলো নিয়ে কাজ করতে সুবিধা হবে।

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

পরের চ্যাপ্টারঃ অবজেক্টিভ-সি এর ক্যাটাগরি (CATEGORY) এবং এর বিস্তারিত

বিঃ দ্রঃ এই সিরিজের নিয়মিত পোস্টের আপডেট পেতে ব্লগে সাবস্ক্রাইব করতে পারেন

৬ – অবজেক্টিভ সি (Objective C) তে মেথড (method) এর বিস্তারিত

Standard

আগের চ্যাপ্টারঃ ৫ – অবজেকটিভ সি (OBJECTIVE C) তে প্রোপার্টি ও এর অ্যাট্রিবিউটের ব্যবহার

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

নেমিং কনভেনশনঃ
সাধারণত অবজেক্টিভ সি তে মেথডের নামকরন করার সময় বেশি বেশি শব্দ ব্যবহার করা হয় বলে আপাত দৃষ্টিতে মনে হয় কিন্তু সত্যি বলতে এই পদ্ধতিতে মেথডটি অনেক বেশি বর্ণনামূলক ও বোধগম্য হয়। কোন রকম শব্দের সংক্ষেপণ না করা এবং বিশদভাবে মেথডটির প্যারামিটার ও রিটার্ন ভ্যালু ঠিক করে দেয়াই হচ্ছে অবজেক্টিভ সি তে মেথডের নামকরণের অঘোষিত নিয়ম।

নিচে কিছু উদাহরণ দেয়া হল,

// Calculated values
- (double)maximumPreparationTime;
- (double)maximumPreparationTimeUsingTool:(NSString *)tool;

// Action methods
- (void)startCooking;
- (void)cookFixedSize:(double)theSize;
- (void)makeWithType:(NSString *) type withSize:(int) size;

// Custom Constructor methods
- (id)initWithItem:(NSString *)item;
- (id)initWithItem:(NSString *)item size:(double)theSize;

// Factory methods
+ (Food *)food;
+ (Food *)foodWithItem:(NSString *)item;

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

C++ বা Java তে যেখানে মেথড এবং এর প্যারামিটার গুলোকে আলাদা ভাবেই ট্রিট করা হয়, সেখানে অবজেক্টিভ সি তে একটি মেথডের নামের সাথে এর প্যারামিটার গুলোর নামও যুক্ত থাকে। যেমন উপরের initWithItem:size: মেথডটি দুইটি প্যারামিটার গ্রহণ করে আর তাই এই মেথডের নাম মূলত initWithItem:size: এটাই। শুধুমাত্র initWithItem: নয়। এখানে initWithItem:size: নামটি প্রথম আর্গুমেন্ট হিসেবে item এবং দ্বিতীয় আর্গুমেন্ট হিসেবে size প্রকাশ করছে।

আরও একটি উল্লেখযোগ্য বিষয় হচ্ছে মেথড গুলোর রিটার্ন করা ভ্যালু আসলেই কি জিনিস সেটাও মেথড ডিক্লেয়ার করার সময়ই পরিষ্কার ভাবে ঠিক করে দেয়া হয়। যেমন উপরের ফ্যাক্টরি মেথড গুলো দেখে বোঝা যাচ্ছে যে, এগুলো Food ক্লাসের ইন্সট্যান্স রিটার্ন করবে।

মেথড কল করাঃ
একটি স্কয়ার ব্রাকেটের মধ্যে প্রথমে অবজেক্টের নাম এবং একটা স্পেস দিয়ে মেথডের নাম লিখে যেকোনো অবজেক্টের মেথডকে কল করা হয়। প্যারামিটার ওয়ালা মেথডের ক্ষেত্রে একটি কোলন দিয়ে মেথডের নাম এবং আর্গুমেন্টকে আলাদা করা হয়। যেমন ৪ নাম্বার চ্যাপ্টারের অনেক জায়গাতেই মেথড কল দেখানো হয়েছে। নিচে আবার আমরা এক সেট উদাহরণ তৈরি করব এই চ্যাপ্টারের টপিক গুলো বোঝানোর জন্য,

// Food.h

#import <Foundation/Foundation.h>

@interface Food : NSObject

@property (nonatomic) int price;

- (void)makeWithType:(NSString *) type withSize:(int) size;

@end
// Food.m

#import "Food.h"

@implementation Food

- (void)makeWithType:(NSString *)type withSize:(int)size {
    
    // Setting price property
    self.price = size*60;
    
    // Logging in the console
    NSLog(@"Making a %d inch %@ that costs %d Tk", size, type, self.price);
}

@end
// main.m

#import <Foundation/Foundation.h>
#import "Food.h"

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {

        Food *pizza = [[Food alloc] init];

        [pizza makeWithType:@"Mexican Pizza" withSize:6];
        
    }
    return 0;
}

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

[pizza makeWithType:@"Mexican Pizza" withSize:6];

অর্থাৎ- দ্বিতীয় এবং এর পরবর্তী প্যারামিটার গুলো, ইনিশিয়ালটির (প্রথম) পর পরই থাকে এবং প্রত্যেকটি প্যারামিটার তার আগেরটির সাথে একটি “স্পেস লেবেল:ভ্যালু” প্যাটার্ন মেইন্টেইন করে।

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

নেস্টেড মেথড কলঃ
নেস্টেড মেথড কলিং, অবজেক্টিভ সি এর একটি কমন প্যাটার্ন। একটি মেথডের রিটার্ন করা ভ্যালু অন্য মেথডে পাঠানোর স্বাভাবিক উপায়কেই নেস্টেড মেথড কল বলা হয়। অন্যান্য ল্যাঙ্গুয়েজের ভিউ থেকে থেকে দেখলে এটা একধরনের চেইনিং মেথড কল। উপরের উদাহরণেও কিন্তু একটা নেস্টেড মেথড কল হয়েছে। main.m ফাইলের ১২ নাম্বার লাইনে,

Food *pizza = [[Food alloc] init];

এখানে [Food alloc] এর মাধ্যমে একবার alloc মেথড কল করে অবজেক্টের জন্য মেমোরি অ্যালোকেট করা হয়েছে এবং তারপরই এটার রিটার্ন ভ্যালুকে দিয়ে init মেথড কল করা হয়েছে।

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

নিচের ৩টি ফাইল মিলে এরকম একটি কেস তৈরি করা হয়েছে।

// Food.h

#import <Foundation/Foundation.h>

@interface Food : NSObject

-(void)makeWithType:(NSString *) type withSize:(int) size;

@end
// Food.m

#import "Food.h"

@implementation Food {
    // The following variable is not directly accessible by any object of Food class
    int _price;
    
}

// The following method is not directly accessible by any object of Food class
- (int) calculatePrice: (int) size {
    
    // Setting the value of a private instance variable
    _price = size*60;
    
    return _price;
    
}

- (void)makeWithType:(NSString *)type withSize:(int)size {

    // Calling a kind of private method
    [self calculatePrice:size];
    
    // Logging in the console
    NSLog(@"Making a %d inch %@ that costs %d Tk", size, type, _price);
}

@end
// main.m

#import <Foundation/Foundation.h>
#import "Food.h"

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {

        Food *pizza = [[Food alloc] init];
        
        // [pizza calculatePrice:6]; // Is not accessible like this way 
        
        [pizza makeWithType:@"Mexican Pizza" withSize:6];
        
    }
    return 0;
}

এখানে _price ভ্যারিয়েবলটি এবং calculatePrice: মেথডটি প্রাইভেট এর মত আচরন করে। অর্থাৎ উপরের উদাহরণটি ঠিক ঠাক মতোই কাজ করবে কিন্তু যদি main.m ফাইলের ১৩ নাম্বার লাইনটি আনকমেন্ট করে প্রোগ্রামটি রান করতে চান তাহলে Xcode এরর ম্যাসেজ দিয়ে জানাবে যে Food ইন্টারফেসে calculatePrice: নামে কোন মেথড নাই।
Screenshot 2014-05-24 19.12.27
যার মানে দাড়ায়, এই মেথডকে আপনি ওই ক্লাসের বাইরে থেকে ব্যবহার করতে পারছে না। কিন্তু calculatePrice: মেথডটি ঠিকই আমরা ব্যবহার করতে পারছি Food.m ফাইলে অর্থাৎ ক্লাসের ভিতরে, যেমন ২৪ নাম্বার লাইনে।

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

সিলেক্টরঃ
অবজেক্টিভ সি তে সিলেক্টর হচ্ছে কোন মেথডের নামের ইন্টারনাল রিপ্রেজেন্টেশন। এর মাধ্যমে আপনি একটি মেথডকে একটি স্বাধীন এলিমেন্ট হিসেবে এর সাথে ডিল করতে পারবেন। যেমন, একটি অবজেক্ট সাধারণত একটি অ্যাকশনকে ব্যবহার করে আর এ ক্ষেত্রে সিলেক্টর অবজেক্ট থেকে ওই অ্যাকশনকে আলাদা করে। এটাকে Target-Action ডিজাইন প্যাটার্ন এর ভিত্তি বলা যায়। আর সহজ ভাবে বলতে গেলে কোন মেথডকে ডাইন্যামিক্যালি ইনভোক/কল করার জন্য সিলেক্টরের দরকার পরে।

দুইটি পদ্ধতিতে একটি মেথড এর নামের সাপেক্ষে একটি সিলেক্টর পাওয়া যায়। @selector() ডিরেক্টিভ ব্যবহার করে অথবা NSSelectorFromString() ফাংশনের সাহায্যে। দুইটি ক্ষেত্রেই এরা আপনার নতুন সিলেক্টর এর জন্য একটি ডাটা টাইপ রিটার্ন করে যার নাম SEL. অন্যান্য সাধারণ ডাটা টাইপ যেমন BOOL, int এর মতই এই SEL কেও একটি ডাটা টাইপ মনে করা যেতে পারে।

নিচের মত করে Food ক্লাসের ইন্টারফেস, ইমপ্লেমেন্টেশন এবং main ফাইলের কোড আপডেট করুন। তারপর আমরা সিলেক্টর এর ব্যবহার আরও ভাল ভাবে উপলব্ধি করতে পারবো।

// Food.h

#import <Foundation/Foundation.h>

@interface Food : NSObject

- (void)placeOrder;
- (void)makeOfType:(NSString *) type ofSize:(NSNumber *) size;

@end
// Food.m

#import "Food.h"

@implementation Food

- (void)placeOrder {
    NSLog(@"Order Received!");
}

- (void)makeOfType:(NSString *)type ofSize:(NSNumber *)size {
    
    NSLog(@"Making a %@ inch %@", size, type);
}

@end
// main.m

#import <Foundation/Foundation.h>
#import "Food.h"

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {

        Food *choice = [[Food alloc] init];
        
        SEL stepOne = NSSelectorFromString(@"placeOrder");
        SEL stepTwo = @selector(makeOfType:ofSize:);

        #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        
        // Same as calling this way: [choice placeOrder];
        [choice performSelector:stepOne];
        
        // Same as calling this way:
        // [choice makeOfType:@"Mexican Pizza" ofSize:[NSNumber numberWithInt:6]];
        [choice performSelector:stepTwo
                     withObject:@"Mexican Pizza"
                     withObject:[NSNumber numberWithInt:6]];
        
        #pragma clang diagnostic pop
    }
    return 0;
}

এখানে খুব স্বাভাবিক ভাবেই ইন্টারফেস এবং ইমপ্লেমেন্টেশন ঠিক করা হয়েছে যে ব্যপারে আপনি ইতোমধ্যে কয়েকবার ধারনা পেয়েছেন। এরপর main.m ফাইলে আমরা দুটি সিলেক্টর তৈরি করে নিয়েছি উপরে উল্লেখিত দুটি পদ্ধতি ব্যবহার করেই ১৩ এবং ১৪ নাম্বার লাইনে। এর পর ১৬, ১৭ এবং ২৮ নাম্বার লাইনের কোড গুলো আমাদের সিলেক্টর এর ব্যবহার সম্পর্কিত নয় বরং কম্পাইলারের কিছু ওয়ার্নিং বন্ধ করার জন্য। এভাবে সিলেক্টর ডিফাইন করলে কম্পাইলার বুঝে উঠতে পারে না যে, এই মেথড গুলোর রেজাল্ট দিয়ে কি করা হবে। তাই Xcode থেকে নিচের মত ওয়ার্নিং পেতে পারেন,
Screen Shot 2014-05-25 at 2.00.16 AM
এই ওয়ার্নিং এর ব্যপারে আরও বিস্তারিত এখানে

এরপর ২০ এবং ২৪ নাম্বার লাইনে আমরা performSelector: মেথড ব্যবহার করে আমাদের তৈরি করা সিলেক্টর গুলোকে এক্সিকিউট করেছি। ১৯ এবং ২২ নাম্বার লাইনে কমেন্ট এর মধ্যে বলা হয়েছে এর সমতুল্য ট্র্যাডিশনাল কল কেমন হতে পারতো। একাধিক প্যারামিটার ওয়ালা মেথডের ক্ষেত্রে withObject: ব্যবহার করে একের পর এক প্যরামিটার পাস করা হয়।

প্রোগ্রামটি রান করালে নিচের মত আউটপুট আসবে,
Screen Shot 2014-05-25 at 2.07.25 AM

পরের চ্যাপ্টারঃ
এই চ্যাপ্টার পর্যন্ত অবজেক্টিভ সি এর ব্যাসিক ব্যপার গুলো সংক্ষেপে তুলে ধরা হয়েছে। পরবর্তী চ্যাপ্টার থেকে একটু অ্যাডভ্যান্স বিষয় নিয়ে আলোচনা হবে। যেমন ৭ নাম্বার চ্যাপ্টারেই থাকবে প্রোটোকল (protocol) যার মাধ্যমে একটি এপিআই কে বিভিন্ন রকম এবং অনেক
গুলো ক্লাস থেকে ব্যবহার করা যাবে।

পরের চ্যাপ্টারঃ অবজেক্টিভ সি (OBJECTIVE C) এর প্রোটোকল ও এর ব্যবহার