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

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 অ্যাপ ডেভেলপমেন্ট শুরু করতে চান তাহলে শুধুমাত্র সুইফ্ট শেখা শুরু করতে পারেন কিন্তু যদি ক্যারিয়ার হিসেবে দেখতে চান তাহলে আপনাকে দুটো ল্যাঙ্গুয়েজ সম্পর্কে ধারনা রাখতেই হবে।

৭- অবজেক্টিভ সি (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) এর প্রোটোকল ও এর ব্যবহার

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

Standard

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

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

সাধারণত, একটি অবজেক্টের প্রোপার্টিগুলো অন্য আরেকটি অবজেক্টকে তাদের অবস্থা পরিবর্তন এবং যাচাই করতে দেয়। কিন্ত ভালভাবে ডিজাইন করা কোন অবজেক্ট অরিয়েনটেড প্রোগ্রামে অবজেক্টের ইন্টারনাল অবস্থা জানা বা পরিবর্তন করা সহজ নয়। এর জন্য প্রয়োজন Accessor Methods তথা getters এবং setters।

Objective C তে @property এর কাজ হল অটোমেটিক্যালি এই Accessor method গুলো জেনারেট করে সহজে প্রোপার্টি তৈরী এবং কনফিগার করার ব্যবস্থা করা। যেসব প্রোপার্টি গুলো Public হবে শুধুমাত্র সেগুলোর জন্যই @property ডিরেকটিভ ব্যবহার করা হয়। ফলে সহজেই সিমান্টিক লেভেলে প্রোপার্টির behavior ঠিক করে দেওয়া যায় এবং এটা একই সাথে ওই প্রোপার্টির ইম্পলিমেন্টেশন ডিটেইলসও ম্যানেজ করে।

@property ডিরেকটিভঃ
প্রথমেই দেখা যাক @property ডিরেকটিভ ব্যবহার করলে কি ঘটনা ঘটে। গত অধ্যায়ের সাথে মিল রেখে একটি Food ক্লাসের জন্য একটি সহজ ইন্টারফেস এবং ইম্পলিমেন্টেশন লেখা যাক।

//  Food.h

#import <Foundation/Foundation.h>

@interface Food : NSObject

@property NSString *item;

@end

 

//  Food.m

#import "Food.h"

@implementation Food

@end

Food ইন্টারফেসের একটি প্রোপার্টি item যার ডাটাটাইপ NSString। যেহেতু অবজেকটিভ সি তে সকল ভ্যারিয়েবলকে পয়েন্টার হিসেবে ব্যবহার করা হয় তাই item এর আগে পয়েন্টার চিহ্ন ব্যবহার করা হয়েছে। লক্ষনীয় ব্যপার হল এখানে item ডিক্লেয়ার করার আগে @property ডিরেকটিভ লেখা আছে।

কোন ভ্যারিয়েবলকে @property ডিরেকটিভ হিসেবে ডিক্লেয়ার করা হলে, তার জন্য অটোমেটিক্যালি Accessor methods জেনারেট হয়ে যায় এবং নিজ ক্লাসের ইন্টারফেস ও ইমপ্লিমেন্টেশনে থাকা অন্যান্য মেথড গুলোর মতই ওই অদৃশ্য গেটার এবং সেটার মেথড গুলোকে কল করা যায়। item প্রোপার্টির জন্য আসলে নিচের মত গেটার এবং সেটার মেথড তৈরি হয়ে গেছে যেগুলো আমরা একটু পরেই মুল প্রোগ্রামে ব্যবহার করতে পারবো।
বিঃ দ্রঃ কাস্টম সেটার ও গেটার তৈরির করার জন্য Food.m -এ এই গেটার এবং সেটারকে ওভাররাইডও করা যায়। তবে কাস্টম সেটার ও গেটার তৈরির জন্য @synthesize ডিরেকটিভ আবশ্যক।

- (NSString *)item {
    return item;
}
- (void)setItem:(NSString *)newValue {
    item = newValue;
}

এবার আমাদের main.m ফাইলকে নিচের মত করে আপডেট করুন।

// main.m

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

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

        Food *food = [[Food alloc] init];
        food.item = @"A bengali dish!"; // Under hood: [food setItem: @"A bengali dish!"]
        
        NSLog(@"Eating a %@", food.item); // Under hood: [food item]
        
    }
    return 0;
}

ডট নোটেশন দিয়ে প্রোপার্টি এক্সেস করার সময় মুলত ব্যাকএন্ডে accessor মেথড গুলোতেই ট্রানস্লেটেড হয়। ফলে food.item এ কোন স্ট্রিং এসাইন করলে setItem: মেথড কল হয় এবং food.item দিয়ে ডাটা চাইলে item: মেথডটাই কল হয়।

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

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

@property (getter=isOpen) BOOL open;

এ অবস্থায় জেনারেট হওয়া এক্সেসর মেথড গুলোকে isOpen এবং setOpen দিয়ে কল করা হবে/যাবে। আসুন আমাদের Food.h, Food.m এবং main.m কে নিচের মত করে সাজাই,

// Food.h

#import <Foundation/Foundation.h>

@interface Food : NSObject

@property (getter=isOpen) BOOL open;

@end
// Food.m

#import "Food.h"

@implementation Food


@end
// main.m

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

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

        Food *place = [[Food alloc] init];
        place.open = NO; // Under hood: [food setOpen]
        
        // NSLog(@"Is the restaurant open?: %hhd", [place open]); // Error: No method exists
        
        NSLog(@"Is the restaurant open?: %hhd", [place isOpen]); // Works!
        
        NSLog(@"Is the restaurant open?: %hhd", place.open);  // Works! Under hood: [place isOpen]
        
    }
    return 0;
}

খেয়াল করুন এখন কিন্তু আর গেটারের ব্যাকএন্ড স্টাইল [place open] কাজ করবে না বরং [place isOpen] কাজ করবে যেখানে isOpen কে গেটার হিসেবে বলে দিয়েছি আমরা। আবার, এখনো কিন্তু পাবলিক প্রোপার্টিগুলোকে শুধু প্রোপার্টির নাম দিয়েও এক্সেস করা যাচ্ছে।
বিঃ দ্রঃ শুধুমাত্র এই অ্যাট্রিবিউট দুটি আর্গুমেন্ট এক্সেপ্ট করে। অন্য সকল অ্যাট্রিবিউটগুলো বুলিয়ান ফ্ল্যাগ।

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

//  Food.h

#import <Foundation/Foundation.h>

@interface Food : NSObject

@property (getter=isOpen, readonly) BOOL open;

@end
//  Food.m

#import "Food.h"

@implementation Food


@end
// main.m

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

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

        Food *place = [[Food alloc] init];
        place.open = YES; // Error: can't assign to readonly property
        
    }
    return 0;
}

এ অবস্থায় Xcode -ই আপনাকে আটকিয়ে দিবে নিচের মত,
Screen Shot 2014-05-20 at 6.12.28 PM

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

//  Food.h

#import <Foundation/Foundation.h>

@interface Food : NSObject

@property (getter=isOpen, readonly) BOOL open;
-(void)openTheRestaurant;

@end
//  Food.m

#import "Food.h"

@implementation Food 

-(void)openTheRestaurant
{
    // N.B. We can use _open directly, cause @property also creates an 
    // instance variable for us!
    _open = YES; 
}

@end
// main.m

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

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

        Food *place = [[Food alloc] init];
        [place openTheRestaurant]; // Setting the new value by this imethod

        NSLog(@"Is the restaurant open?: %hhd", place.open);
    }
    return 0;
}

এটি রান করালে কনসোলে Is the restaurant open?:1 দেখতে পাবেন অর্থাৎ ওই রিডঅনলি বুলিয়ান প্রোপার্টিটার ভ্যালু চেঞ্জ করা গেছে যেটা বাই ডিফল্ট 0 থাকে।

ননঅ্যাটমিক (nonatomic) অ্যাট্রিবিউটঃ
মাল্টিথ্রেডেড এনভাইরনমেন্টে (যখন কোন অ্যাপ্লিকেশনে একাধিক থ্রেড থাকে) getter এবং setter মেথড গুলো একাধিকবার কল হতে পারে। অর্থাত অন্য কোন অপারেশন বর্তমানে এক্সিকিউট হচ্ছে এমন কোন getter/setter মেথডের এক্সিকিউশনে ইন্টারাপ্ট করে রেজাল্টে প্রভাব ফেলতে পারে। Atomic প্রোপার্টি অবজেক্টকে লক করে দেয় যাতে করে getter/setter মেথড গুলো কমপ্লিট ভ্যালু নিয়ে কাজ করতে পারে।

@property ডিরেকটিভ দিয়ে ডিক্লেয়ার করা প্রোপার্টি বাই ডিফল্ট Atomic হয়। যা আমাদের মাথার উপর থেকে ম্যানুয়ালী Atomicity নিশ্চিত করার বোঝা কমিয়ে দিয়েছে। কিন্তু যদি আপনার অ্যাপ্লিকেশনটি একটিমাত্র থ্রেড নিয়ে হয় অথবা আপনি নিজের মত করে Thread-Safety ইমপ্লেমেন্ট করে থাকেন, তাহলে প্রোপার্টিটিকে nonatomic হিসেবে ডিক্লেয়ার করলেই চলে।

@property (nonatomic) NSString *item;

আরও একটি উল্লেখযোগ্য ব্যপার হল, Atomic প্রোপার্টিগুলোর getter এবং setter দুটি মেথডই অটো-জেনারেটেড হবে অথবা দুটি মেথডই ইউজার ডিফাইন্ড হবে। কিন্তু nonatomic প্রোপার্টি গুলোর জন্য কোন একটি ইউজার ডিফাইন্ড এবং অন্যটি অটো জেনারেটেড এরকম মিক্স হতে পারে।

মেমরী ম্যানেজমেন্ট (Memory Management)ঃ 
অবজেক্ট অরিয়েন্টেড ল্যাঙ্গুয়েজে, অবজেক্ট গুলো কম্পিউটারের মেমরী লোকেশনে স্টোর করা হয়। এখনো পর্যন্ত মেমরীই হল দুর্লভ রিসোর্স বিশেষ করে মোবাইল ডিভাইস গুলোর জন্য। মেমরী ম্যানেজমেন্টের মুল লক্ষ্যই হল কম্পিউটার বা মোবাইলের মেমরীর সঠিক ব্যবহার করা। অ্যাপ্লিকেশনটি অপ্রয়োজনীয় কোন মেমরী যাতে ব্যবহার করতে না পারে সেটা নিশ্চিত করাই মেমরী ম্যানেজমেন্টের কাজ।

বেশীরভাগ ল্যাঙ্গুয়েজেই garbage collection দিয়েই মেমরী ম্যানেজমেন্ট করা হয়। কিন্তু অবজেকটিভ সি (Objective C) অবজেক্ট ওনারশিপ (Object Ownership) দিয়ে মেমরী ম্যানেজ করে যা garbage collection এর চেয়ে অনেকবেশী ইফিশিয়েন্ট। যখন কোন প্রোগ্রাম কোন অবজেক্টের সাথে ইন্টারঅ্যাক্ট শুরু করে তখনই প্রোগ্রামটিকে এই অবজেক্টের ওনার (owner) বলা হয়। আবার যখন প্রোগ্রামটির কাজ শেষ হয়ে যায় অথবা প্রোগ্রামটির আর অবজেক্টের দরকার থাকে না তখন অবজেক্টটির owner হিসেবে প্রোগ্রামটি থাকেনা। একটি অবজেক্টের একাধিক owner থাকতে পারে। অর্থাত যখন কোন অবজেক্টের কমপক্ষে একটি ওনার থাকবে তখন বুঝতে হবে অবজেক্টটি ব্যবহার করা হচ্ছে। কোন অবজেক্টের যদি একটিও Owner না থাকে তখন বুঝতে হবে যে এই অবজেক্টের আর কোন কাজ নেই। তাই অপারেটিং সিস্টেম অবজেক্টটিকে ডেস্ট্রয় করে দিয়ে মেমরী ফ্রি করে নেয়।
AUTOMATIC REFERENCE COUNTING এর মাধ্যমে অপারেটিং সিস্টেম নিজে নিজেই মেমরী ম্যানেজমেন্টের কাজ গুলো করে নেয়। এসব নিয়ে আমাদের মাথা গরম করার কোন দরকার নেই।
কিন্তু আমাদেরকে অবশ্যই strong, weak এবং copy অ্যাট্রিবিউটগুলো সম্পর্কে জানতে হবে কারন এই অ্যাট্রিবিউটগুলোই কম্পাইলারকে অবজেক্টের রিলাশনশিপ কেমন হবে তা জানায়।

স্ট্রং (strong) এবং উইক (weak) অ্যাট্রিবিউটঃ
প্রথমেই দেখি কিভাবে একটি স্ট্রং অ্যাট্রিবিউটের প্রোপার্টি ডিক্লেয়ার করা যায়,

@property (nonatomic, strong) NSString *name;

আগেও একবার বলা হয়েছে যে, ARC বা Automatic Reference Counting হচ্ছে একটি স্বয়ংক্রিয় মেমোরি ম্যানেজমেন্ট ব্যবস্থা যার মাধ্যমে সিস্টেম স্বয়ংক্রিয় ভাবে যেকোনো অপ্রয়োজনীয় অবজেক্টের মেমোরি ফ্রি করে। আর এই ফ্রি করার ব্যপারটা সে ঠিক করে রেফারেন্সের নাম্বার (Count) এর উপর। যেটার রেফারেন্স যখন 0 হয়ে যায় তখনি তার মেমোরি ফ্রি/রিলিজ করে দেয়।

উপরে strong অ্যাট্রিবিউট হচ্ছে কোন অবজেক্টের এমন একটি স্ট্রং রেফারেন্স যা তাকে ডিঅ্যালোকেটেড হতে দেয় না। অর্থাৎ তার রেফারেন্স কাউন্ট হয় 0 এর চেয়ে বেশি বা 1, 2 … এরকম। এখানে name কে strong প্রোপার্টি হিসেবে ডিক্লেয়ার করা দুটি অর্থ বহন করেঃ
১। যখন আপনি name এর মধ্যে একটি অবজেক্ট অ্যাসাইন করবেন, তখন সেই অবজেক্টটির রেফারেন্স কাউন্ট বেড়ে যায়।
২। যখন অ্যাসাইন করা অবজেক্টটি যাকে name পয়েন্ট করে সেটাকে আপনি বা সিস্টেম কোনভাবে ডিঅ্যালোকেট করবেন তখনও name এর ভ্যালু আগের মতই থাকবে অর্থাৎ name আসলে এর মধ্যে অ্যাসাইন করা অবজেক্টটির মালিকানা পেয়ে গিয়েছিল; কারন name একটি strong Relationship.

এবার আসুন একটি উদাহরণ দেখি। প্রথমেই আমাদের বর্তমান প্রজেক্টে আরও একটি নতুন ক্লাস (NSObject এর সাব) যুক্ত করে নিন যার নাম Person. ফলে নতুন দুটি ফাইল পেয়ে যাবো আমরা যেগুলোর কোড আপডেট করে নিন নিচের মত,

// Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic) NSString *name;

@end

অর্থাৎ এই নতুন ক্লাসের একটি মাত্র সাধারণ প্রোপার্টি যার নাম name.

// Person.m

#import "Person.h"

@implementation Person

- (NSString *)description {
    return self.name;
}

@end

Person ক্লাসের ইমপ্লেমেন্টেশনে আমরা description নামের একটি ডিফল্ট মেথডকে ওভাররাইড করেছি যার কাজ হল এর name প্রোপার্টিটিকে রিটার্ন করা। অর্থাৎ কোথাও এই ক্লাসের ডাইরেক্ট অবজেক্টকেই ব্যবহার করলে তা পক্ষান্তরে এটার name প্রোপার্টিটিকেই সেখানে হাজির করবে। এটুকু আমরা করছি আমাদের উদাহরণ এর স্বার্থে, description মেথডের ব্যবহার শেখাতে নয়।

এখন নিচের মত করে Food.h ফাইল আপডেট করুন,

// Food.h

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

@interface Food : NSObject

@property (nonatomic) NSString *item;
@property (nonatomic, strong) Person *cook;

@end

এখানে প্রথমে আমরা একটি সাধারণ NSString প্রোপার্টি অ্যাড করেছি এবং তার নিচে একটি Person প্রোপার্টি অ্যাড করেছি এবং সেটার অ্যাট্রিবিউট হিসেবে strong সেট করেছি। অর্থাৎ পরবর্তীতে এই cook এর মধ্যে যখন আমরা কোন অবজেক্ট অ্যাসাইন করব তখন সেই অবজেক্টটির রেফারেন্স কাউন্ট বেড়ে যাবে।

Food এর ইমপ্লেমেন্টেশন একদমই ফাকা আপাতত এই উদাহরণ এর সাপেক্ষে,

// Food.m

#import "Food.h"

@implementation Food


@end

এবার main.m কে নিচের মত আপডেট করুন,

// main.m

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

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

        Person *tomi = [[Person alloc] init];
        tomi.name = @"Tomi Mia";
        
        Food *newDish = [[Food alloc] init];
        newDish.item = @"Halim";
        newDish.cook = tomi;
        
        NSLog(@"%@ is cooking the %@", newDish.cook, newDish.item);
        
    }
    return 0;
}

খেয়াল করুন, প্রথমে আমরা Person ক্লাস ইন্সট্যান্সিয়েট করে tomi অবজেক্টের মাধ্যমে এর (Person এর) name প্রোপার্টিটি সেট করে দিয়েছি। তারপর আমরা Food ক্লাস ইন্সট্যান্সিয়েট করে newDish অবজেক্টের মাধ্যমে এর (Food এর) item প্রোপার্টিটি সেট করে দিয়েছি। অতঃপর, newDish অবজেক্টের মাধ্যমে এর cook নামের স্ট্রং প্রোপার্টিটির ভেতর আমাদের একটু আগে তৈরি করা tomi অবজেক্টটিকে অ্যাসাইন করেছি। এই প্রোগ্রামকে রান করালে কনসোলে আউটপুট আসবে “Tomi Mia is cooking the Halim”।
যেহেতু, cook একটি strong relationship তাই newDish অবজেক্টটি tomi এর ownership নিয়ে নেয় এবং এটা ততক্ষণ পর্যন্ত ভ্যালিড থাকে যতক্ষণ পর্যন্ত tomi কে newDish এর প্রয়োজন হয়।

এখন সবকিছু ঠিক রেখেই শুধুমাত্র main.m কে নিচের মত করে আপডেট করুন,

// main.m

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

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

        Person *tomi = [[Person alloc] init];
        tomi.name = @"Tomi Mia";
        
        Food *newDish = [[Food alloc] init];
        newDish.item = @"Halim";
        newDish.cook = tomi;

        tomi = nil; // Trying to deallocating the "tomi" object

        NSLog(@"%@ is cooking the %@", newDish.cook, newDish.item);
        
    }
    return 0;
}

এখানে আমরা একটা ধাপে tomi অবজেক্টকে ম্যানুয়ালি nil করেছি কিন্তু প্রোগ্রামটি রান করে দেখুন আউটপুট আগের মতই আসবে, “Tomi Mia is cooking the Halim”।

কিন্তু,

এবার নিচের মত করে Food.h ফাইলকে আপডেট করুন,

// Food.h

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

@interface Food : NSObject

@property (nonatomic) NSString *item;
@property (nonatomic, weak) Person *cook;

@end

অর্থাৎ cook কে weak করে ফেলুন এবং main.m রাখুন একটু আগে যেমন ছিল তেমনি,

// main.m

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

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

        Person *tomi = [[Person alloc] init];
        tomi.name = @"Tomi Mia";
        
        Food *newDish = [[Food alloc] init];
        newDish.item = @"Halim";
        newDish.cook = tomi;

        tomi = nil; // Trying to deallocating the "tomi" object

        NSLog(@"%@ is cooking the %@", newDish.cook, newDish.item);
        
    }
    return 0;
}

এবার প্রোগ্রামটিকে রান করালে আউটপুট আসবে “(null) is cooking the Halim”। অর্থাৎ tomi অবজেক্ট ডিঅ্যালোকেট হবার পর newDish এর weak প্রোপার্টি cook, tomi কে হারিয়ে ফেলছে। কিন্তু ১৮ নাম্বার লাইনটি মুছে ফেলে দেখুন, কনসোলে আগের মত আউটপুট আসবে।

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

কপি (copy) অ্যাট্রিবিউটঃ
এটি মোটামুটি strong এর মতই কিন্তু বিশাল একটা পার্থক্য আছে। স্ট্রং এর মত সরাসরি অবজেক্টের ownership না নেয়ার বদলে এটি প্রথমে, ওই প্রোপার্টির মধ্যে যা অ্যাসাইন করা হয় সেটার একটা কপি তৈরি করে এবং সেই কপিটির ownership নিয়ে রাখে। যে ধরনের প্রোপার্টি গুলো ভ্যালু রিপ্রেজেন্ট করে সেগুলোই সাধারণত copy হিসেবে ডিক্লেয়ার করা হয় যেমন NSString টাইপের প্রোপার্টি গুলো।

@property (nonatomic, copy) NSString *item;

আরও যেসব অ্যাট্রিবিউট রয়েছেঃ

  • retain
  • unsafe_unretained
  • assign

পরের চ্যাপ্টারঃ
পরের চ্যাপ্টারে থাকছে মেথড নিয়ে বিস্তারিত আলোচনা। এদের নেমিং কনভেনশন, ডাইনামিক মেথড কলিং ইত্যাদি।

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

৪- অবজেক্টিভ-সি (Objective-C) তে ক্লাস ও অবজেক্ট

Standard

আগের চ্যাপ্টারঃ ৩ঃ OBJECTIVE-C তে ফাংশন ও এর ব্যবহার

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

ক্লাস তৈরিঃ
এই চ্যাপ্টারে আমরা আদর্শ একটি ক্লাসের উদাহরণ হিসেবে food নামের একটি ক্লাস তৈরি করব যার ইন্টারফেস থাকবে food.h ফাইলে যেটাকে হেডারও বলা হয় আর ইমপ্লেমেন্টেশন থাকবে food.m ফাইলে। এ দুটোই Objective C ক্লাসের স্ট্যান্ডার্ড এক্সটেনশন। হেডার ফাইলের মাধ্যমে অন্য ক্লাস এই ক্লাসের সাথে যোগাযোগ করবে আর ইমপ্লেমেন্টেশন ফাইলটি মূলত কম্পাইলার এর কাছে প্রসেস হবে।

Xcode লঞ্চ করে আমাদের সেই Hello World নামক কমান্ড-লাইন টাইপ অ্যাপটাই ওপেন করুন। File -> New -> File এ গিয়ে নিচের মত করে Objective-C class সিলেক্ট করে Next করুন,
Screen Shot 2014-05-17 at 6.03.32 PM

এরপর ক্লাসের নাম হিসেবে দিন food এবং Subclass of ফিল্ডে লিখুন NSObject. নিচের ছবির মত করে। তারপর Next ক্লিক করুন। NSObject হচ্ছে অবজেক্টিভ সি এর একটি টপ লেভেল ক্লাস যাকে মোটা মুটি অন্য সবাই ইনহেরিট করে।
Screen Shot 2014-05-17 at 6.04.08 PM

শেষে Group এবং Targets ঠিক মত সিলেক্ট/চেক করা আছে কিনা দেখে নিন এবং Create বাটনে ক্লিক করুন।
Screen Shot 2014-05-17 at 6.04.26 PM

এখন আপনার প্রজেক্ট ন্যাভিগেটরে food.h এবং food.m নামের দুইটি নতুন ফাইল দেখতে পাবেন। এ দুটোই হচ্ছে আমাদের food ক্লাসের ইন্টারফেস এবং ইমপ্লেমেন্টেশন ফাইল।
Screen Shot 2014-05-17 at 6.46.43 PM

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

// food.h

#import <Foundation/Foundation.h>

@interface food : NSObject

@property (copy) NSString *item;

-(void)grab;

@end

@interface ডিরেক্টিভ ব্যবহার করে ইন্টারফেস তৈরি করা হয় এবং এর পরেই ক্লাসের নাম এবং তার সুপার ক্লাসের নাম একটি কোলন দিয়ে পৃথক করে লেখা হয়।
@property ডিরেক্টিভ ব্যবহার করে পাবলিক প্রোপার্টি ডিক্লেয়ার করা হয়। copy অ্যাট্রিবিউট দিয়ে এটির মেমোরি ম্যানেজমেন্ট এর ধরন বোঝানো হচ্ছে। item এ অ্যাসাইন করা ভ্যালু সরাসরি পয়েন্টার টাইপ না হয়ে কপি টাইপ হবে। সামনের চ্যাপ্টারে এ ব্যাপারে আরও বিস্তারিত থাকবে।
-(void)grab দিয়ে একটি grab নামের মেথড ডিক্লেয়ার করা হচ্ছে যার কোন প্যারামিটার নাই এবং এটা কোন কিছু রিটার্নও করে না। – সাইন দিয়ে এটা যে একটা instance method তা বোঝানো হয়। instance method এবং class method এর ব্যবহার সামনের চ্যাপ্টারে বিস্তারিত আসবে।
বিঃ দ্রঃ অনেকেই এই ইন্টারফেস ফাইলেই কিছু প্রোটেক্টেড ভেরিয়েবল ডিফাইন করে থাকেন কিন্তু এটা ভালো প্র্যাকটিস নয়। এ ব্যাপারে আমাদের একদম প্রথম অর্থাৎ সিরিজ শুরুর আগের পোস্টেও লেখা ছিল। এখানে

objective-c তে একটি ক্লাসের ইন্টারফেস সাধারণত .h ফাইল-এ লেখা হয়। অন্যদিকে .m ফাইল এর টেম্পলেট কোডেও ইন্টারফেস দেখা যায় যেটিকে মূলত বলা হয় ক্লাস এক্সটেনশন। .h ফাইলের এর ইন্টারফেসে সেই সব প্রপার্টি, মেথড ডিক্লেয়ার করা হয় যেগুলো হয়তবা অন্য ফাইল থেকেও ব্যবহার করা হতে পারে এবং যদি এমন কিছু প্রপার্টি, মেথড প্রয়োজন হয় যেগুলো শুধুই একটি .m ফাইলে ব্যবহৃত হবে অথবা প্রোটেক্টেড ভেরিয়েবল হিসেবে ব্যবহার করা হবে সেগুলোকে ক্লাস এক্সটেনশন এর মধ্যেই ডিক্লেয়ার করা ভালো অর্থাত ওই .m ফাইলের ইন্টারফেসের মধ্যে।

ইমপ্লেমেন্টেশনঃ
নিচের কোড দেখে আমরা তার নিচে এটার বিভিন্ন লাইনের বর্ণনা করবো,

// food.m

#import "food.h"

@implementation food {
    // private instance variables here
    double makingCost;
}

- (void)grab {
    NSLog(@"Eating %@ :)", self.item);
}

@end

@implementation ডিরেক্টিভ ব্যবহার করে ইমপ্লেমেন্টেশন তৈরি করা করা হয়। এখানে ইন্টারফেসের মত সুপার ক্লাসের নাম উল্লেখ করতে হয় না। এখানে একটি {} দিয়ে এর মধ্যে প্রয়োজনীয় প্রাইভেট ইন্সট্যান্স ভেরিয়েবল ডিক্লেয়ার করা হয়ে থাকে।
এরপর grab ফাংশনের কার্যক্রম ডিফাইন করা হচ্ছে অর্থাৎ এই ফাংশন কোথাও থেকে কল হলে আসলে এর প্রেক্ষিতে কি ঘটবে। self হচ্ছে এখানে Java, C++ বা PHP এর this কি-ওয়ার্ড এর মত। এটা আসলে- এই মেথড যে ইন্সট্যান্স এর মাধ্যমে কল হয় তার রেফারেন্স। এই ক্লাসে যদি payBill নামের আরও একটি মেথড ডিফাইন করা থাকতো এবং সেটিকে এখানেই কল করার দরকার হলে [self payBill] লিখে কল করা যেত।

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

// main.m

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

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

        food *pizza = [[food alloc] init];
        
        // accessing value using accessor methods aka getter/setter
        [pizza setItem:@"Italian Pizza"];
        NSLog(@"Ordered a %@", [pizza item]);
        
        // accessing value using dot syntax
        pizza.item = @"Mexican Pizza";
        NSLog(@"Changed the item to a %@", pizza.item);
        
        [pizza grab];
        
    }
    return 0;
}

৪ নাম্বার লাইনে ইটারফেস import করার পর মেইন ফাংশনের মধ্যে থেকে food ক্লাসের অবজেক্ট ইন্সট্যন্সিয়েট করা হয়েছে alloc init প্যাটার্ন এর মধ্যমে। অজেক্টিভ-সি তে একটি অবজেক্ট ইন্সট্যন্সিয়েট করা দুই ধাপে সম্পন্ন হয়। প্রথমে অবজেক্টটির জন্য কিছু মেমোরি অ্যালোকেট করে নেয়া হয় alloc নামের বিল্টইন মেথডের মাধ্যমে তারপর সেটিকে ইনিশিয়ালাইজ করা হয়। আর অবজেক্টটিকে একটি পয়েন্টার হিসেবে স্টোর করা হয় কারন ইতোমধ্যে অনেকবারই বলা হয়েছে যে অবজেক্টিভ-সি তে সব রকম অবজেক্টকেই পয়েন্টার হিসেবে স্টোর করতে হয়। আর তাই food pizza = … না লিখে food *pizza = … লেখা হয়েছে। এখানে food হচ্ছে যে ক্লাসের অবজেক্ট বানাতে চান তার নাম এবং pizza হচ্ছে অবজেক্টের নাম।
এর পরেই এই অবজেক্টের প্রোপার্টি সেট করা হয়েছে item ভ্যারিয়েবলের accessor method (গেটার/সেটার) ব্যবহার করে যেটা স্বয়ংক্রিয় ভাবেই হয়ে যায় Xcode 5 এ। এর আগে এই ভ্যারিয়েবলকে @synthesize করে এর জন্য (গেটার/সেটার) জেনারেট করতে হত। গেটার হিসেবে শুধুমাত্র ভ্যারিয়েবলটির নাম ব্যবহার করলেই হবে এবং সেটার হিসেবে ভ্যারিয়েবলটির প্রথম অক্ষরকে বড় হাতের করে এবং সামনে set বসিয়ে দিয়ে এক্সেস করলেই হবে। যা করা হয়েছে উপরের ১৪, ১৫ নাম্বার লাইনে। ১৪ নাম্বারে ভ্যালু সেট করা হয়েছে এবং ১৫ নাম্বারে ভ্যালু চেয়ে নেয়া হয়েছে।
আবার, dot syntax ব্যবহার করেও এই অবজেক্টের প্রোপার্টি গুলোকে কিভাবে এক্সেস করা যেতে পারে তা দেখানো হয়েছে ১৮ এবং ১৯ নাম্বার লাইনে।
এরপরে ২১ নাম্বার লাইনে [রিসিভার ম্যাসেজ] প্যাটার্ন মোতাবেক food অবজেক্টের grab মেথডকে কল করা হয়েছে।
আর আলোচ্য অংশে যে, [ ] এর ব্যবহার সেটাকে অবজেক্টিভ-সি স্টাইল মনে করেই আগাতে পারেন। Command+R চেপে এই অ্যাপকে রান করালে এর আউটপুট নিচের মতই আসবে এবং সেটা অ্যানালাইসিস করলেই বুঝতে পারবেন আপনি অবজেক্ট অরিয়েন্টেশনে ঢুকে পরেছেন।
Screen Shot 2014-05-17 at 7.54.44 PM

ক্লাস মেথড ও ক্লাস ভেরিয়েবলঃ
উপরের উদাহরণে ইন্সট্যান্স লেভেলের প্রোপার্টি ও মেথড নিয়ে কাজ করা হয়েছে। অর্থাৎ প্রথমে একটি ক্লাসের অবজেক্ট তৈরি করা হয়েছে এবং সেই অবজেক্টকে ধরেই ওই ক্লাসের প্রোপার্টি ও মেথড এর সাথে যোগাযোগ করা হয়েছে। অব্জেক্টিভ-সি তে ক্লাস লেভেল প্রোপার্টি ও মেথডও আছে।
এর ডিক্লেয়ারেশনও ইন্সট্যান্স লেভেল মেথড এর মতই কিন্তু শুরুতে – সাইনের বদলে + সাইন দেয়া হয়। যেমন নিচে একটি ক্লাস লেভেল মেথড ডিক্লেয়ার করা হয়েছে, স্বভাবতই হেডার ফাইলে। উল্লেখ্য, এই নতুন মেথডটি কিন্তু একটি NSString টাইপের প্যারামিটার নেয় কিন্তু কিছু রিটার্ন করে না।

// food.h

#import <Foundation/Foundation.h>

@interface food : NSObject

@property (copy) NSString *item;

-(void)grab;
+ (void)setDefaultItem:(NSString *)item;

@end

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

// food.m

#import "food.h"

static NSString *defaultItem;


@implementation food {
    // private instance variables here
    double makingCost;
}

- (void)grab {
    NSLog(@"Eating %@ with an obvious %@:)", self.item, defaultItem);
}

+ (void)setDefaultItem:(NSString *)item {
    defaultItem = [item copy];
}

@end

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

// main.m

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

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {
        
        [food setDefaultItem:@"Lemonade"];

        food *pizza = [[food alloc] init];
        
        // accessing value using accessor methods aka getter/setter
        [pizza setItem:@"Italian Pizza"];
        NSLog(@"Ordered a %@", [pizza item]);
        
        // accessing value using dot syntax
        pizza.item = @"Mexican Pizza";
        NSLog(@"Changed the item to a %@", pizza.item);
        
        [pizza grab];
        
    }
    return 0;
}

কিন্তু এবার grab মেথড জানাচ্ছে যে, আপনি একটা পিৎজা খাচ্ছেন কিন্তু সাথে আপনার একটা কমন পছন্দের আইটেম আছে লেমোনেড।
Screen Shot 2014-05-17 at 8.35.57 PM

কন্সট্রাক্টরঃ
অবজেক্টিভ-সি তে সরাসরি কোন কন্সট্রাক্টর মেথড নাই। তবে অন্যান্য ল্যাঙ্গুয়েজের বিল্টইন কন্সট্রাক্টর মেথড যা করে (ক্লাস থেকে অবজেক্ট ইন্সট্যান্সিয়েট করার সময়ই কিছু সাধারণ কাজ করে ফেলা) ঠিক তাই করা যাবে নিচের পদ্ধতি অনুযায়ী।
অবজেক্টিভ-সি তে একটি অবজেক্ট অ্যালোকেটেড হবার পর পরই init মেথড কল করার মাধ্যমে সেটি ইনিশিলাইজ হয়। যেমন উপরের উদাহরণে food *pizza = [[food alloc] init] লাইনে। আবার ক্লাস লেভেল ইনিশিলাইজেশনও আছে সেটা পরে দেখা যাবে। আগে এই পদ্ধতিতে কন্সট্রাকশন এর কাজ করি।
init হচ্ছে একটি ডিফল্ট ইনিশিলাইজেশন মেথড। কিন্তু আপনি নিজেও আপনার মত করে একটা ইনিশিলাইজেশন মেথড তৈরি করতে পারেন। এটা খুব একটা কঠিন কাজ না, শুধু নরমাল টাইপের একটা মেথডের নামের আগে init শব্দটা ব্যবহার করতে হবে। নিচে আমরা সেরকম একটি ডিক্লেয়ার করেছি food.h ফাইলে যার নাম initWithItem,

// food.h

#import <Foundation/Foundation.h>

@interface food : NSObject

@property (copy) NSString *item;

-(void)grab;
-(id)initWithItem:(NSString *)item;

@end

এখন এই কাস্টম ইনিশিলাইজার এর ইমপ্লেমেন্টেশন করতে হবে নিচের মত করে,

// food.m

#import "food.h"
#import "food.h"

@implementation food {

}

- (void)grab {
    NSLog(@"Eating %@ :)", self.item);
}

- (id)initWithItem:(NSString *)item {
    self = [super init];
    if (self) {
        _item = [item copy];
    }
    return self;
}

@end

এখানে super, প্যারেন্ট ক্লাসকে নির্দেশ করে এবং self হচ্ছে সেই ইন্সট্যান্সকে/অবজেক্টকে নির্দেশ করে যেটা এই মেথডটিকে কল করে। ইনিশিলাইজার মেথডকে সবসময় ওই অবজেক্টের কাছে নিজের রেফারেন্সটাই রিটার্ন করতে হয়। আর তাই, এই মেথডের মধ্যে অন্যান্য কাজ করার আগে চেক করে নিতে হয় self আছে কিনা। তারপর আমরা ডাইরেক্ট _item = … ব্যবহার করে এই ইন্সট্যান্স ভ্যারিয়েবলের ভ্যালু সেট করে দিলাম এই মেথডের সাথে আসা প্যারামিটার এর কন্টেন্টকে কপি করে।
অর্থাৎ যখনই initWithItem ব্যবহার করে এই ক্লাস এর অবজেক্ট ইন্সট্যান্সিয়েট করা হচ্ছে ঠিক তখনই এই ক্লাসের একটি প্রোপার্টির ভ্যালু আমরা আসাইন করে দিচ্ছি। ঠিক এটাই সহজ ভাবে যেকোনো কন্সট্রাক্টর মেথডের কাজ। এখন এটা টেস্ট করার জন্য main.m ফাইলটিকে নিচের মত করে পরিবর্তন করে নিতে পারেন,

// main.m

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

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

        food *choice = [[food alloc] initWithItem:@"Pan Pizza"];

        [choice grab];
        
    }
    return 0;
}

উপরের কোড ব্লক লক্ষ্য করলে দেখবেন যে, food ক্লাসের অবজেক্ট choice কে ইনিশিয়ালাইজ করা হয়েছে initWithItem নামক কাস্টম ইনিশিয়ালাইজার মেথড দিয়ে। আর এই initWithItem মেথড food ক্লাসের একটি প্রোপার্টি যার নাম item সেটার ভ্যালু সেট করে দেয়। আর তাই, অবজেক্ট ইনিশিয়ালিয়াজেশনের পর পরই যদি আমরা grab মেথড কল করি যেটা কিনা কনসোলে ওই আইটেমটা খাওয়ার কথাটাই প্রিন্ট করার কথা – তাহলে নিচের মত ঠিক ঠাক আউটপুট আসে।
Screen Shot 2014-05-18 at 3.27.30 AM

পরের চ্যাপ্টারঃ
প্রোপার্টি ( @property ) নিয়ে বিস্তারিত আলোচনা এবং বিভিন্ন অ্যাট্রিবিউট সাথে এর সম্পর্ক নিয়ে কিছু উদাহরণ এবং আলোচনা থাকবে।

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

৩- অবজেক্টিভ-সি (Objective-C) তে ফাংশন ও এর ব্যবহার

Standard

আগের চ্যাপ্টারঃ ২ঃ OBJECTIVE-C এর ভিতরে আমাদের প্রিয় C

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

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

ফাংশনের ব্যাসিক সিন্ট্যাক্সঃ
একটি ফাংশনের মোট চারটি অংশ থাকে-

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

উদাহরন স্বরুপ নিচের কোডটুকু দেখা যাক,

#import <Foundation/Foundation.h>

void billKotoHolo()
{
    NSLog(@"স্যার আপনার বিল হয়েছে ১২০০ টাকা");
}


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

    @autoreleasepool {
        billKotoHolo();
    }
    return 0;
}

এখানে দেখা যাচ্ছে billKotoHolo() একটা ফাংশন। মেইন main() ফাংশন এর মধ্যে থেকে একে কল করা হয়েছে। এই ফাংশনটি মেইন ফাংশনকে কোন কিছু রিটার্ন করবে না তাই এর রিটার্ন টাইপ void। এই ফাংশনের কার্যকলাপ বলতে গেলে সিমপ্লি কনসোলে একটা ম্যাসেজ প্রিন্ট করা আর এই কাজটুকু করতে এর কোন প্যারামিটার দরকার নাই। তাই ডেফিনেশন এর সময় () এর মধ্যে কিছু নাই। {} এর ভিতরের অংশই কোডব্লক যেখানে বলা আছে এই ফাংশনের কাজ কি। আবার, কল করার সময়ও কোন প্যারামিটার পাঠানো হয়নি তাই () ব্র্যাকেটের ভিতরের অংশ ফাকা। অর্থাৎ কোন প্যারামিটার পাস করা হচ্ছে না।

ফাংশনে, পয়েন্টার রেফারেন্সকেও রিটার্ন ভ্যালু এবং প্যারামিটার হিসেবে ব্যবহার করা যায়। অর্থাৎ ফাংশন গুলো খুব সহজেই Objective-C এর অবজেক্ট গুলোর সাথে ব্যবহৃত হতে পারে। কারন, আগেই বলা হয়েছে যে, “Objective-C তে সব ধরনের অবজেক্টকে পয়েন্টার হিসেবে প্রকাশ করা হয়/যায়।” এখন নিচের উদাহরণটা খেয়াল করুন,

#import <Foundation/Foundation.h>

int getCount(NSArray *foods) {
    int count = (int)[foods count];
    return count;
}

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

    @autoreleasepool {
        NSArray *foods = @[@"Chicken Fry", @"Soup", @"Biriani", @"Snacks"];
        NSLog(@"%d", getCount(foods));    }
    return 0;
}

getCount() ফাংশনটি আর্গুমেন্ট হিসেবে NSArray অবজেক্ট (Objective-C তে একটি অ্যারেটাইপ ভ্যারিয়েবল যাতে আমরা রেষ্টুরেন্টের খাবারের লিস্ট রাখছি) নেয় এবং integer টাইপ ডাটা (মোট আইটেমের সংখ্যা) রিটার্ন করে।

int count = (int)[foods count];

এই লাইন টি দেখে ঘাবড়ানোর কিছু নেই। foods অ্যারেতে কতগুলো ইলিমেন্ট আছে সে সংখ্যাটি count রিটার্ন করে। count হচ্ছে food অবজেক্টের একটি বিল্ট-ইন মেথড যেটা আসলে NSArray ক্লাসে ডিফাইন করা আছে। পরবর্তীতে বিস্তারিত দেখানো হবে।

ডিক্লেয়ারেশন বনাম ইম্প্লিমেন্টেশনঃ
যেখানে একটি ফাংশনকে কল করে ব্যবহার করা হবে তার পুর্বেই কোথাও ফাংশনটিকে ডিফাইন করতে হয়। অর্থাৎ ব্যবহারের পুর্বেই কম্পাইলার কে জানাতে হবে যে getCount() নামে একটি ফাংশন আছে এবং এই ফাংশনটির কাজের বর্ণনা তথা ডেফিনিশন/ইম্প্লিমেন্টেশনও ঠিক করা আছে। উপরের কোড এ getCount() ফাংশনটি main() এর আগে না লিখে পরে লিখে main() এর ভিতর থেকে কল করলে কম্পাইলার ফাংশনটিকে খুজে পেত না।

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

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

// Declaration
int getCount(NSArray *);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray *foods = @[@"Chicken Fry", @"Soup", @"Hydrabadi Biriani", @"Snacks"];
        NSLog(@"%d", getCount(foods));
    }
    return 0;
}

// Implementation
int getCount(NSArray *foods) {
    int count = (int)[foods count];
    return count;
}

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

ফাংশন ওভারলোডিংঃ
C এর মত Objective-C তেও ফাংশন ওভারলোডিং নাই। আসলে অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিং এর প্রধান যে ৩ টি ফিচার আছে তার মধ্যে একটি হল পলিমরফিজম। ফাংশন ওভারলোডিং পলিমরফিজম এরই উদাহরন। পরবর্তীতে অধ্যায় গুলো এ সম্পর্কে বিস্তারিত আলোচনা হবে। নিচের উদাহরনটি দেখলেই বুঝা যাবে।
Screen Shot 2014-05-09 at 10.40.28 PM

Static কি-ওয়ার্ড ঃ
Static কি-ওয়ার্ডটি ফাংশন অথবা ভ্যারিয়েবল এর অ্যাভেইলাবিলিটি (Availibility) অল্টার করার সুবিধা দেয়। মুলত এই কিওয়ার্ডের এফেক্ট নির্ভর করে কোন ক্ষেত্রে এর ব্যবহার হচ্ছে তার ওপর। Static কিওয়ার্ডটি ফাংশন অথবা ভ্যারিয়েবল ডিক্লারেশন এ ব্যবহার করা হয়। এই অংশে স্ট্যাটিক কিওয়ার্ডের ব্যবহার দেখানো হবে।

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

স্ট্যটিক ফাংশনের বেসিক সিনট্যাক্স নিচের উদাহরনে দেখানো হল। এই কোডটুকু যদি অন্য কোন ফাইলে লিখা হয় যেমন কোন ফাংশন লাইব্রেরীতে, তাহলে কখনোই main.m এর মধ্যে থেকে getRestaurantMenu() ফাংশনটিকে এক্সেস/কল করা যাবে না।

// Static function declaration
static NSArray getRestaurantMenu();

// Static function implementation
static int getRestaurantMenu() {
    NSArray *menuList = @[@"Chicken Fry", @"Soup", @"Biriani", @"Snacks"];
    return (int)[menuList count];
}

বিশেষভাবে উল্লেখ্য যে, static কিওয়ার্ডটি ডিক্লারেশন এবং ইমপ্লিমেন্টেশন উভয় ক্ষেত্রেই ব্যবহার করতে হবে।

স্ট্যাটিক লোকাল ভ্যারিয়েবলঃ
কোন ফাংশনের ভেতরে ডিক্লেয়ার করা ভ্যারিয়েবল কে ওই ফাংশনের লোকাল ভ্যারিয়েবল বলা হয়। অর্থাৎ ভ্যারিয়েবলটির স্কোপ হবে ওই ফাংশনের ভেতরের অংশটুকুই। যতবার এই ফাংশনটি কল করা হবে ততবারই ভ্যারিয়েবলটি নতুন করে রি-সেট হবে। “নতুন করে রি- সেট” হওয়া বলতে বোঝানো হচ্ছে যে, আগের বার কল হওয়ার সময় ভ্যারিয়েবলটির আপডেটেড ভ্যালু, মেমোরি লোকেশন পরেরবারের সাথে মিলবে না। প্রত্যেক বার ফাংশনটি কল হবার সময় ভ্যারিয়েবলটির নতুন করে মেমোরি লোকেশন সেট হবে এবং সেটির প্রাথমিক ইনিসিয়ালাইজড মান সেট হবে, তথা রিসেট হবে।

#import <Foundation/Foundation.h>

int getNumberOfChicken()
{
    int count = 0;
    count = count + 1;
    return count; 
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog( @"%d", getNumberOfChicken());   // 1
        NSLog( @"%d", getNumberOfChicken());   // 1     
    }
    return 0;
}

উপরের কোডে getNumberOfChicken() ফাংশনটিতে count ভ্যারিয়েবলটি স্ট্যাটিক না। তাই যতবারই main() থেকে getNumberOfChicken() কে কল করা হবে, ততবারই নতুন করে count ডিক্লেয়ার হবে (নতুন মেমরী লোকেশন এবং ভ্যালু ০)।

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

#import <Foundation/Foundation.h>

int getNumberOfChicken()
{
    static int count = 0;
    count = count + 1;
    return count; 
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog( @"%d", getNumberOfChicken());   // 1
        NSLog( @"%d", getNumberOfChicken());   // 2     
    }
    return 0;
}

ভ্যারিয়েবলটি static হিসেবে ডিক্লেয়ার করার কারনে শুধুমাত্র প্রথমবার count ভ্যারিয়েবলের জন্য মেমরী লোকেশন সেট হয় এবং ০ দিয়ে ইনিশিয়ালাইজ হয়। কিন্তু পরবর্তীতে যতবারই কল করা হোক না কেন count এর ভ্যালু ওই মেমরী লোকেশন থেকেই ইনভোক করা হয়।

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

ফাংশন লাইব্রেরীঃ
অবজেক্টিভ-সি নেমস্পেস সাপোর্ট করে না। অন্যান্য গ্লোবাল ফাংশন গুলোর সাথে যাতে নাম নিয়ে কোন কনফ্লিক্ট না হয় সে জন্য ফাংশন এবং ক্লাসগুলোতে ইউনিক আইডেন্টিফায়ার হিসেবে প্রিফিক্স যোগ করে দেওয়া হয়। ফলস্বরুপ শুধু CaptureSession এর পরিবর্তে AVCaptureSession(), শুধু Layer এর পরিবর্তে CALayer() ইত্যাদি ফাংশন দেখা যায়।

তাই আমরা যখন নিজেদের লাইব্রেরী বানাবো তখন ফাংশনের নাম গুলো একটা হেডার (.h) ফাইলে ডিক্লেয়ার করব এবং আলাদা ইমপ্লিমেন্টেশন (.m) ফাইলে ফাংশনগুলোর ইমপ্লিমেন্টেশন করব। Objectiv-C এর ক্লাস গুলোর গঠনও এরকম। এই পদ্ধতিতে ফাইল দুটোকে যথাক্রমে ইন্টারফেস ফাইল এবং ইমপ্লিমেন্টেশন ফাইল বলা হয়। এতে করে ওই লাইব্রেরী ব্যবহার করার সময় ইমপ্লিমেন্টেশন না ভেবে শুধুমাত্র হেডার ফাইলকে ইমপোর্ট করেই কাজ করা যাবে।
উল্লেখ্য, হেডার ফাইলের এই ধরনের ব্যবহারকে একটা API লেয়ার হিসেবেও কল্পনা করা যায়। অর্থাৎ আমরা আরেকটা ক্লাসের সাথে যোগাযোগের জন্য সেটার এক্সেস-অ্যাবল লেয়ার তথা ইন্টারফেস ফাইলের সাথে যোগাযোগ করবো, মুল ইমপ্লেমেন্টেশন ফাইলের সাথে নয়।

//  RestaurantUtilities.h
#import <Foundation/Foundation.h>

NSString *RUgetRestaurantTitle();
int RUgetCountOfItems();

এই হেডার ফাইলের ইম্পলিমেন্টেশন ফাইলটি নিচের মত হতে পারে,

//  RestaurantUtilities.m
#import "RestaurantUtilities.h"

NSString *RUgetRestaurantTitle()
{
    return @"ভুতের আড্ডা";
}

int RUgetCountOfItems()
{
    NSArray *items = @[@"সরষে ইলিশ", @"খিচুরী"];
    return (int)[items count];
}

এখন main.m এ RestaurantUtilities.h হেডার ফাইল কে ইমপোর্ট করে সকল ফাংশনগুলোকে ব্যবহার করতে পারবো। এখানে উল্লেখযোগ্য যে RestaurantUtilities.m এ যদি কোন static ফাংশনের ডিক্লারেশন এবং ইমপ্লিমেন্টেশন থেকে থাকত তাহলে কিন্তু main.m থেকে সেই ফাংশনগুলো ব্যবহার করতে চাইলে কম্পাইলার এরর হবে, যার ব্যাখ্যা আগেই দেয়া হয়েছে।

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        NSLog( getRestaurantTitle());     // ভুতের আড্ডা
        NSLog( @"%d", getCountOfItems()); // 2 
        
    }
    return 0;
}

পরের চ্যাপ্টারঃ
এই চ্যাপ্টারে C এবং Objective-C তে ফাংশনের ব্যবহার, সাথে এদের এবং ভেরিয়েবলের স্কোপ, ডিক্লেয়ারেশন, ইমপ্লেমেন্টেশন, ইন্টারফেস/হেডার ফাইল, ইমপ্লেমেন্টেশন ফাইল ইত্যাদি বিষয় নিয়ে আলোচনা হয়েছে। পরবর্তী চ্যাপ্টারে আমরা Objective-C এর অবজেক্ট ওরিয়েন্টেড ফিচার নিয়ে বিস্তারিত আলোচনা করবো। সেখানে থাকবে কিভাবে ক্লাস ডিফাইন করা যায়, কিভাবে অবজেক্ট ইন্সট্যান্সিয়েট করা যায়, কিভাবে প্রোপার্টি সেট করা যায় এবং কিভাবে মেথড কল করা যায় ইত্যাদি।

পরের চ্যাপ্টারঃ ৪ঃ অবজেক্টিভ-সি (Objective-C) তে ক্লাস ও অবজেক্ট

২- Objective-C এর ভিতরে আমাদের প্রিয় C

Standard

আগের চ্যাপ্টার – ১ঃ OBJECTIVE-C প্রাথমিক ধারনা এবং XCODE

ভূমিকাঃ
টাইটেল দেখে যদি আপনার মনে এই ধারনা হয়ে থাকে যে, “Objective-C এর মধ্যে কি C ব্যবহার করা যাবে?” অথবা “Objective-C কি C এর উপড়ের লেয়ারের কিছু?” তাহলে আপনার দুটো ধারনাই একদম ঠিক 🙂
আগেও বলা হয়েছে, Objective-C হচ্ছে ট্র্যাডিশনাল C কে অবজেক্ট ওরিয়েন্টেড ফিচার দিয়ে তৈরি হওয়া আরেকটি ল্যাঙ্গুয়েজ। C যা যা করতে পারে, Objective-C ও তাই তাই করতে পারে কিন্তু Objective-C এর কিছু কাজ হয়ত C কে দিয়ে হবে না। অর্থাৎ Objective-C কে C এর সুপারসেট বলতে পারেন। C এর সোর্স আপনি Objective-C এর সোর্স ফাইলের মধ্যেই একই সাথে লিখতে পারেন এবং কম্পাইলার এর কাছে পাঠাতে পারেন।
আর তাই, Objective-C এর উপর দখল আনার জন্য আপনাকে সেই মাদার ল্যাঙ্গুয়েজ C এর ব্যাসিক জানতেই হচ্ছে। এই ব্যপারটাকে মাথায় রেখে এই চ্যাপ্টারে ব্যাসিক C এর ব্যপার গুলো ঝালাই করার চেষ্টা করা হবে এবং অবশ্যই তা আমাদের Objective-C এর সাথে কম্বাইন করেই। অর্থাৎ Objective-C এর ভিতরে আমাদের প্রিয় C

কমেন্টঃ
কিচ্ছু বলার নাই। দেখেই বোঝা যাচ্ছে কিভাবে C কোডের মধ্যে কমেন্ট লেখা যায়।

// This is an inline comment

/* This is a block comment.
   It can span multiple lines. */

ভ্যারিয়েবলঃ
একধরনের কন্টেইনার যা কিছু ভ্যালু ধারন করতে পারে। C তে ভ্যারিয়েবল গুলো Typed অর্থাৎ ভ্যারিয়েবল ডিক্লেয়ার করার সময় জানাতে হবে এটা কি ধরনের ভ্যালু ধারন করবে। ভ্যারিয়েবলকে

<Data Type> <Variable Name>

এই প্যাটার্নে ডিক্লেয়ার করা হয়। যেমন, আমাদের আগের চ্যাপ্টারে করা Command Line Tool টাইপের অ্যাপটির main.m ফাইল নিচের মত করে এডিট করে Command+R চেপে রান করে দেখতে পারি,

#import <Foundation/Foundation.h>

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

    @autoreleasepool {
        
        double bill = 100.50;
        NSLog(@"Total bill: %.2f", bill);
        
    }
    return 0;
}

এখানে bill একটি double টাইপ ভেরিয়েবল যেটা আমরা কনসোলে প্রিন্ট করছি।

কন্সট্যান্টঃ
যে ভেরিয়েবলের ভ্যালু চেঞ্জ করা যায় না। কম্পাইলারকে সেই ভেরিয়েবলের কথা জানাতে const নামক ভেরিয়েবল মডিফায়ার ব্যবহার করা হয়।

#import <Foundation/Foundation.h>

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

    @autoreleasepool {
        
        double const tax = 15.50;
        tax = 10.00;
        NSLog(@"You have to pay a tax of: %.2f %%", tax);
        
    }
    return 0;
}

উপড়ের মত করে ভ্যালু চেঞ্জ করতে গেলে প্রথমেই Xcode বাধা দিবে নিচের মত করে। অন্য ভাবে কম্পাইল করতে গেলেও কম্পাইলার এরর দিবে। তাই, লাইন নাম্বার 9 এখানে অবাঞ্ছিত।
Screen Shot 2014-05-06 at 2.46.18 AM

অ্যারিদম্যাটিকঃ
আমাদের আলোচিত অ্যাপ এর main.m ফাইল নিচের মত করে পরিবর্তিত করে Command+R চেপে রান করে দেখলেই বুঝতে পারবেন কোডের কমেন্ট এ দেয়া বর্ণনা অনুযায়ী C এর ব্যাসিক অ্যারিদম্যাটিক অপারেশন গুলো মনে পরছে কিনা।

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {
        
        NSLog(@"6 + 2 = %d",  6 + 2);   // Addition. Output: 8
        NSLog(@"6 - 2 = %d",  6 - 2);   // Subtraction. Output: 4
        NSLog(@"6 * 2 = %d",  6 * 2);   // Multiplication. Output: 12
        NSLog(@"6 / 2 = %d",  6 / 2);   // Division. Output: 3
        NSLog(@"6 %% 2 = %d", 6 % 2);   // % modulo operator for getting Remainder. Output: 0
        
        int i = 0;
        i++;    // increment operator
        NSLog(@"I was 0 and now I am: %d", i);
        
    }
    return 0;
}

কন্ডিশনালঃ
C তে অন্যান্য ল্যাঙ্গুয়েজের মত if else ফিচার আছে যার ব্যবহার নিচের মত,

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {
        
        double bill = 500;
        if (bill >= 400) {
            NSLog(@"You ate enough");
        } else if (bill <= 100) {
            NSLog(@"You ate least");
        } else {
            NSLog(@"You tool a good meal!");
        }
        
    }
    return 0;
}

এর সাথে সাথেই চলে আসে সবচেয়ে সাধারণ লজিক্যাল অপারেটর গুলোর কথা যেগুলো এই if else এর সাথে সব সময়ই ব্যবহৃত হয়,

        a == b	Equal to
        a != b	Not equal to
        a > b	Greater than
        a >= b	Greater than or equal to
        a < b	Less than
        a <= b	Less than or equal to
        !a	Logical negation
        a && b	Logical and
        a || b	Logical or

আর switch তো আছেই। কিন্তু switch এ শুধু মাত্র integer ভেরিয়েবল ব্যবহার করা যায় এর সুইচিং ফ্যাক্টর হিসেবে।

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {
        
        int bill = 50;
        
        switch (bill) {
            case 500:
                NSLog(@"You ate exactly 500 Tk.");
                break;
            case 400:
                NSLog(@"You ate exactly 100 Tk");
                break;
            case 300:
            case 200:
                NSLog(@"You ate less than 300 Tk.");
                break;
            default:
                NSLog(@"Do not you how much you ate.");
                break;
        }
        
    }
    return 0;
}

উপড়ের if else এবং switch এর ব্যবহার ওয়ালা উদাহরণ দুইটা main.m ফাইলে লিখে শুধুমাত্র Command+R চেপেই রান করে দেখতে পারেন Xcode এর ডিবাগ কনসোলে কি আউটপুট আসে।

লুপঃ
কোন ভ্যালুর মান ও অবস্থার উপর নির্ভর করে একটা কাজ বার বার করা হয় for এবং while লুপ ব্যবহার করে নিচের মত করে,

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {
        
        int totalItem = 5;
        
        for (int itemNumber = 1; itemNumber <= totalItem; itemNumber++) {
            if (itemNumber == 3) {
                NSLog(@"Do not add this item in my dish");
                continue;
            }
            NSLog(@"Added item number: %d", itemNumber);
        }
        
    }
    return 0;
}
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {
        
        int totalItem = 15;
        
        int itemNumber = 1;
        while (itemNumber <= totalItem) {
            if (itemNumber > 5) {
                NSLog(@"Please, Do not add any more food in my dish");
                break;
            }
            NSLog(@"Added item number: %d", itemNumber);
            itemNumber++;
        }
        
    }
    return 0;
}

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

#import <Foundation/Foundation.h>

#define PI 3.14159

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

        NSLog(@"Whats the value of PI? It is: %f", PI);
        
    }
    return 0;
}

এখানে দেখুন কিভাবে main() ফাংশনের স্কোপের বাইরে থেকেও PI এর একটা মান ব্যবহার করা যাচ্ছে। এখানে PI কে অবজেক্ট টাইপ ম্যাক্রো বলা হয়। C তে ফাংশন টাইপ ম্যাক্রোও আছে যেমন নিচের উদাহরণ,

#import <Foundation/Foundation.h>

#define PI 3.14159
#define circleName @"Pizza"
#define RAD_TO_CIRCUM(radius) (2*PI*radius) // Calculate circumference of a circle

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

        int radius = 6;
        NSLog(@"Circumference of this %@ is: %f", circleName, RAD_TO_CIRCUM(radius));
        
    }
    return 0;
}

এখানে RAD_TO_CIRCUM() একটি ফাংশন টাইপ ম্যাক্রো যেটা কিনা আবার আর্গুমেন্টও রিসিভ করতে পারে।

স্ট্রাকচারঃ
C এর struct ব্যবহার অনেক গুলো বিভিন্ন রকম ভেরিয়েবলকে একত্রিত করে একটা নতুন ডাটা প্যাকেজ/গ্রুপ হিসেবে ডিফাইন করা যায়। পরে সেই ডাটা গ্রুপটাকে একটা অবজেক্ট এর মত ব্যবহারও করা যায়। নিচে একটা পিৎজার জন্য দরকারি তথ্যের ভেরিয়েবল গুলো গ্রুপ করে একটা নতুন ডাটা স্ট্রাকচার তৈরি করা হয়েছে। আর C এর typedef (নতুন data type ডিক্লেয়ার করার পদ্ধতি) ব্যবহার করে এই নতুন স্ট্রাকচারটিকে Pizza টাইপের data type হিসেবে যাতে ডিল করা যায় সে ব্যবস্থা করা হয়েছে।

#import <Foundation/Foundation.h>

typedef struct {
    unsigned int diameter;
    char mainIngredient[10];
    char pizzaName[10];
} Pizza;

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

        Pizza makePizza = {6, "beef", "italian"};
        NSLog(@"You are making one : %d inch %s %s pizza!", makePizza.diameter, makePizza.pizzaName, makePizza.mainIngredient);
        
    }
    return 0;
}

এখানে ফাংশনের কাজের শুরুতে makePizza নামের একটি Pizza টাইপ ভেরিয়েবল (এক্ষেত্রে স্টাকচার) এর জন্য ডাটা পপুলেট/এসাইন করা হয়েছে একটি Initializer syntax এর মাধ্যমে।

ইনিউমেরেশন (enum) ঃ
enum কি-ওয়ার্ড ব্যবহার করে একটি enumerated type তৈরি করা হয় যেটাকে আসলে একাধিক কন্সট্যান্ট ভেরিয়েবলের (enumerators) সমষ্টিও বলা যেতে পারে যেখানে কন্সট্যান্ট ভেরিয়েবল গুলোর মান বাই ডিফল্ট সিরিয়ালি 0,1,2,3 … হিসেবে ডিফাইন হয়ে থাকে।

#import <Foundation/Foundation.h>

typedef enum {
    Beef,
    Chicken,
    Mutton
} Pizza;

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

        Pizza makePizza = Chicken;
        
        NSLog(@"Chicken has position %u in the enumerated type Pizza.", makePizza);
        
        switch (makePizza) {
            case Beef:
                NSLog(@"A beef pizza is coming!");
                break;
            case Chicken:
                NSLog(@"A chicken pizza is coming!");
                break;
            case Mutton:
                NSLog(@"A mutton pizza is coming!");
            default:
                break;
        }
        
    }
    return 0;
}

প্রথমত, আগের মত এই উদাহরণেও typedef ব্যবহার করা হয়েছে। অতঃপর, এখানে যে Enumerated type টি তৈরি করা হয়েছে তার মধ্যে Beef একটি কন্সট্যান্ট ভেরিয়েবল যার মান 0, Chicken এর 1 এবং Mutton এর 2 এভাবে কল্পনা করা যেতে পারে। আর ঠিক এভাবেই, switch এর যে একমাত্র integer টাইপের উপরই নির্ভরতা সেটা দুর করে এর মধ্যে প্রয়োজনে string নিয়েও কাজ করা যেতে পারে। এই প্রোগ্রামটি রান করলে নিচের মত আউটপুট আসবেঃ
Screen Shot 2014-05-07 at 2.45.49 AM

অ্যারেঃ
যদিও iOS/OSX অ্যাপ ডেভেলপমেন্টের সময় Foundation ফ্রেমওয়ার্কের সাথে থাকা হাই লেভেল NSArray এবং NSMutableArray ক্লাস গুলো দিয়ে অ্যারে অপারেশন করাই হবে সবচেয়ে সুবিধা জনক তবুও এখানে যেহেতু C নিয়েই একটু স্মৃতিচারণ করা হচ্ছে এবং C এর অ্যারেকেও Objective-C এর সাথে ব্যবহার করা যাবে তাই নিচে থাকছে অ্যারে এর উদাহরণ,

#import <Foundation/Foundation.h>

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

        int pizzaSize[3] = {6, 12, 18};

        for (int i=0; i<3; i++) {
            NSLog(@"Pizza at index %d is: %d inch", i, pizzaSize[i]);
        }
        
    }
    return 0;
}

অ্যারে ডিক্লেয়ার করার int pizzaSize[3] স্টেটমেন্টটি শুরুতেই ৩টা অনুক্রমিক মেমোরি ব্লক অ্যালোকেট করে নেয় যেখানে integer টাইপের ডাটা অনায়াসে ধরবে। তারপরেই initializer syntax ব্যবহার করে সেই অ্যারেটিকে পপুলেট করা হচ্ছে অর্থাৎ ডাটা এসাইন করা হচ্ছে। এরপর pizzaSize[] এর মধ্যে i এর মান অর্থাৎ 0,1 বা 2 অফসেট হিসেবে দিয়ে ওই অ্যারের ভ্যালু গুলো এক্সেস করা হচ্ছে। যেহেতু অ্যারেতে যেহেতু অনুক্রমিক ভাবেই ডাটা থাকে তাই অফসেট এর সাথে ১ যোগ করে দিয়ে দিয়েই পরের অফসেটের ভ্যালু পাওয়া যাচ্ছে।

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

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {
        // Define a normal variable
        int bill = 500;
        // Declare a pointer that points to an int
        int *pointer;
        
        // Find the memory address of the variable
        pointer = &bill;
        // Dereference the address to get its value
        NSLog(@"Your bill is: %d Tk", *pointer);
        
        // Assign a new value to the memory address
        *pointer = 525;
        // Access the chnaged value via the variable
        NSLog(@"Your bill including tax is: %d Tk", bill);

        
    }
    return 0;
}

Void Pointer নামের একধরনের পয়েন্টার আছে যা আসলে যেকোনো কিছুকেই পয়েন্ট করতে পারে। অর্থাৎ নির্দিষ্ট করে প্রথম থেকেই যে একটা integer বা char কে পয়েন্ট করবে/করছে, তা নয়। কিন্তু ওই ভয়েড পয়েন্টারে জমা থাকা মেমোরি অ্যাড্রেস ধরে সেখানকার ভ্যালু বা কন্টেন্ট এক্সেস করতে হলে একটু কাজ করতে হবে অর্থাৎ পয়েন্টার কে বলতে হবে ওই অ্যাড্রেস থেকে কোন ফরম্যাট হিসেবে ডাটা পড়বে। নিচে একটি উদাহরণ আছে,

#import <Foundation/Foundation.h>

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

        int toPay = 500;
        
        void *genericPointer = &toPay;
        int *intPointer = (int *)genericPointer; // Casting to non-void pointer
        
        NSLog(@"To pay actually: %d", *intPointer);
        
    }
    return 0;
}

এখানে ভয়েড পয়েন্টারটিকে cast করে নন-ভয়েড এবং integer টাইপের পয়েন্টারে রূপান্তর করা হচ্ছে। (int *) স্টেটমেন্টটি ভয়েড পয়েন্টারের কন্টেন্ট কে integer হিসেবে interpret করছে।

Objective-C তে পয়েন্টারঃ
Objective-C তে পয়েন্টারের ব্যপারটা খুব কম কথাতেই শেষ হয়ে যাবে কিন্তু ভালো মতই মনে রাখতে হবে যে সব রকম Objective-C এর অবজেক্টকে পয়েন্টার হিসেবে উল্লেখ করা হয়। যেমন নিচের মত করে, একটা NSString অবজেক্টকে অবশ্যই পয়েন্টার হিসেবে স্টোর করতে হবে, নরমাল ভেরিয়েবল হিসেবে নয়।

NSString *foodItem = @"Beef Pizza";

ভেরিয়েবল ডিক্লেয়ারেশন বাদে বাকি সব সময়ের জন্য Objective-C এর সিনট্যাক্সকে পয়েন্টার হিসেবে কাজ করার উপযোগী করেই তৈরি করা হয়েছে। তাই, একবার একটি অবজেক্ট পয়েন্টার ডিফাইন করার পর থেকেই সেটাকে একটা নরমাল ভেরিয়েবল মনে করে সেটার সাথে ডিল করা যেতে পারে। এতে করে ব্যপারটা সহজবোধ্য হবে।

পরের চ্যাপ্টারঃ
ব্যাসিক C এর ফাংশন নিয়ে একটু আলোচনা করেই Objective-C তে ফাংশনের ডিক্লেয়ারেশন, ডেফিনেশন নিয়ে আলোচনা হবে। তার পর পরই, এই যে Objective-C আমাদের প্রিয় C এর সাথে object oriented feature জুড়ে দিয়ে C দিয়েও OOP বেজড প্রোগ্রামিং করার রাস্তা তৈরি করলো, সেটার ব্যবহার অর্থাৎ Objective-C এর ক্লাস, প্রোপার্টি, মেথড নিয়ে আলোচনা হবে।

পরের চ্যাপ্টার – ৩ঃ OBJECTIVE-C তে ফাংশন ও এর ব্যবহার