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

Standard

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

আগের চ্যাপ্টারঃ সুইফ্ট (Swift) – অ্যাপলের নতুন চমক (পরিচিতি ও অন্যান্য বেসিক)

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

ক্যারেকটার ও স্ট্রিং ঃ
অন্যান্য বেসিক ডাটাটাইপ (Integer, Float, Double) গুলোর মতই ক্যারেকটার (Character) একটি ডাটাটাইপ যা এক বাইট (Byte) ডাটা সংরক্ষন করে। অন্যদিকে ক্যারেকটার টাইপ অ্যারে কে স্ট্রিং(String) বলা হয়। অর্থাৎ যদি একাধিক ক্যারেকটার একত্রে কোন নির্দিষ্ট নিয়মে সাজানো হয় তাহলে এই ক্যারেকটারগুলোকে একত্রে একটি স্ট্রিং বলা হয়। যেমন ঃ “Steve Jobs”, “Swift”, “Programming”, “Macbook Pro and My IPhone” ইত্যাদি। অনেকসময় একটি ক্যারেকটার কে ও স্ট্রিং হিসেবে ব্যবহার করা যায় যার উদাহরন আমরা একটু পরেই দেখব।

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

স্ট্রিং ডিক্লেয়ার করাঃ
কিভাবে ভ্যারিয়েবল ও কনস্ট্যান্ট ডিক্লেয়ার করতে হয় তা আমরা প্রথম অধ্যায়ে শিখেছি। একই ভাবে স্ট্রিং টাইপ ভ্যারিয়েবল বা কনস্ট্যান্ট ডিক্লেয়ার করতে হয়।

let stringVariable = "যেকোন ইউনিকোড সাপোর্টেড ক্যারেকটারসমুহ"
let anotherStringVariable: String = "Yosemite is the name of latest OS X"

যেহেতু stringVariable ও anotherStringVariable দুটিতে স্ট্রিং দিয়ে ইনিশিয়ালাইজ করা হয়েছে তাই এই কনস্ট্যান্ট দুটি এখন স্ট্রিং টাইপ কনস্ট্যান্ট হিসেবে কাজ করবে। এবং এদের উপর যাবতীয় স্ট্রিং অপারেশনগুলো করা যাবে।

স্ট্রিং এ ক্যারেকটার ছাড়াও অন্যান্য যা যা থাকতে পারে ঃ

  • সব ধরনের স্পেশাল (escaped special) ক্যারেকটার যেমন ঃ \০ ( null ), \n (new line), \\ (backslash), \t (horizontal tab), \” (double quote), \’ (single quote) ইত্যাদি।
  • এক বাইটের ইউনিকোড স্কেলার (\xnn, nn এর জায়গায় যেকোন দুটি হেক্সাডেসিমেল ডিজিট বসতে পারে)
  • দুই বাইটের ইউনিকোড স্কেলার (\xnnnn, nnnn এর জায়গায় যেকোন চারটি হেক্সাডেসিমেল ডিজিট বসতে পারে)
  • চার বাইটের ইউনিকোড স্কেলার (\xnnnnnnnn, nnnnnnnn এর জায়গায় যেকোন আটটি হেক্সাডেসিমেল ডিজিট বসতে পারে)

এই চারধরনের বিশেষ ক্যারেকটার গুলো নিয়ে নিচে চারটি উদাহরন দেওয়া হলঃ

let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imagination is more important than knowledge" - Einstein
let dollarSign = "\x24"        // $,  Two Byte Unicode scalar U+0024
let blackHeart = "\u2665"      // ♥,  Four Byte Unicode scalar U+2665
let sparklingHeart = "\U0001F496"  // 💖, Eight Byte Unicode scalar U+1F496

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

var ফাঁকাস্ট্রিং = ""               // empty string literal
var emptyString = ""
var anotherEmptyString = String()  // initialiser syntax
// these three strings are all empty, and are equivalent to each other

প্রয়োজনে খুব সহজেই isEmpty প্রোপার্টির বুলিয়ান ভ্যালু দেখে কোন স্ট্রিং ফাঁকা কিনা তা চেক করা যায়।

var emptyString = ""
if emptyString.isEmpty {
    println("Nothing to see here")
}
// prints "Nothing to see here"

স্ট্রিং মিউট্যাবিলিটি (String Mutability) ঃ
কোন স্ট্রিং মিউট্যাবল( Mutable or Modifiable) হবে কিনা তা নির্ধারিত হয় ভ্যারিয়েবল বা কনস্ট্যান্ট ডিক্লেয়ার করার সময়। যদি স্ট্রিংটি “var” টাইপ দিয়ে ডিক্লেয়ার করা হয় তাহলে এটিকে মডিফাই করা যাবে। কিন্তু যদি এই স্ট্রিংটি “let” টাইপ হয় তাহলে এটি একটি কনস্ট্যান্ট এর ন্যায় আচরন করবে। তাই “let” টাইপ স্ট্রিং মিউট্যাবল না। নিচের উদাহরন টি দেখলেই পরিস্কার হয়ে যাবে ব্যাপার টি।

var variableString = "Swift is"
variableString += " a new programming language."
// variableString is now "Swift is a new programming language."
 
let constantString = "Swift is very much"
constantString += " faster and interactive"
// this reports a compile-time error - a constant string cannot be modified

নোটঃ
সুইফ্ট (Swift) এর এই স্ট্রিং এর সাথে অবজেকটিভ-সি (Objective-C) এর NSString রয়েছে ব্রিজ কানেকশন। আমরা যদি Cocoa অথবা Cocoa Touch এর Foundation ক্লাস নিয়ে কাজ করি তাহলে সুইফ্ট এর স্ট্রিং টাইপ ভ্যারিয়েবলগুলোর জন্য NSString ক্লাসের সব এপিআই (API) কল করা যায়।

Cocoa বা Cocoa Touch এর Foundation ক্লাসের NSString এর ইনস্ট্যান্স তৈরী করে যদি ফাংশন বা মেথডে পাঠানো হয় তাহলে মুলত ওই স্ট্রিং এর রেফারেন্স বা পয়েন্টারকেই পাঠানো হয় যা নতুন পয়েন্টার বা রেফারেন্সে অ্যাসাইন করা হয়। আসল স্ট্রিং এর কোন কপি তৈরী হয় না। অন্যদিকে Swift এর String ভ্যালু তৈরী করে যদি কোন ভ্যারিয়েবলে অ্যাসাইন করা হয় অথবা কোন মেথড বা ফাংশনে পাঠানো তাহলে প্রথমে ওই স্ট্রিং এর একটি কপি তৈরী হয় এবং তারপর এই নতুন স্ট্রিং টি মেথডে পাঠানো হয় অথবা নতুন ভ্যারিয়েবলে অ্যাসাইন করা হয়।
আমাদের কাগুজে বইতে এই সম্পর্কে আরও বিস্তারিত থাকবে।

ক্যারেকটার ম্যানিপুলেশনঃ
এতক্ষনে আমরা জেনে গেছি যে, স্ট্রিং আসলে একত্রে থাকা অনেকগুলো Character যা সুইফ্ট এ String টাইপ দিয়ে রিপ্রেজেন্ট করা হয়। প্রত্যেকটি Character আবার একটি ইউনিকোড ক্যারেকটার। সুইফ্টে for-in লুপ দিয়ে একটি স্ট্রিং এর প্রত্যেকটি ক্যারেকটার এক্সেস করা যায়। for-in লুপ এর সিনট্যাক্স ও বিস্তারিত পরের অধ্যায়ে থাকবে। এই মুহুর্তে আমরা একটি স্ট্রিং এর প্রথম থেকে শেষ পর্যন্ত সবগুলা Character এক্সেস করব এবং তা দেখব।

for ch in "Macintosh" {
    println(ch)
}
// M
// a
// c
// i
// n
// t
// o 
// s
// h

আবার কোন এক ক্যারেকটারের স্ট্রিং থেকে Character টাইপ ভ্যারিয়েবল বা কনস্ট্যান্ট ডিক্লেয়ার করার জন্য নিচের মত কোড লিখতে হয়।

let charConstant: Character = "$"
var charVariable: Character = "ক"

স্ট্রিং এ থাকা মোট ক্যারেকটারের সংখ্যা জানাঃ
countElements() একটি গ্লোবাল মেথড যা আর্গুমেন্ট হিসেবে একটি স্ট্রিং নেয় এবং এই স্ট্রিং এ কয়টি ক্যারেকটার আছে তা রিটার্ন করে।

let intro: String = "In Swift, It is too easy to count characters."
var countChar = countElements(intro); 
println(countChar)  // 45

নোটঃ
সুইফ্ট এর স্ট্রিং এর ক্যারেকটারগুলো ইউনিকোড ক্যারেকটার হয় এবং ইউনিকোডে বিভিন্ন ক্যারেকটারের সাইজ ও আলাদা হয়। একারনে সুইফ্ট ল্যাঙ্গুয়েজে সকল ক্যারেকটার একই সাইজের মেমরী নেয় না। তাই countElements() মেথডের রিটার্ন ভ্যালু ও আমাদের চাওয়া অনুযায়ী হয় না। তাই কোন স্ট্রিং এ ক্যারেকটারের সঠিক সংখ্যা জানার জন্য লুপ ব্যবহার করে প্রত্যেকটি ক্যারেকটার গননা করতে হয়। নিচের উদাহরনটিতে প্রথম লাইনে counter ভ্যারিয়েবল ০ দিয়ে ইনিশিয়ালাইজ করা হয়েছে। এরপর for-in লুপ দিয়ে প্রথম থেকে শেষ পর্যন্ত প্রত্যেকটি ক্যারেকটার ভিজিট করা হয় এবং প্রতিবার counter এর ভ্যালু ১ করে বাড়ানো হয়। ফলে for-in টির এক্সিকিউশন শেষ হলে counter ভ্যারিয়েবলে “Macintosh” এর মোট ক্যারেকটারের সংখ্যা পাওয়া যাবে।

var counter = 0
for ch in "Macintosh" {
    counter++;
}
println("This string has \(counter) characters"); // This string has 9 characters

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

//Example 1
let string1 = "hello"
let string2 = " Steve"
let character1: Character = "!"
let character2: Character = "?"
 
let stringPlusCharacter = string1 + character1        // equals "hello!"
let stringPlusString = string1 + string2              // equals "hello Steve"
let characterPlusString = character1 + string1        // equals "!hello"
let characterPlusCharacter = character1 + character2  // equals "!?"

//Example 2
var instruction = "follow"
instruction += string2
// instruction now equals "follow Steve"
 
var welcome = "good morning"
welcome += character1
// welcome now equals "good morning!"

স্ট্রিং ইন্টারপুলেশন (String Interpolation)ঃ
কোন স্ট্রিং এর মধ্যে নতুন করে কোন কনস্ট্যান্ট, ভ্যারিয়েবল, এক্সপ্রেশন ইত্যাদির মিশিয়ে নতুন কোন স্ট্রিং তৈরী করা কে বলা হয় স্ট্রিং ইন্টারপুলেশন। এজন্য যে নতুন ভ্যারিয়েবল বা কনস্ট্যান্ট বা এক্সপ্রেশন মেশানো হবে তা প্যারেনথেসিস বা “()” এর ভিতর লিখে তার আগে একটি ব্যাকস্ল্যাশ দিতে হয়। তাহলে স্ট্রিং এই অংশে নতুন জিনিস টি ইনসার্ট হয়ে যায়।

let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"

উপরের কোডটিতে multiplier কনস্ট্যান্টটি ২ বার ব্যবহার করে একটি নতুন স্ট্রিং message তৈরী করা হয়েছে। এজন্য যেসব পজিশনে multiplier এর ভ্যালু ইনসার্ট করা হবে সেসব স্থানে প্রথমে ব্যাকস্ল্যাশ ও তারপর প্যারেনথেসিস দিয়ে multiplier লেখা হয়েছে। ইন্টারপুলেশনের সময় “\(multiplier)” এর পরিবর্তে এখানে multiplier এর ভ্যালু তথা 3 বসবে। এখানে লক্ষ্য করার বিষয় হল প্রথম multiplier টি একটি কনস্ট্যান্ট যা সরাসরি স্ট্রিং টিতে ইনসার্ট করা হয়েছে। কিন্তু ২য় টিতে একটি এক্সপ্রেশন “Double(multiplier) * 2.5” ইনসার্ট করা হয়েছে। প্রথমে multiplier এর ভ্যালু Double দিয়ে কাস্ট (Cast) করে তারপর 2.5 দিয়ে গুন করার পর যে রেসাল্ট পাওয়া যাবে তাই ইনসার্ট করা হয়েছে স্ট্রিং টিতে।

স্ট্রিং কমপ্যারিজন (String Comparison)ঃ
সুইফ্ট (Swift) এর String টাইপের মধ্যকার কমপ্যারিজন করার জন্য ৩টি মেথড রয়েছে : স্ট্রিং(String) ইকুয়্যালিটি, প্রিফিক্স(Prefix) ইকুয়্যালিটি এবং সাফিক্স(Suffix) ইকুয়্যালিটি।

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

let quotation = "We're a lot alike, you and I."
let sameQuotation = "We're a lot alike, you and I."
if quotation == sameQuotation {
    println("These two strings are considered equal")
}
// prints "These two strings are considered equal"

স্ট্রিং টিতে কোন পার্টিকুলার (particular) স্ট্রিং প্রিফিক্স বা সাফিক্স আছে কিনা তা চেক করার জন্য রয়েছে hasPrefix বা hasSuffix মেথড। উভয় মেথড স্ট্রিং টাইপ আর্গুমেন্ট নিয়ে বুলিয়ান ভ্যালু রিটার্ন করে। আর্গুমেন্টের প্রত্যেকটি ক্যারেকটার মুল স্ট্রিং এ একই অর্ডারে আছে কিনা সেটাই চেক করে মেথড দুটি।

let comment = "Swift is a awesome language."
if comment.hasPrefix("Swift"){
   // String comment contains a "Swift" as prefix  
}

if comment.hasSuffix("language."){
   // String comment contains a "language." as Suffix  
}

উপরের উদাহরনটিতে comment একটি স্ট্রিং টাইপ কনস্ট্যান্ট নেওয়া হয়েছে। hasPrefix(“Swift”) মেথড টি মুলত comment স্ট্রিং এর প্রথমে “Swift” স্ট্রিং টি আছে কিনা। যেহেতু comment স্ট্রিং টিতে প্রিফিক্স হিসেবে “Swift” আছে তাই মেথডটি TRUE রিটার্ন করে। আবার hasSuffix(“language.”) মেথডটি এটাই চেক করছে যে comment স্ট্রিং টি “language.” দিয়ে শেষ হয়েছে কিনা। যেহেতু স্ট্রিং টির শেষ “language.” দিয়ে হয়েছে তাই মেথড টি TRUE রিটার্ন করে।

কেস(Case) কনভারসনঃ
স্ট্রিং ম্যানিপুলেশনের একটি কমন টাস্ক হল কেস কনভারসন। অর্থাৎ ছোট হাতের অক্ষর থেকে বড় হাতের অক্ষর বা বড় হাতের অক্ষর থেকে ছোট হাতের অক্ষরে রুপান্তর করা। সুইফ্টে এই কাজ যথেষ্ট সহজেই করা যায়।

let normal = "Would you mind giving me a glass of Water?"
let uppercase = normal.uppercaseString
// uppercase is equal to "WOULD YOU MIND GIVING ME A GLASS OF WATER?"
let lowercase = normal.lowercaseString
// lowercase is equal to "would you mind giving me a glass of water?"

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

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

পরের চাপ্টারঃ 
পরের চাপ্টারে Collections তথা Arrays ও Dictionaries নিয়ে বিস্তারিত থাকবে। এছাড়াও থাকবে Enumerations ও Closures নিয়ে অল্প বিস্তর আলোচনা।

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

২-২ঃ 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 বাটনে ক্লিক করে দেখুন বাটনের ঠিক উপরের জায়গায় আপনার মন মত আউটপুট দেখাচ্ছে কিনা।

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

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

৩-১ঃ সুইফ্ট (Swift) – অ্যাপলের নতুন চমক (পরিচিতি ও অন্যান্য বেসিক)

Standard

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

ভুমিকাঃ
অবজেকটিভ-সি (Objective-C) এর বেসিক লার্নিং (সেকশন ১) ও আইওএস জিইউআই (GUI) অ্যাপ ডেভেলপমেন্ট (সেকশন ২) এ নজর রাখার জন্য আপনাকে অসংখ্য অভিনন্দন। আমাদের সেকশন ১ লেখা শেষ হওয়ার সাথে সাথেই অনুষ্ঠিত হয় অ্যাপলের ওয়ার্ল্ড ওয়াইড ডেভেলপার কনফারেন্স – ২০১৪ (WWDC – 2014)। প্রতিবছরের মত এবারও অ্যাপলের ওয়ার্ল্ডওয়াইড ডেভেলপার কনফারেন্স – ২০১৪ (WWDC) -এ ডেভেলপাররা পেয়েছে ৩ টি নতুন চমক। একদিকে আইওস-৮ (iOS 8) ও ইউসেমাইট (Yosemite, OS X 10.1০) এবং অন্যদিকে সুইফ্ট (Swift)। সুইফ্ট হল একেবারেই নতুন একটি প্রোগ্রামিং ল্যাঙ্গুয়েজ যা দিয়ে অবজেক্টিভ-সি এর মতই আইফোন, আইপ্যাড ও আইপডের জন্য অ্যাপ্লিকেশন ডেভেলপ করা যাবে। প্রায় ৩০ বছর ধরে অবজেক্টিভ-সি একাই রাজত্ব করে আসছে আইওএস অ্যাপ্লিকেশন ডেভেলপমেন্ট ফিল্ডে। অ্যাপলের মতে অদুর ভবিষ্যতে অবজেকটিভ-সি কে সরিয়ে এই রাজত্ব দখল করে নেবে সুইফ্ট নিজের ফাস্ট (first), মডার্ন (modern), সেইফ (safe), ইন্টারঅ্যাকটভ (interactive) বৈশিষ্ট্যের দ্বারা।

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

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

মাত্রই ঘোষিত হওয়া সুইফ্ট শেখার জন্য অ্যাপল দিয়েছে পর্যাপ্ত রিসোর্স। একটি পিডিএফ ফরম্যাটের বই (অ্যাপলের দেওয়া সুইফ্ট এর ফ্রি বই) এবং আরও আছে কিছু  অনলাইন রিসোর্স

সুইফ্ট ল্যাঙ্গুয়েজে হ্যালো ওয়ার্ল্ড প্রোগ্রামঃ
XCode 6 এ একটি নতুন প্রজেক্ট ওপেন করি। যেভাবে অবজেক্টিভ-সি ও আইফোন ডেভেলপমেন্ট প্রজেক্ট ওপেন করা হয়, সুইফ্ট এর জন্য একিভাবে প্রজেক্ট ওপেন করতে হয়। শুধুমাত্র প্রথমে Playgroud টাইপ প্রজেক্ট ওপেন করা এবং ল্যাঙ্গুয়েজ লিস্ট থেকে “swift” সিলেক্ট করতে হবে।
এই সুবিধাটি XCode 6 থেকে পাওয়া যাবে।

তাহলে চলুন এবার সুইফ্ট ল্যাঙ্গুয়েজে হ্যালো ওয়ার্ল্ড প্রোগ্রামটি লিখে ফেলিঃ

println("Hello, World")

সদ্য তৈরি করা প্রজেক্টের মুল ফাইলটিতে শুধুমাত্র এটুকু লিখেই রান করলে ডানদিকের আউটপুট উইন্ডোতে “Hello, world” দেখা যাবে।

লাইব্রেরী ইমপোর্ট (Import) করা
সুইফ্ট ল্যাঙ্গুয়েজ দিয়ে করা প্রোগ্রামেও যদি কোন এক্সটার্নাল লাইব্রেরী প্রয়োজন হয় তাহলে প্রজেক্টটিতে ওই লাইব্রেরী ইমপোর্ট করতে হয়। লাইব্রেরী ইমপোর্ট করার জন্য শুধুমাত্র import দিয়ে লাইব্রেরীর নাম লিখলেই হয়।

import Foundation

println("Foundation Library is imported.");

ভ্যারিয়েবল, কনস্ট্যান্ট ও টাইপ অ্যানোটেশনঃ 
অবজেক্টিভ-সি (Objective-C) এর মত ভ্যারিয়েবল বা কনস্ট্যান্ট ডিক্লেয়ার করার জন্য সুইফ্ট এ আসল ডাটাটাইপ লেখা লাগে না। সুইফ্ট এ ভ্যারিয়েবল ডিক্লেয়ার করার জন্য শুধুমাত্র “var” ও কনস্ট্যান্ট ডিক্লেয়ার করার জন্য “let” ব্যবহার করা হয়। সুবিধা হল ভ্যারিয়েবল বা কনস্ট্যান্ট ডিক্লেয়ার করার সময় এর ডাটাটাইপ সম্পর্কে মাথা ঘামানোর প্রয়োজন নেই। যে ধরনের ডাটা দিয়ে ইনিশিয়ালাইজ করা হবে, এদের ডাটাটাইপ ও তাই হবে। নিজের প্রোগ্রামটুকু দেখলেই পরিস্কার হয়ে যাবে।

println("Hello, world")
var myVariable = 42
// myVariable is a variable which will be inferred to be int

var (x, y, z) = (11, 12, 45)                      // x = 11, y = 12, z = 45
// x, y and z variables are inferred to be int

let π = 3.1415926                                 // constant
// π is inferred to be int 

let (x, y) = (10, 20.8)                             // x = 10, y = 20.8
// x is inferred to be int and y is inferred to be double

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

let π = 3.14159       // any special characters
let 你好 = "你好世界"  // any unicode supported characters
let স্টিভ = "একজন কিংবদন্তীর নাম"  // any unicode supported characters
let 🐶🐮 = "dogcow"     // any emoji icons

emoji ক্যারেকটারের লিস্ট পাওয়ার জন্য কন্ট্রোল + কমান্ড + স্পেসবার একসাথে চাপ দিন।

আপনি চাইলে ভ্যারিয়েবল বা কনস্ট্যান্ট ডিক্লেয়ার করার সময় এক্সপ্লিসিটলি ডাটাটাইপ জানিয়ে দিতে পারেন। এজন্য ভ্যারিয়েবল বা কনস্ট্যান্ট এর নামের পর কোলন (ঃ) ও তারপর স্পেস দিয়ে ডাটাটাইপ লিখতে হয়। এভাবে আলাদা করে ডাটাটাইপ সম্পর্কে জানানো কে বলা হয় টাইপ অ্যানোটেশন (Type Annotation)।

var annotatedMessage: String = "Hello World! This variable is Type Annotated"

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

println("I am the first statement. I need semicolon"); println("I am the last statement. I don't need semicolon")
println("Another Line, I don't need any semicolon.But you can give me a semicolon if you want.");

অপারেটরঃ
অন্যান্য প্রোগ্রামিং ল্যাঙ্গুয়েজের অপারেটর গুলোর সাথে সুইফ্ট এর অপারেটর গুলোর মধ্যে তেমন কোন পার্থক্য নেই। তবে কিছু কিছু ক্ষেত্রে অনেকটা ইমপ্রুভমেন্ট এনেছে সুইফ্ট। নিচে ক্যাটেগরী অনুযায়ী কিছু অপারেটর সম্পর্কে ধারনা দেওয়া হলঃ

অ্যাসাইনমেন্ট অপারেটর (=)
অ্যাসাইনমেন্ট অপারেটর (=) একটি বাইনারি অপারেটর। অর্থাত “=” এর দুইটি অপারেন্ড (Operands) থাকবে এবং ডানদিকের ভ্যালু বামদিকের ভ্যারিয়েবল বা কনস্ট্যান্ট এ অ্যাসাইন হবে।

let a = 5
let b = 10
var c = a + b
var (x, y) = (a, b)   // a is assigned to x and b is assigned to y
var httpNotFoundError = (404, "Page Not Found")

শেষ লাইন দুটি দেখে বোঝা যাচ্ছে যে, এভাবে “=” অপারেটরের ডানদিকে যদি একাধিক ডাটা থাকে তাহলে তা বামদিকের একাধিক সংখ্যক ভ্যারিয়বল বা কনস্ট্যান্ট এ অ্যাসাইন হতে পারে। আবার কোন একটি ভ্যারিয়েবল বা কনস্ট্যান্ট এর ভ্যালু একাধিক ভ্যালুর টাপল [ var a = (b,c) ] হতে পারে।

অ্যারাথমেটিক অপারেটর
অন্যান্য সকল প্রোগ্রামিং ল্যাঙ্গুয়েজের অ্যারাথমেটিক অপারেটর গুলোর মতই সুইফ্ট এর অ্যারাথমেটিক অপারেটর গুলো নিজ নিজ অপারেশন করে।

1 + 2       // equals 3 (Addition)
5 - 3       // equals 2 (Subtraction)
2 * 3       // equals 6 (Multiplication)
10.0 / 2.5  // equals 4.0 (Division)
9 % 4       // equals 1 (Remainder after division)
-9 % 4      // equals -1 (Remainder after division)
8 % 2.5     // equals 0.5

“+” অপারেটর দিয়ে স্ট্রিং কনক্যাটেনেশনও করা যায়।

var msg1 = "Hello"
var msg2 = "World"
var concatenated = msg1 + msg2
println(concatenated)    // Hello World

অন্যান্য অ্যারাথমেটিক অপারেটর

var a = 10;
++a      // equals 11
a++      // equals 11

// currenlty a is 11
var b = a++  // b = 11, but a = 12
// at first a is assigned to b. So, value of b is same as current a (11). Then a is incremented by 1. So, value of as is 12. 

//currently a is 12
var b = ++a  // b = 13, and a = 13
// at first a is incremented by 1. So, value of a is 13 (12+1). Then value of a is assigned to b. So, value of b is also 13

//currenly a is 13
var b = a--  // b = 13, but a = 12
// at first a is assigned to b. So, value of b is same as current a (13). Then a is decremented by 1. So, value of as is 12. 

//currently a is 11
var b = --a  // b = 11, and a = 11
// at first a is decremented by 1. So, value of a is 13 (12+1). Then value of a is assigned to b. So, value of b is also 13

কমপ্যারিজন/কন্ডিশনাল অপারেটর
সুইফ্ট সকল ধরনের কন্ডিশনাল অপারেটর গুলো সাপোর্ট করে। নিচে কয়েকটি অপারেটরের নাম দেওয়া হলঃ

  • Equal to (a == b)
  • Not equal to (a != b)
  • Greater than (a > b)
  • Less than (a < b)
  • Greater than or equal to (a >= b)
  • Less than or equal to (a <= b)
1 == 1   // true, because 1 is equal to 1
2 != 1   // true, because 2 is not equal to 1
2 > 1    // true, because 2 is greater than 1
1 < 2    // true, because 1 is less than 2
1 >= 1   // true, because 1 is greater than or equal to 1
2 <= 1   // false, because 2 is not less than or equal to 1

//ternary conditional operator
let hasHeader = true
let contentHeight = 100
let rowHeight = contentHeight + (hasHeader ? 50 : 20)  // rowHeight will be 50

এধরনের অপারেটর গুলো প্রোগ্রামের বিভিন্ন সিলেকশন বা ডিসিশন নেওয়ার সময় ব্যবহৃত হয়। পরবর্তী অধ্যায়সমুহে এ ব্যাপারে বিস্তারিত আলোচনা হবে।

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

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

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

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

৬-১ – অবজেক্টিভ-সি (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 এর মাধ্যমে একটি ব্লক টাইপ তৈরি করা যায় যাতে করে ব্লক ব্যবহারের সময় এর সিনট্যাক্স এলোমেলো না হয়ে যায় এবং ওই ব্লকটিকে আরও সহজ এবং সংক্ষেপ নামে ব্যবহার করা যায়। এ ব্যাপারে এবং উপরের টপিক গুলোর আরও বিস্তারিত থাকবে আমাদের সম্ভাব্য কাগুজে বইয়ে। বই এর আপডেট পেতে লাইক দিয়ে রাখতে পারেন এখানে

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

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

Standard

ভুমিকাঃ
অ্যাপ্লিকেশন ডেভেলপমেন্ট এর সময় যে দুটি প্রধান সমস্যার কথা ডেভেলপার/প্রোগ্রামারদের ভাবতে হয় সেগুলো হল এক্সেপশন ও এরর। iOS ও OS X এ অ্যাপ্লিকেশন চলার সময়ও এক্সেপশন ও এরর ঘটতে পারে।

প্রোগ্রামার লেভেলের বাগ গুলো কে এক্সেপশন বলা হয়। উদাহরনস্বরুপ: যখন কোডের মাধ্যমে কোন অ্যারে (Array) এর ননএক্সিস্টিং ইলিমেন্টকে এক্সেস করার চেষ্টা করা হয় তখন সিস্টেম এক্সেপশন থ্রো করে। অ্যাপ্লিকেশন চলার সময় যদি কোন কারনে এরকম একটা বিষয় উদ্ভূত হয় তাহলে ডেভেলপারকে সেটা সম্পর্কে তথ্য দেয়ার জন্যই প্রোগ্রামিং ল্যাঙ্গুয়েজগুলোতে এক্সেপশন ফিচার ডিজাইন করা হয়েছে। প্রোগ্রামের মধ্যেই প্রোগ্রামারকে এরকম এক্সেপশনাল কেস হ্যান্ডেল করার জন্য প্রয়োজনীয় কোড লিখতে হয় যাকে বলে এক্সেপশন হ্যান্ডেলিং।
Screen Shot 2014-06-10 at 3

এক্সেপশন ডেভেলপার কে জানাও।

অন্যদিকে ইউজার লেভেলে যদি কোন অনাকাঙ্খিত ঘটনা ঘটে সেগুলোকে এরর বলা হয়। যেমন, যদি আপনার অ্যাপ্লিকেশনটির কাজ হয় যে, ডিভাইসের GPS সার্ভিস ব্যবহার করে আসেপাশের কিছু লোকেশন দেখাবে। কিন্তু ইউজার তার ডিভাইসের GPS সেটিং ডিজ্যাবল করে রেখেছে । তখন অ্যাপটা চলার সময় ইউজারকে একটা ম্যাসেজ দেখানো উচিত যে, তার GPS অফ আছে। এরকম অবস্থায় ইউজারকে কিছুই না জানিয়ে অ্যাপ্লিকেশন এর ক্র্যাশ হওয়া উচিত হবে না। তাই এধরনের কন্ডিশনগুলো যখন ঘটবে তখন ম্যানুয়ালী চেক করে সঠিক তথ্যটা বা এরর ম্যাসেজটা ইউজারকে দেওয়াই হচ্ছে এরর হ্যান্ডেলিং।
Screen Shot 2014-06-10 at 3.09.58 PM

ওয়ার্ক এরর ইউজারকে জানাও এবং সম্ভব হলে অ্যাপ রান করতেই থাকো।

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

কোডিং এরর ডেভেলপারকে জানিয়ে থেমে যাও

এক্সেপশন (Exceptions)ঃ
অবজেকটিভ-সি তে NSException ক্লাস দিয়ে এক্সেপশনকে রিপ্রেজেন্ট করা হয়। যখন কোন এক্সেপশন ঘটে তখন এই এক্সেপশনের যাবতীয় তথ্য NSException ক্লাসের ইনস্ট্যান্স থেকেই পাওয়া যায়। NSException ক্লাসের ৩টি প্রধান প্রোপার্টি নিচে দেওয়া হলঃ

  • name ঃ এক্সেপশনের নাম (ডাটাটাইপ: NSString ) যা সকল এক্সেপশনের জন্য আলাদা
  • reason ঃ এক্সেপশনের সহজবোধ্য বর্ননা ( ডাটাটাইপ : NSString)
  • userInfo ঃ NSDictionary টাইপের অবজেক্ট। এই অবজেক্টে কি-ভ্যালু রিলেশনশিপ দিয়ে এক্সেপশনের অতিরিক্ত তথ্য থাকে

এক্সেপশন হ্যান্ডেলিং (Exception Handling) ঃ
নিচের মত করে একটি নতুন ফ্রেশ প্রজেক্টের main.m ফাইলটি এডিট করে ফেলিঃ

// main.m

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        // menuList of the restaurant
        NSArray *menuList = @[@"Vegetable Rice", @"Chicken Toast", @"Thai Soup"];
        
        NSLog(@"1st Item Name is %@",  menuList[0]); // Vegetable Rice
        NSLog(@"2nd Item Name is %@",  menuList[1]);  // Chicken Toast
        NSLog(@"3rd Item Name is %@",  menuList[2]);; // Thai Soup
        NSLog(@"3rd Item Name is %@",  menuList[3]);  // throughs exception
    }
    return 0;
}

উপরের প্রোগ্রামটিতে প্রথমেই NSArray টাইপ একটি অ্যারে লিস্ট নেওয়া হয়েছে যাতে ৩ টি আইটেম আছে। আমরা জানি যে অবজেকটিভ সি এবং প্রায় সকল প্রোগ্রামিং লাঙ্গুয়েজেই ০ থেকে ইনডেক্সিং শুরু হয়। সুতরাং menuList অ্যারেতে ০ থেকে ২ এই তিনটি ইনডেক্স রয়েছে। কিন্তু প্রোগ্রামটিতে ০ থেকে ৩ পর্যন্ত ইনডেক্স এক্সেস করা হয়েছে। প্রোগ্রামটিতে যদিও কোন সিন্ট্যাক্স এরর নেই তবুও এই প্রোগ্রামটি রান করানো হলে কনসোলে নিচের মত একটি মেসেজ ডিসপ্লে হবে।

2014-06-05 23:14:01.320 restaurant[598:303] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 3 beyond bounds [0 .. 2]'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff9366641c __exceptionPreprocess + 172
	1   libobjc.A.dylib                     0x00007fff8e791e75 objc_exception_throw + 43
	2   CoreFoundation                      0x00007fff935521df -[__NSArrayI objectAtIndex:] + 175
	3   restaurant                          0x0000000100001798 main + 376
	4   libdyld.dylib                       0x00007fff8a1405fd start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)

প্রোগ্রামটি রান করলে এই এক্সেপশনটি থ্রো হয় এবং তারপর প্রোগ্রামটি ক্র্যাশ করে। কিভাবে হল? menuList এ তিনটি খাবারের নাম আছে যাদের ইনডেক্স যথাক্রমে ০, ১, ২। প্রোগ্রামটি কম্পাইল করলে কোন রকম এরর বা অন্য কোন ধরনের অস্বাভাবিক ঘটনা ঘটে না। কিন্তু কম্পাইলেশনের পর রান করালে শেষলাইনটি এক্সিকিউশনের সময় menuList[3] দিয়ে menuList এর ৩ নং ইনডেক্সের ভ্যালু এক্সেস করা হয়, তখনই এক্সেপশন (ব্যতিক্রম) ঘটে এবং প্রোগ্রামটি ক্র্যাশ করে কারন menuList অ্যারেতে ০-২ এই তিনটি মাত্র ইনডেক্সই আছে।

এভাবে অনেক কারনেই অ্যাপ্লিকেশনটি রান করানোর পর এক্সেপশন হয়ে ক্র্যাশ হতে পারে যা মোটেই কাম্য নয়। তাই প্রোগ্রামেই যেখানে যেখানে এধরনের এক্সেপশন হওয়ার সম্ভাবনা থাকে সেসব জায়গায় এক্সেপশন হ্যান্ডেল করতে হয়।

অন্যান্য হাই লেভেল প্রোগ্রামিং ল্যাঙ্গুয়েজের মতই অবজেকটিভ-সি তেও try-catch-finally প্যাটার্নে এক্সেপশন হ্যান্ডেল করা হয়। প্রথমেই কোডের যেসব লাইনে এক্সেপশন হতে পারে সেগুলোকে @try ব্লকের ভেতরে লেখা হয়। যদি কোন এক্সেপশন ঘটে তাহলে @catch ব্লকে এক্সেপশনের ধরন জেনে এক্সেপশনটিকে হ্যান্ডেল করতে হয়। অবশেষে @finally ব্লকের কোড গুলো এক্সিকিউট করা হয়। এক্সেপশন হোক আর না হোক @finally ব্লক এক্সিকিউট হবেই।

// main.m

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        // menuList of the restaurant
        NSArray *menuList = @[@"Vegetable Rice", @"Chicken Toast", @"Thai Soup"];
        
        @try{
          // write codes where exception can be thrown
          NSLog(@"4th Item Name is %@",  menuList[3]);
        }
        @catch(NSException *theException){
            // exception occurred. Lets handle this exception
            NSLog(@"An Exception named %@ has occurred@",  theException.name);
            NSLog(@"Some more details about the exception : %@",  theException.reason);
        }
        @finally{
            NSLog(@"Continues without crashing the application");
        }
        
        NSLog(@"This is being displayed. That means this application didn't crash.");
    }
    return 0;
}

উপরের main.m কে রান করলে @try ব্লকে menuList এর ৪র্থ আইটেম এক্সেস করার সাথে সাথেই এক্সেপশন থ্রো হয়। @catch ব্লকে NSException এর *theException এ এক্সেপশনটি অ্যাসাইন হয়। NSException ক্লাসের অবজেক্ট theException এর প্রোপার্টিগুলো name, reason, userInfo তে এক্সেপশনটি সম্পর্কে তথ্য পাওয়া যাচ্ছে।

রিয়েল লাইফ অ্যাপ্লিকেশনগুলো তে @catch ব্লকে সাধারনত এক্সেপশনের name ও reason থেকে তথ্য নিয়ে সমস্যার সমাধান করে অথবা ইউজারকে সহজবোধ্য এরর মেসেজ দিয়ে প্রোগ্রামটি কে নরমালি এক্সিট/কন্টিনিউ করা হয়।

অবজেকটিভ-সি এর এক্সেপশন হ্যান্ডেলিং খুব বেশী ইফিশিয়েন্ট না হওয়ায় @try/@catch ব্লক ব্যবহার না করে যদি সাধারন If স্টেইটমেন্ট দিয়ে চেক করে সমস্যা বাইপাস করা যায় তাহলে সেটা করাই ভাল। একান্তই যদি দরকার পড়ে সেক্ষেত্রে উপরের মত @catch ব্লকে ইউজার কে এক্সেপশনের সহজবোধ্য এরর মেসেজ দিয়ে প্রোগ্রামটি এক্সিট করে দেওয়া যেতে পারে।

// main.m

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        // menuList of the restaurant
        NSArray *menuList = @[@"Vegetable Rice", @"Chicken Toast", @"Thai Soup"];
        
        int selectedIndex = 3;
        
        if(selectedIndex < [menuList count]){
            NSLog(@"4th Item Name is %@",  menuList[selectedIndex]);
        }
        else
        {
            //Exception is avoided
        }
    }
    return 0;
}

এই প্রোগ্রামটিতে শুরুতেই selectedIndex এর ভ্যালু চেক করে নেওয়া হচ্ছে। যদি selectedIndex টি menuList এর count এর চেয়ে ছোট হয় তাহলে ওই ইনডেক্সের ভ্যালু এক্সেস করা হচ্ছে। selectedIndex এর ভ্যালু যদি menuList এর count এর সমান বা বড় হয় তাহলে ওই ইনডেক্সের ভ্যালু এক্সেস না করে লাইনটিকে বাইপাস করা হচ্ছে।

বিল্ট-ইন এক্সেপশন (Built-in Exceptions) ঃ
স্ট্যান্ডার্ড iOS ও OS X ফ্রেমওয়ার্ক এ অনেকগুলো বিল্ট-ইন এক্সেপশন রয়েছে যেগুলো দিয়ে মোটামুটি সবরকম এক্সেপশন @catch ব্লকে ধরা যায়। সবচেয়ে বেশী ব্যবহৃত এক্সেপশন গুলো সম্পর্কে নিচে ২-১ লাইন লেখা হলঃ

  • NSRangeException ঃ অ্যারে বা কালেকশনের বাইরের ইনডেক্সের ইলিমেন্ট এক্সেস করলে NSRangeException থ্রো হয়।
  • NSInvalidArgumentException ঃ মেথডে ইনভ্যালিড আরগুমেন্ট পাঠানো হলে NSInvalidArgumentException থ্রো হয়।
  • NSInternalInconsistencyException ঃ ইন্টারনালী যদি কোন অস্বাভাবিক কিছু ঘটে যা প্রোগ্রাম ক্র্যাশ করায় তখন NSInvalidArgumentException হয়।
  • NSGenericException ঃ যখন কোন স্পেসিফিক এক্সেপশন না ধরে জেনেরিক এক্সেপশন ধরতে হয় তখন NSGenericException ক্লাসের অবজেক্ট দিয়ে এক্সেপশনটি ধরা হয়। অর্থাত যেকোন কারনেই এক্সেপশন থ্রো হউক না কেন NSGenericException থ্রো হয়।

খেয়াল করুন যে এই সব এক্সেপশন গুলোর ভ্যালু স্ট্রিং। এদের NSException ক্লাসের সাবক্লাস ভাবার দরকার নেই। তাই কোন স্পেসিফিক এক্সেপশন পেতে চাইলে name প্রোপার্টি চেক করতে হবে।

// main.m
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        // menuList of the restaurant
        NSArray *menuList = @[@"Vegetable Rice", @"Chicken Toast", @"Thai Soup"];
        
        @try{
            // write codes where exception can be thrown
            NSLog(@"4th Item Name is %@",  menuList[3]);
        }
        @catch(NSException *theException){
            // exception occured. Lets handle this exception
           if(theException.name == NSRangeException)
               NSLog(@"NSRangeException Exception is caught.");
            
        }
        @finally{
            NSLog(@"Continues without crashing the application");
        }
        
        NSLog(@"This is being displayed. That means this application didnt crashed.");
    }
    return 0;
}

কাস্টম এক্সেপশন (Custom Exceptions) ঃ
@throw ডিরেক্টিভ দিয়ে সহজেই কাস্টম এক্সেপশন থ্রো করানো যায়। যেখানে এক্সেপশন থ্রো হবে সেখানে NSException ক্লাসের ইন্স্ট্যান্স বানিয়ে exceptionWithName:reason:userInfo: ফ্যাক্টরী মেথড দিয়ে কাস্টম এক্সেপশন ইনস্ট্যান্স বানানো যায়। বোঝার জন্য নিচের উদাহরনটি দেখিঃ

// main.m
#import <Foundation/Foundation.h>

NSString *getRandomItemFromMenuList(NSArray *menuList) {
    int max = (int)[menuList count];
    if (max == 0) {
        NSException *e = [NSException
                          exceptionWithName:@"EmptyMenuListException"
                          reason:@"*** The list has no food!"
                          userInfo:nil];
        @throw e;
    }
    int randomIndex = arc4random_uniform(max);
    return menuList[randomIndex];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        @try {
            NSString *foodItem = getRandomItemFromMenuList(@[]);
            NSLog(@"The selected food is: %@", foodItem);
        } @catch(NSException *theException) {
            if (theException.name == @"EmptyMenuListException") {
                NSLog(@"Caught an EmptyMenuListException");
            } else {
                NSLog(@"Ignored a %@ exception", theException);
                @throw;
            }
        }
    }    return 0;
}

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

এরর (Errors)ঃ
প্রোগ্রামে এমন অনেক অপারেশন থাকতে পারে যেগুলো ফেইল করলেও প্রোগ্রাম ক্র্যাশ করে না। এধরনের সমস্যাগুলো কে এরর বলা হয়। যেমন রানটাইমে ইউজারকে যদি কোন ফাইল ইনপুট দিতে বলা হয়, সেক্ষেত্রে যদি ফাইলটি করাপ্টেড থাকে বা ওই লোকেশনে ফাইলটি না পাওয়া যায় তাহলে এরর হয় কারন প্রোগ্রামটি ফাইলটিকে পড়তে পারে না। এরর হলে এক্সেপশনের মত প্রোগ্রাম ক্রাশ করে না। যখন কোন এরর হবে তখন ইউজারকে বোধগম্য মেসেজ এবং ইনস্ট্রাকশন দিয়ে প্রোগ্রামটি কন্টিনিও করানো কে এরর হ্যান্ডেলিং বলা হয়।

NSException ক্লাসের মতই NSError ক্লাসের ইনস্ট্যান্স দিয়ে ফেইল হওয়া অপারেশন তথা এরর সম্পর্কে যাবতীয় তথ্য পাওয়া যায়। NSError ক্লাসের প্রধান প্রোপার্টিগুলো নিচে দেওয়া হলঃ

  • domain (NSString) ঃ সব এরর গুলো কে কতগুলো ডোমেইনে ভাগ করা হয়েছে। এই ডোমেইন দিয়ে এরর গুলোকে সুন্দরভাবে hierarchy তে সাজানো হয়েছে।
  • code (NSInteger) ঃ code হল একটি নির্দিষ্ট ডোমেইনে এররের ইউনিক আইডি (ID)
  • userInfo (NSDictionary) ঃ NSException ক্লাসের মতই userInfo এরর সম্পর্কে অতিরিক্ত তথ্য রাখে

আগেই বলেছি যে userInfo তে তথ্যগুলো কি-ভ্যালু পেয়ার (key-value pair) হিসেবে থাকে। আবার NSException এর তুলনায় NSError এ বেশী সংখ্যক তথ্য থাকে। গুরুত্বপুর্ন কয়েকটি কি (key) হল NSLocalizedDescriptionKey, NSLocalizedFailureReasonErrorKey, NSUnderlyingErrorKey। মজার ব্যপার হল এররের ধরন অনুযায়ী userInfo ডিকশনারীর কি(key) গুলো কমবেশী হয়। যেমন ফাইল লোড করতে গিয়ে যে এরর হয় তাতে NSFilePathErrorKey নামে একটি কি(key) থাকে যাতে ফাইলটির লোকেশন থাকে।

এরর হ্যান্ডেলিং (Handling Errors) ঃ
এরর হ্যান্ডেল করার জন্য এক্সেপশনের মত @try-@catch ব্লক বা আলাদা কিছু লিখতে হয় না। যেসব মেথড বা ফাংশনে এরর হতে পারে সেসবে একটি বাড়তি আর্গুমেন্ট হিসেবে NSError ক্লাসের অবজেক্ট পাঠানো হয়। মেথড অথবা ফাংশনটি যদি কোন কারনে ফেইল করে (কোন এরর হয়) তাহলে NO অথবা nil রিটার্ন করে এবং NSError ক্লাসের এই অবজেক্টটিতে এরর ডিটেইলস অ্যাসাইন হয়। কিন্তু যদি মেথড/ফাংশনটি সাকসেসফুলি এক্সিকিউট হয় তাহলে এই মেথডটি রিটার্ন টাইপ অনুযায়ী যে ডাটা রিটার্ন করার কথা ছিল তাই রিটার্ন করে।

অবজেক্টিভ সি এর বেশিরভাগ মেথডগুলো আর্গুমেন্ট হিসেবে NSError ক্লাসের অবজেক্টের Indirect রেফারেন্স নিয়ে থাকে। Indirect রেফারেন্স হল মুলত পয়েন্টার যা অন্য পয়েন্টারকে পয়েন্ট করে থাকে। এতে যে সুবিধা পাওয়া যায় তা হল, মেথডটি ওই আর্গুমেন্টটিকে NSError ক্লাসের সম্পুর্ন নতুন একটি ইনস্ট্যান্সে পয়েন্ট করাতে পারে। মেথডের মধ্যে ওই পয়েন্টার যে ইনস্ট্যান্সকে পয়েন্ট করে আছে তার প্রোপার্টিগুলোতে ভ্যালু অ্যাসাইন করা হয়। মেথডের এরর আর্গুমেন্টটি যদি Indirect রেফারেন্স এক্সেপ্ট করে তাহলে নিচের মত দুইবার পয়েন্টার চিহ্ন (double-pointer notation) দিয়ে ইনস্ট্যান্স পাওয়া যায়।

(NSError **)error

শুরুতেই এরর হ্যান্ডেল করার একটি সাদামাটা প্রোগ্রাম দেখা যাক। নিচের main.m ফাইলের মত করে আমাদের main.m ফাইলটি এডিট করি। এ প্রোগ্রামে শুরুতেই path নামের একটি ভ্যারিয়েবলে একটি ফাইলের পাথ দেওয়া হয়েছে। মুলত আমরা এই file.md ফাইলটির ডাটাগুলো পড়তে চাচ্ছি। এরপর NSError ক্লাসের একটি ইনস্ট্যান্স error নেওয়া হল। পরবর্তী লাইনে stringWithContentsOfFile:encoding:error: মেথডকে কিছু আর্গুমেন্ট সহ কল করা হয়েছে। এই মেথডটি অবজেক্টিভ সি এর বিল্টইন মেথড যা প্রথম আর্গুমেন্টের লোকেশন থেকে ফাইল লোড করে ফাইলের ডাটা (NSString টাইপ) রিটার্ন করে। তৃতীয় প্যারামিটারটি NSError ক্লাসের অবজেক্ট যা রেফারেন্স অপারেটর (&) দিয়ে পাঠানো হয়। &error মুলত একটি পয়েন্টার যা NSError ক্লাসের ইনস্ট্যান্স *error কে পয়েন্ট করে থাকবে।

// main.m
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
    
        NSString *path = @"/path/to/file.md";
        
        NSError *error;
        NSString *content = [NSString stringWithContentsOfFile: path
                                                      encoding: NSUTF8StringEncoding
                                                         error: &error];
        
        if (content == nil) {
            // Method failed
            NSLog(@"Error loading file %@!", path);
            NSLog(@"Domain: %@", [error domain]);
            NSLog(@"Error Code: %ld", [error code]);
            NSLog(@"Description: %@", [error localizedDescription]);
            NSLog(@"Reason: %@", [error localizedFailureReason]);
            NSLog(@"Recovery procedure: %@",[error localizedRecoverySuggestion]);
        } else {
            // Method succeeded
            NSLog(@"Date Loaded from file: %@", content);
        }
    }
    return 0;
}

file.md ফাইলটি যদি ওই লোকেশনে না থাকে তাহলে stringWithContentsOfFile মেথডটি nil বা NO রিটার্ন করে এবং error অবজেক্টে এররের ডিটেইলস অ্যাসাইন হয়। অন্যথায় মেথডটি ফাইলের কনটেন্ট রিড করে তা রিটার্ন করে যা NSString টাইপ content ভ্যারিয়েবলে অ্যাসাইন হয়। এরপর content এর ভ্যালু চেক করে যদি NO বা nil হয় তাহলে error অবজেক্টের প্রোপার্টিগুলো code, domain, localizedDescription, localizedFailureReason ইত্যাদি এক্সেস করে এরর সম্পর্কে যাবতীয় তথ্য পাওয়া যায়।

যদি এমন হয় যে আপনি শুধু জানতে চাচ্ছেন যে এরর হয়েছে কিনা; এররের কারন না জানলেও চলবে সেক্ষেত্রে error ইনস্ট্যান্সের জন্য শুধুমাত্র NULL পাঠালেই চলবে। নিচের কোডটুকু খেয়াল করিঃ

        NSString *content = [NSString stringWithContentsOfFile:fileToLoad
                                                      encoding:NSUTF8StringEncoding
                                                         error: NULL];
        
        if (content == nil) {
           // error occured  
        } else {
            // Method succeeded
           }

বিল্ট-ইন এরর (Built-in Errors) ঃ
NSException ক্লাসের মত NSError ক্লাসের ও কিছু বিল্ট-ইন এরর ডোমেইন রয়েছে যা দিয়ে এরর গুলো কে রিপ্রেজেন্ট করা যায়। প্রধান কয়েকটি বিল্ট-ইন এরর ডোমেইন এর নাম নিচে দেওয়া হলঃ

  • NSMachErrorDomain
  • NSPOSIXErrorDomain
  • NSOSStatusErrorDomain
  • NSCocoaErrorDomain

আমাদের প্রোগ্রামগুলোতে সবচেয়ে বেশী যে এরর গুলো ঘটবে সেগুলো NSCocoaErrorDomain এর অন্তর্ভুক্ত। তারপরও চাইলে আরো লো-লেভেলের এরর ডোমেইনের এরর ইনফরমেশন পাওয়া যাবে। যেমন আমাদের উপরের main.m ফাইলে যদি নিচের লাইনটি লেখি তাহলে NSPOSIXErrorDomain এর এরর ইনফরমেশন পাওয়া যাবে।

 NSLog(@"Lower Level Domain Error : %@",error.userInfo[NSUnderlyingErrorKey]);

এই লাইনটির আউটপুট হবে
Lower Level Domain Error : Error Domain=NSPOSIXErrorDomain Code=2 “The operation couldn’t be completed. No such file or directory”

আগেই বলেছি যে সকল এরর গুলো কতগুলো ডোমেইন এ ভাগ করা হয়েছে। প্রত্যেক ডোমেইনের অন্তর্গত সকল এররের নির্দিষ্ট ও ইউনিক কোড (code) থাকে যা দিয়ে স্পেসিফিক এরর ইনফরমেশন পাওয়া যায়। Foundation Constants Reference ডকুমেন্টটিতে কিছু enum দিয়ে NSCocoaErrorDomain ডোমেইনের প্রায় সকল কোডের (code) বর্ননা দেওয়া আছে। উদাহরন স্বরুপ নিচের প্রোগ্রামে দেখা যাচ্ছে NSCocoaErrorDomain এর NSFileReadNoSuchFileError নামক কোডের এরর খোজার জন্য প্রোগ্রামটি ওত পেতে বসে আছে।

if (content == nil) {
    if ([error.domain isEqualToString:@"NSCocoaErrorDomain"] &&
        error.code == NSFileReadNoSuchFileError) {
        NSLog(@"That file doesn't exist!");
        NSLog(@"Path: %@", [[error userInfo] objectForKey:NSFilePathErrorKey]);
    } else {
        NSLog(@"Some other kind of read occurred");
    }
}

কাস্টম এরর (Custom Errors) ঃ
বড় সাইজের IOS ও OS X এর অ্যাপ্লিকেশনে বিভিন্ন মেথডে বিভিন্ন ধরনের এরর হওয়ার সম্ভাবনা থাকে যা সংখ্যায় খুব কম নয়। একারনে প্রোগ্রামারের সুবিধার্থে ও ইউজারের কাছে আরও সহজবোধ্য মেসেজ পাঠানোর উদ্দেশ্যে কাস্টম এরর বানানো হয়। বেস্ট প্র্যাকটিস হিসেবে সব এররগুলো কে একটি আলাদা হেডার ফাইলে (For Ex- RestaurantErrors.h) লেখা হয়। পরবর্তীতে প্রোগ্রামের যেসব ইমপ্লিমেনটেশন ফাইলে এই কাস্টম এরর গুলো ব্যবহার করা হবে সেখানে ইমপোর্ট করে নিলেই হবে।

//RestaurantErrors.h
NSString *RestaurantErrorDomain = @"net.Nuhil.Restaurant.ErrorDomain";

enum {
    MenuListNotLoadedError,
    MenuListEmptyError,
    RestaurantInternalError
};

কাস্টম এরর ডোমেইনের নাম যেকোন কিছুই হতে পারে, তবে CompanyName.ProjectOrFrameWorkName.ErrorDomain রাখাটাই বেস্ট প্র্যাকটিস। এরর কোড কনস্ট্যান্ট গুলো কে enum এর মধ্যে ডিফাইন করা হয়।

//  main.m
#import <Foundation/Foundation.h>
#import "RestaurantErrors.h"

NSString *getFoodTitleWithId( int foodId, NSError **error) {
   
    NSArray *Food = @[];
    
    int max = [Food count];
    
    if (max == 0) {
        if (error != NULL) {
            NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"MenuList"
                                       " Currently Empty."};
            
            *error = [NSError errorWithDomain: RestaurantErrorDomain
                                         code: MenuListEmptyError
                                     userInfo: userInfo];
        }
        return nil;
    }
    return Food[foodId];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int searchFor = 2;
        NSError *error;
        NSString *title = getFoodTitleWithId(searchFor, &error);
        
        if (title == nil) {
            // If Failed
            NSLog(@"Title could not be found");
            NSLog(@"Domain: %@", error.domain);
            NSLog(@"Error Code: %ld", error.code);
            NSLog(@"Description: %@", [error localizedDescription]);
            
        } else {
            // Succeeded
            NSLog(@"Selected Food Title Found!");
            NSLog(@"%@", title);
        }
    }
    return 0;
}

main.m এর main মেথড এ দেখা যাচ্ছে যে আরগুমেন্ট হিসেবে একটি ইনডেক্স বা আইডি (searchFor) এবং NSError ক্লাসের একটি Indirect রেফারেন্স &error সহ getFoodTitleWithId কে কল করা হযেছে। মেথডটিতে &error পাঠানোর উদ্দেশ্য হল যদি মেথডের ভেতর কোন এরর হয় তাহলে এররের প্রোপার্টিগুলোর ভ্যালু যাতে এই error ইনস্ট্যান্স এ অ্যাসাইন হয়। যদি ২য় আরগুমেন্টে NULL পাঠানো হত তাহলে এরর সম্পর্কিত কোন ইনফরমেশন আর পাওয়া যেত না। যদি এরর হত তাহলে মেথডটি শুধুমাত্র NO অথবা nil রিটার্ন করত।

getFoodTitleWithId মেথডে FoodId তে 2 এবং NSArray ক্লাসের Indirect রেফারেন্স প্যারামিটার হিসেবে এসেছে। মেথডের শুরুতেই NSArray টাইপ একটি ভ্যারিয়েবল ডিক্লেয়ার করা হয়েছে যাতে আইটেম সংখ্যা শুন্য। অর্থাত পরের লাইনে max এর ভ্যালু শুন্য হবে। এপর্যায়ে আমরা চাচ্ছি যেহেতু Food লিস্টটিতে কোন আইটেম নেই তাই একটি কাস্টম এরর থ্রো করাব। তাই যখন max এর ভ্যালু শুন্য এবং NSError এর Indirect রেফারেন্সটি NULL নাহয় তখন কাস্টম এররটির প্রোপার্টিগুলোর ভ্যালু পপুলেট করা হচ্ছে এবং অবশেষে nil রিটার্ন করে দেওয়া হয়েছে।

উপরের প্রোগ্রামটি রান করালে নিচের মত আউটপুট আসবে।

2014-06-10 00:56:19.817 restaurant[904:303] Title could not be found
2014-06-10 00:56:19.819 restaurant[904:303] Domain: net.Nuhil.Restaurant.ErrorDomain
2014-06-10 00:56:19.819 restaurant[904:303] Error Code: 1
2014-06-10 00:56:19.820 restaurant[904:303] Description: MenuList Currently Empty.
Program ended with exit code: 0

RestaurantErrorDomain এ এরর কোডগুলো যেহেতু enum টাইপ সেহেতু এরর কোডগুলোর ভ্যালুগুলো শুন্য থেকে শুরু করে ২ পর্যন্ত। তাই MenuListEmptyError এর ভ্যালু ১।

পরিশিষ্ঠঃ
এই চ্যাপ্টারের মাধ্যমেই আমাদের ব্যাসিক ওব্জেক্টিভ-সি লার্নিং সেকশন শেষ হয়ে যাচ্ছে। এর পরের সেকশনে আসছে রিয়াল লাইফ গ্রাফিক্যাল অ্যাপ্লিকেশন তৈরি সম্পর্কিত ১০টি চ্যাপ্টার। উক্ত সেকশন শেষ হবার পর আসবে ব্যাসিক সুইফ্ট ল্যাঙ্গুয়েজ লার্নিং সেকশন।

অনেকেই হয়ত ভেবে থাকতে পারেন যে, Apple যেহেতু iOS অ্যাপ ডেভেলপমেন্ট এর জন্য তাদের নতুন ল্যাঙ্গুয়েজ ঘোষণা করেছে তাহলে আর অবজেক্টিভ শিখে কি লাভ। কিন্তু এ ধারনা একদম ভুল। প্রথমত, এখন পর্যন্ত অ্যাপ স্টোরে ১০ লাখেরও বেশি অ্যাপ জীবন্ত আছে যেগুলো অব্জেক্টিভ-সি তে করা। সেই অ্যাপ গুলোর মেইন্টেইনেন্স, আপডেট, বাগ ফিক্সিং ইত্যাদির জন্য আরও সামনে ৬/৭ বছর iOS এবং অব্জেক্টিভ-সি এর ব্যবহার চলবেই। দ্বিতীয়ত, তাদের নতুন ঘোষণা মতে, একটি প্রজেক্টে অথবা অ্যাপ্লিকেশনে একি সাথে অবজেক্টিভ-সি এবং সুইফ্ট ল্যাঙ্গুয়েজ কোড থাকতে পারে এবং রান হতে পারে। অর্থাৎ তারা চাচ্ছে, আস্তে আস্তে অব্জেক্টিভ-সি এর জায়গাটা কমে আসুক, একবারে না। তৃতীয়ত, অনেক ডেভেলপার বা কোম্পানি হয়ত চাইতেই পারে যে তাদের সামনের অ্যাপ্লিকেশনগুলো তারা অবেজক্টিভ-সি দিয়েই করবে কারন সুইফ্ট -এ দক্ষ ডেভেলপার এই মুহূর্তে হয়ত তারা ব্যবস্থা করতে পারবেন না। চতুর্থত, হাজার হাজার থার্ড পার্টি লাইব্রেরী, ফ্রেমওয়ার্কও কিন্তু অব্জেক্টীভ-সি তে করা যেগুলো হয়ত আপনি আপনার নেক্সট প্রজেক্টে ব্যবহার করবেন বলে ভেবে রেখেছেন। অন্যদিকে, অনেকেই হয়ত চাইবেন তাদের পরবর্তী প্রজেক্টটি নতুন ল্যাঙ্গুয়েজ দিয়ে করতে। অর্থাৎ আপনি যদি ফ্রেশ ভাবে iOS অ্যাপ ডেভেলপমেন্ট শুরু করতে চান তাহলে শুধুমাত্র সুইফ্ট শেখা শুরু করতে পারেন কিন্তু যদি ক্যারিয়ার হিসেবে দেখতে চান তাহলে আপনাকে দুটো ল্যাঙ্গুয়েজ সম্পর্কে ধারনা রাখতেই হবে।

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

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