২- 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 তে ফাংশন ও এর ব্যবহার