Author: Pooja Kotwani

  • Dynamic Binding in Objective-C

    Dynamic Binding in detail

    Dynamic binding refers to the process of linking a function call to its actual definition at runtime instead of compile time. In Objective-C, this feature allows greater flexibility and avoids the limitations of static binding, where method resolution occurs at build time. Dynamic binding is also referred to as late binding.

    With dynamic binding, a specific method to execute is determined during program execution based on the object’s type. This feature is essential for enabling polymorphism, making it possible for a single method call to operate on different types of objects seamlessly.

    Usage of Dynamic Binding

    Dynamic binding in Objective-C allows a single method name to handle multiple types of objects. It simplifies debugging, reduces code complexity, and enhances program flexibility. All method resolution in Objective-C happens at runtime through the combination of method names and the receiving objects.

    Example 1

    #import <Foundation/Foundation.h>
    
    @interface Triangle : NSObject {
        float area;
    }
    - (void)calculateAreaWithBase:(CGFloat)base andHeight:(CGFloat)height;
    - (void)displayArea;
    @end
    
    @implementation Triangle
    - (void)calculateAreaWithBase:(CGFloat)base andHeight:(CGFloat)height {
        area = (base * height) / 2;
    }
    - (void)displayArea {
        NSLog(@"Area of Triangle: %f", area);
    }
    @end
    
    @interface Rectangle : NSObject {
        float area;
    }
    - (void)calculateAreaWithLength:(CGFloat)length andBreadth:(CGFloat)breadth;
    - (void)displayArea;
    @end
    
    @implementation Rectangle
    - (void)calculateAreaWithLength:(CGFloat)length andBreadth:(CGFloat)breadth {
        area = length * breadth;
    }
    - (void)displayArea {
        NSLog(@"Area of Rectangle: %f", area);
    }
    @end
    
    int main() {
        Triangle *triangle = [[Triangle alloc] init];
        [triangle calculateAreaWithBase:10.0 andHeight:5.0];
    
        Rectangle *rectangle = [[Rectangle alloc] init];
        [rectangle calculateAreaWithLength:8.0 andBreadth:4.0];
    
        NSArray *shapes = @[triangle, rectangle];
    
        id object1 = shapes[0];
        [object1 displayArea];
    
        id object2 = shapes[1];
        [object2 displayArea];
    
        return 0;
    }

    Output:

    Area of Circle: 153.938400
    Area of Square: 36.000000
    Key Differences: Dynamic Binding vs Static Binding
    Dynamic BindingStatic Binding
    The method is resolved at runtime.The method is resolved at compile time.
    Known as late binding.Known as early binding.
    Applies to real objects.Does not apply to real objects.
    Uses virtual functions.Uses normal function calls.
    Supports polymorphism.Does not support polymorphism.
    Execution is slower due to runtime resolution.Execution is faster since it is resolved at compile time.
  • Extensions in Objective-C

    Extensions in detail

    In Objective-C, Extensions (also known as Class Extensions) are a special type of category where methods must be declared within the main implementation block of the associated class. This allows overriding publicly declared property attributes. Unlike regular categories, class extensions can only be applied to classes where the source code is available during compile time, meaning the class and extension are compiled together.

    One common use of extensions is to convert a read-only property into a read-write property within a class implementation.

    Extensions do not have a name and are often referred to as anonymous categories. These extensions allow developers to add private methods or instance variables to a class. They promote code encapsulation, reusability, and modularity, which are key concepts in object-oriented programming.

    Specifications of Extensions
    • Extensions can declare private methods that are specific to a class.
    • Only classes with the source code available at compile time can be extended.
    • Extensions can override publicly declared property attributes.
    • Extensions are frequently used to define private methods or properties used internally in the class implementation.

    Syntax for Declaring an Extension

    Extensions are declared using the @interface keyword followed by parentheses () without specifying subclass inheritance.

    @interface ClassName ()
    @end

    Example of an Extension

    Here, we define a class Calculator and use an extension to declare a private method.

    #import <Foundation/Foundation.h>
    
    // Public interface
    @interface Calculator : NSObject
    
    - (int)addNumber:(int)a withNumber:(int)b;
    
    @end
    
    // Class extension for private method
    @interface Calculator ()
    
    - (void)displayResult:(int)result;
    
    @end
    
    @implementation Calculator
    
    - (int)addNumber:(int)a withNumber:(int)b {
        int sum = a + b;
        [self displayResult:sum];
        return sum;
    }
    
    // Private method implementation
    - (void)displayResult:(int)result {
        NSLog(@"The result is: %d", result);
    }
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Calculator *calc = [[Calculator alloc] init];
            [calc addNumber:10 withNumber:20];
        }
        return 0;
    }

    Output:

    The result is: 30

    Example of a Private Extension

    Private methods are another common use case for class extensions. By forward-declaring private methods in an extension, we ensure the compiler verifies their existence in the implementation block. Since extensions are defined within the implementation file, they remain hidden from other classes, mimicking private methods.

    #import <Foundation/Foundation.h>
    
    // Public interface
    @interface MessagePrinter : NSObject
    
    - (void)printMessage;
    
    @end
    
    // Class extension to declare private method
    @interface MessagePrinter ()
    
    - (void)prepareToPrint;
    
    @end
    
    // Implementation
    @implementation MessagePrinter {
        BOOL _isPrepared;
    }
    
    - (void)printMessage {
        if (!_isPrepared) {
            [self prepareToPrint];
            _isPrepared = YES;
        }
        NSLog(@"Hello, Objective-C!");
    }
    
    // Private method implementation
    - (void)prepareToPrint {
        NSLog(@"Preparing to print...");
    }
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            MessagePrinter *printer = [[MessagePrinter alloc] init];
            [printer printMessage];
        }
        return 0;
    }

    Output:

    Preparing to print...
    Hello, Objective-C!
  • Posing in Objective-C

    In Objective-C, posing is a runtime mechanism that allows one class to completely replace another class so that all messages sent to the original class are handled by the posing class. The class that replaces the original is said to pose as it.

    ⚠️ Important (Modern Reality)
    Class posing is deprecated and not supported with ARC. It existed in older Objective-C runtimes (pre–macOS 10.5). Today, it is mainly a theoretical and historical concept, useful for understanding the Objective-C runtime.


    What Is Posing?

    Posing enables a subclass to assume the identity of its superclass at runtime. Once posing occurs:

    • The original class is replaced globally
    • All messages to the original class are redirected to the posing class
    • Existing instances behave as instances of the posing class

    This was historically used to:

    • Intercept and customize behavior
    • Extend or fix system classes
    • Aid debugging and testing

    Key Characteristics of Posing

    • Only a subclass can pose as its superclass
    • Posing affects the entire program
    • The original class becomes unavailable
    • Must occur before the original class is used
    • Unsafe and hard to debug (reason for deprecation)

    Important Clarification (Very Common Confusion)

    Many examples online confuse class posing with object-level class swapping.

    ❌ Not Posing:

    Using object_setClass(obj, NewClass)
    This only changes the class of a single object instance, not the class itself.

    ✅ True Posing (Legacy):

    Using +poseAsClass:
    This replaces the entire class globally.


    True (Legacy) Posing Syntax

    [PosingClass poseAsClass:[OriginalClass class]];
    

    Once executed:

    • OriginalClass is replaced everywhere
    • All messages to OriginalClass go to PosingClass

    Conceptual Example (Legacy Runtime Only)

    Original Class

    @interface Printer : NSObject
    - (void)printMessage;
    @end
    
    @implementation Printer
    - (void)printMessage {
        NSLog(@"Original Printer");
    }
    @end
    

    Posing Class

    @interface DebugPrinter : Printer
    @end
    
    @implementation DebugPrinter
    + (void)load {
        [self poseAsClass:[Printer class]];
    }
    
    - (void)printMessage {
        NSLog(@"Debug Printer (Posing)");
    }
    @end
    

    Usage

    Printer *p = [[Printer alloc] init];
    [p printMessage];
    

    Output

    Debug Printer (Posing)
    

    Even though Printer is instantiated, the runtime routes the call to DebugPrinter.


    “Static” vs “Dynamic” Posing (Clarified)

    Static Posing (Historical Concept)

    • Occurs during program load (+load)
    • Fixed for the program’s lifetime
    • Requires recompilation to change

    Dynamic Runtime Tricks (Not True Posing)

    • Using object_setClass
    • Affects only specific objects
    • Does not replace the class globally
    • Often mislabelled as “dynamic posing” (incorrect)

    Why Posing Was Removed

    Posing caused serious problems:

    • Global side effects
    • Unpredictable behavior
    • Difficult debugging
    • Breaks encapsulation
    • Unsafe with multithreading
    • Incompatible with ARC

    Apple deprecated posing in favor of safer alternatives.


    Modern Alternatives to Posing

    1. Categories (Most Common)

    Add methods without replacing the class.

    @interface Printer (Debug)
    - (void)debugPrint;
    @end
    

    2. Method Swizzling (Advanced, Use Carefully)

    Swap method implementations at runtime.

    method_exchangeImplementations(original, swizzled);
    

    3. Subclassing

    Override behavior in a controlled, object-oriented way.


    4. Composition (Best Practice)

    Wrap behavior instead of replacing it.


    Interview Perspective

    If asked about posing:

    • Clearly state it is deprecated
    • Explain what it does conceptually
    • Clarify the difference between class posing and object_setClass
    • Mention modern alternatives

    This shows strong understanding of the Objective-C runtime.


    Summary

    • Posing allows a subclass to completely replace its superclass
    • All messages to the original class are redirected
    • Extremely powerful but unsafe
    • Deprecated and unsupported with ARC
    • Replaced by categories, swizzling, subclassing, and composition

  • Categories in Objective-C

    Categories in detail

    Categories are an essential concept in the Objective-C programming language. They enable developers to extend the functionality of existing classes without altering their original implementation. This discussion covers the purpose of categories, their use cases, and how they can be implemented with examples.

    What are Categories?

    In Objective-C, categories allow the addition of new methods and properties to existing classes without the need for subclassing. This feature provides a convenient way to enhance the functionality of a class without duplicating code. Additionally, categories help organize code into distinct logical sections, improving its readability and maintainability.

    To use categories in Objective-C, you must first define them. This involves creating a header file with the same name as the class you wish to extend, appended with the word “Category.” Within the header file, you define the @interface block for the category, listing the new methods and properties. The @implementation block then includes the actual logic for these additions.

    Syntax:

    @interface ClassName (CategoryName)
    // Method declarations go here
    @end
    Steps to Implement Categories

    Step 1: Define a Category: For example, suppose you have a class MyClass and wish to create a category named MyCategory that adds a new method and property. You would start by creating a header file (MyClass+MyCategory.h) and defining the @interface block as follows:

    @interface MyClass (MyCategory)
    
    @property (nonatomic, strong) NSString *myString;
    - (void)greet;
    
    @end

    Step 2: Implement the Category: In the implementation file (MyClass+MyCategory.m), you add the logic for the declared method and property:

    @implementation MyClass (MyCategory)
    
    @synthesize myString;
    
    - (void)greet {
        NSLog(@"Hello, Objective-C!");
    }
    
    @end

    Step 3: Use the Category: Once implemented, you can use the extended functionality of MyClass like this:

    Example:

    MyClass *myInstance = [[MyClass alloc] init];
    [myInstance greet];

    Example

    Here is an example of using categories in Objective-C:

    // Importing required library
    #import <Foundation/Foundation.h>
    
    // Creating a category
    @interface NSString (Reversed)
    + (NSString *)reverseString:(NSString *)originalString;
    @end
    
    // Implementing the category
    @implementation NSString (Reversed)
    
    + (NSString *)reverseString:(NSString *)originalString {
        NSMutableString *reversedString = [NSMutableString stringWithCapacity:[originalString length]];
        for (NSInteger i = [originalString length] - 1; i >= 0; i--) {
            [reversedString appendFormat:@"%c", [originalString characterAtIndex:i]];
        }
        return reversedString;
    }
    
    @end
    
    // Main program
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSString *original = @"Objective-C";
            NSString *reversed = [NSString reverseString:original];
            NSLog(@"Original: %@, Reversed: %@", original, reversed);
        }
        return 0;
    }

    Output:

    Original: Objective-C, Reversed: C-evitcejbO
  • Data Encapsulation in Objective-C

    Data Encapsulation in detail

    Encapsulation is a core concept in Object-Oriented Programming (OOP), designed to bundle data and the methods that operate on the data within a single unit, while shielding them from outside interference or misuse. This concept gives rise to data hiding, which is essential for maintaining data integrity and writing robust, maintainable code in Objective-C.

    Objective-C enables encapsulation and data hiding through the creation of user-defined types, known as classes.

    Syntax and Related Keywords

    In Objective-C, encapsulation is implemented by declaring instance variables within the @interface section of a class and controlling their visibility using access specifiers.

    Access Specifiers:

    • @public: Members marked as public are accessible from anywhere, even outside the class.
    • @private: Private members are only accessible within the defining class.
    • @protected: Protected members are accessible within the class and its subclasses.

    Example:

    // Bike.h
    #import <Foundation/Foundation.h>
    
    @interface Bike : NSObject {
        @private
        NSString *_bikeModel;  // Private instance variable
        @public
        float _fuelCapacity;   // Public instance variable
    }
    
    @end

    Explanation:

    The Bike class defines a private instance variable _bikeModel and a public instance variable _fuelCapacity. The @private and @public keywords determine their visibility.

    Example Code for Encapsulation

    #import <Foundation/Foundation.h>
    
    // Bike class interface
    @interface Bike : NSObject {
        @private
        NSString *_bikeModel;   // Private instance variable for the bike model
        float _fuelLevel;       // Private instance variable for the fuel level
    }
    
    // Initialization method
    - (instancetype)initWithModel:(NSString *)model fuelLevel:(float)fuelLevel;
    
    // Methods to interact with the Bike object
    - (void)startRide;
    - (void)ride;
    
    @end
    
    // Bike class implementation
    @implementation Bike
    
    // Initialization method implementation
    - (instancetype)initWithModel:(NSString *)model fuelLevel:(float)fuelLevel {
        self = [super init];
        if (self) {
            _bikeModel = [model copy];  // Copy the model name
            _fuelLevel = fuelLevel;    // Set the fuel level
        }
        return self;
    }
    
    // Method to start the ride
    - (void)startRide {
        NSLog(@"%@ is ready to hit the road!", _bikeModel);
    }
    
    // Method to simulate riding the bike
    - (void)ride {
        if (_fuelLevel > 0) {
            NSLog(@"%@ is now cruising!", _bikeModel);
            _fuelLevel -= 5.0;  // Reduce fuel level as the bike rides
        } else {
            NSLog(@"%@ is out of fuel. Please refuel to continue.", _bikeModel);
        }
    }
    
    @end
    
    // Main function
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // Create an instance of the Bike class
            Bike *myBike = [[Bike alloc] initWithModel:@"Yamaha" fuelLevel:20.0];
    
            // Start the ride and simulate riding
            [myBike startRide];
            [myBike ride];
            [myBike ride];
        }
        return 0;
    }

    Output:

    Yamaha is ready to hit the road!
    Yamaha is now cruising!
    Yamaha is now cruising!
    Properties in Objective-C

    Objective-C provides properties to simplify getter and setter methods for instance variables. These are declared with the @property keyword, and getter and setter methods are generated automatically using @synthesize.

    Syntax:

    // Bike.h
    #import <Foundation/Foundation.h>
    
    @interface Bike : NSObject
    
    @property(nonatomic, strong) NSString *bikeModel;  // Public property
    @property(nonatomic) float fuelLevel;             // Public property
    
    @end

    Explanation:

    Here, @property defines properties bikeModel and fuelLevel with attributes like nonatomic and strong for memory management and thread safety.

    Synthesizing Accessors

    You can use the @synthesize keyword to automatically generate getter and setter methods for properties.

    Syntax:

    // Bike.m
    @implementation Bike
    
    @synthesize bikeModel = _bikeModel; // Synthesize getter and setter for bikeModel
    
    @end

    Example Code with Custom Logic

    #import <Foundation/Foundation.h>
    
    // Calculator class interface
    @interface Calculator : NSObject {
        NSInteger total;  // Private instance variable
    }
    
    // Method declarations
    - (id)initWithInitialValue:(NSInteger)initialValue;
    - (void)addValue:(NSInteger)value;
    - (NSInteger)getTotal;
    
    @end
    
    // Calculator class implementation
    @implementation Calculator
    
    // Initialize with an initial value
    - (id)initWithInitialValue:(NSInteger)initialValue {
        total = initialValue;
        return self;
    }
    
    // Add a value to the total
    - (void)addValue:(NSInteger)value {
        total += value;
    }
    
    // Retrieve the total
    - (NSInteger)getTotal {
        return total;
    }
    
    @end
    
    // Main function
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // Create an instance of Calculator
            Calculator *calc = [[Calculator alloc] initWithInitialValue:100];
    
            // Perform operations
            [calc addValue:20];
            [calc addValue:30];
    
            // Display the total
            NSLog(@"The total sum is %ld", [calc getTotal]);
        }
        return 0;
    }

    Output:

    The total sum is 150
  • Inheritance in Objective-C

    Inheritance is a programming concept where a subclass or child class derives attributes and methods from a parent or superclass. This enables code reuse and allows subclasses to extend or override the behavior of the superclass. In Objective-C, inheritance is implemented using the class syntax. A class inheriting from another class is known as a “subclass,” while the inherited class is called the “superclass.”

    Syntax for Subclass Declaration:

    @interface SubclassName : SuperclassName
    // Additional methods and properties for the subclass
    @end

    A subclass inherits all the instance variables, properties, and methods from its superclass. Moreover, a subclass can override methods from the superclass to provide its own implementation.

    Example Program:

    #import <Foundation/Foundation.h>
    
    @interface Vehicle : NSObject {
        NSString *model;
        NSInteger year;
    }
    
    - (id)initWithModel:(NSString *)modelName andYear:(NSInteger)modelYear;
    - (void)displayDetails;
    
    @end
    
    @implementation Vehicle
    
    - (id)initWithModel:(NSString *)modelName andYear:(NSInteger)modelYear {
        model = modelName;
        year = modelYear;
        return self;
    }
    
    - (void)displayDetails {
        NSLog(@"Model: %@", model);
        NSLog(@"Year: %ld", year);
    }
    
    @end
    
    @interface Car : Vehicle {
        NSString *fuelType;
    }
    
    - (id)initWithModel:(NSString *)modelName andYear:(NSInteger)modelYear andFuelType:(NSString *)type;
    - (void)displayDetails;
    
    @end
    
    @implementation Car
    
    - (id)initWithModel:(NSString *)modelName andYear:(NSInteger)modelYear andFuelType:(NSString *)type {
        self = [super initWithModel:modelName andYear:modelYear];
        if (self) {
            fuelType = type;
        }
        return self;
    }
    
    - (void)displayDetails {
        [super displayDetails];
        NSLog(@"Fuel Type: %@", fuelType);
    }
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSLog(@"Vehicle Information:");
            Vehicle *vehicle = [[Vehicle alloc] initWithModel:@"Sedan" andYear:2020];
            [vehicle displayDetails];
    
            NSLog(@"Car Details:");
            Car *car = [[Car alloc] initWithModel:@"SUV" andYear:2023 andFuelType:@"Diesel"];
            [car displayDetails];
        }
        return 0;
    }

    Output:

    Vehicle Information:
    Model: Sedan
    Year: 2020
    Car Details:
    Model: SUV
    Year: 2023
    Fuel Type: Diesel

    Output:

    The product of a and b is: 56

    Types of Inheritance in Objective-C

    Objective-C supports two types of inheritance:

    • Single Inheritance
    • Multiple Inheritance
    Single Inheritance

    Single inheritance is the most commonly used form in Objective-C. It allows a subclass to inherit methods and attributes from only one superclass. The subclass can override methods from the superclass and add new properties or methods.

    Example Program:

    // Animal.h
    @interface Animal : NSObject
    @property NSString *species;
    - (void)makeSound;
    @end
    
    // Animal.m
    @implementation Animal
    - (void)makeSound {
        NSLog(@"The animal makes a sound");
    }
    @end
    
    // Dog.h
    @interface Dog : Animal
    @end
    
    // Dog.m
    @implementation Dog
    - (void)makeSound {
        NSLog(@"The dog barks");
    }
    @end
    
    // main.m
    #import <Foundation/Foundation.h>
    #import "Dog.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Dog *myDog = [[Dog alloc] init];
            myDog.species = @"Golden Retriever";
            [myDog makeSound];
        }
        return 0;
    }

    Output:

    The dog barks
    Multiple Inheritance in Objective-C

    Objective-C doesn’t directly support multiple inheritance. However, it can be achieved using protocols. A class can conform to multiple protocols, effectively inheriting methods from each.

    Example Program:

    // Flying.h
    @protocol Flying
    - (void)fly;
    @end
    
    // Swimming.h
    @protocol Swimming
    - (void)swim;
    @end
    
    // Bird.h
    #import "Flying.h"
    #import "Swimming.h"
    
    @interface Bird : NSObject <Flying, Swimming>
    @property NSString *name;
    @end
    
    // Bird.m
    @implementation Bird
    - (void)fly {
        NSLog(@"%@ is flying", self.name);
    }
    - (void)swim {
        NSLog(@"%@ is swimming", self.name);
    }
    @end
    
    // main.m
    #import <Foundation/Foundation.h>
    #import "Bird.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Bird *penguin = [[Bird alloc] init];
            penguin.name = @"Penguin";
            [penguin swim];
            [penguin fly];
        }
        return 0;
    }

    Output:

    Penguin is swimming
    Penguin is flying
  • Classes & Object in Objective – C

    Objective-C is an object-oriented programming language widely used for developing applications for Apple platforms such as iOS, macOS, watchOS, and tvOS. In Objective-C, classes and objects form the core of object-oriented programming. A class serves as a blueprint that defines the properties and behaviors of objects, while an object is an instance of a class.

    Objective-C supports several types of classes, including abstract classes, concrete classes, and root classes. Additionally, it offers various types of objects such as mutable objects, immutable objects, and singleton objects. Below, we explore these concepts in detail with syntax, keywords, and examples.

    Syntax and Keywords for Classes and Objects

    Syntax and Keywords for Classes and Objects

    The syntax for declaring a class in Objective-C is as follows:

    @interface ClassName : SuperClassName
    {
        // Instance variables declaration
    }
    
    // Properties declaration
    // Methods declaration
    
    @end
    • ClassName: The name of the class.
    • SuperClassName: The name of the class from which ClassName inherits. If no superclass is specified, NSObject is the default.
    • Instance variables are declared within {}.
    • Properties and methods follow the instance variable declaration.
    Class Implementation

    The @implementation keyword is used to provide the implementation of the methods declared in the @interface section:

    @implementation ClassName
    
    // Methods implementation
    
    @end
    Accessing Properties and Methods

    Once an object is created, its properties and methods can be accessed using dot notation:

    NSString *myCarMake = myCar.make;
    Keywords and Symbols in Objective-C
    • @interface: Indicates the beginning of a class declaration.
    • ClassName: The name of the class is declared.
    • SuperclassName: The name of the class that the class being declared inherits from. If the class doesn’t inherit from any class, NSObject is used as the default superclass.
    • @property: Declares a property of the class. The attributes and type parts are optional and specify the attributes and type of the property.
    • type: The type of the property.
    • propertyName: The name of the property.
    • –: Indicates an instance method.
    • +: Indicates a class method.
    • returnType: The return type of the method.
    • methodName: The name of the method.
    • parameterType: The type of the method parameter.
    • parameterName: The name of the method parameter.

    Types of Classes

    Abstract Class

    An abstract class serves as a base class that cannot be instantiated. It provides common methods and properties for its subclasses.

    Example:

    @interface AbstractClass : NSObject
    - (void)method1;
    - (void)method2;
    @end
    
    @implementation AbstractClass
    - (void)method1 {
        NSLog(@"Method 1");
    }
    - (void)method2 {
        NSLog(@"Method 2");
    }
    @end

    Output:

    Method 1
    Method 2
    Concrete Class

    A concrete class defines specific methods and properties that can be instantiated.

    Example:

    @interface ConcreteClass : NSObject
    @property (nonatomic, strong) NSString *property1;
    @property (nonatomic, assign) int property2;
    - (void)method1;
    - (void)method2;
    @end
    
    @implementation ConcreteClass
    @synthesize property1;
    @synthesize property2;
    - (void)method1 {
        NSLog(@"Method 1");
    }
    - (void)method2 {
        NSLog(@"Method 2");
    }
    @end

    Output:

    Property1: Test String
    Property2: 42
    Method 1
    Method 2
    Root Class

    The root class in Objective-C is NSObject, which provides basic methods and properties common to all classes.

    #import <Foundation/Foundation.h>
    
    @interface MyObject : NSObject
    @property (nonatomic, strong) NSString *name;
    - (instancetype)initWithName:(NSString *)name;
    @end
    
    @implementation MyObject
    - (instancetype)initWithName:(NSString *)name {
        self = [super init];
        if (self) {
            self.name = name;
        }
        return self;
    }
    @end

    Output:

    Name: Test Name

    Types of Objects

    Mutable Object

    A mutable object can be modified after creation. These are typically created using the NSMutable prefix.

    Example:

    NSMutableString *mutableString = [NSMutableString stringWithString:@"Hello"];
    [mutableString appendString:@" World!"];
    NSLog(@"%@", mutableString);

    Output:

    Hello World!
    Immutable Object

    An immutable object cannot be modified after creation. These are typically created using the NS prefix.

    Example:

    NSString *immutableString = @"Hello World!";
    NSLog(@"%@", immutableString);

    Output:

    Hello World!
    Singleton Object

    A singleton ensures a single instance of a class is created during the application’s lifecycle.

    Example:

    @interface MySingleton : NSObject
    + (instancetype)sharedInstance;
    @end
    
    @implementation MySingleton
    static MySingleton *sharedInstance = nil;
    
    + (instancetype)sharedInstance {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedInstance = [[self alloc] init];
        });
        return sharedInstance;
    }
    @end

    Example 1: Calculating Box Volume

    #import <Foundation/Foundation.h>
    
    @interface Box : NSObject {
        double len;
        double br;
        double h;
    }
    @property (nonatomic, readwrite) double h;
    - (double)vol;
    @end
    
    @implementation Box
    @synthesize h;
    
    - (id)init {
        self = [super init];
        len = 4.0;
        br = 6.0;
        return self;
    }
    
    - (double)vol {
        return len * br * h;
    }
    @end
    
    int main() {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        Box *box1 = [[Box alloc] init];
        Box *box2 = [[Box alloc] init];
    
        box1.h = 5.0;
        box2.h = 10.0;
    
        NSLog(@"Volume of Box1: %f", [box1 vol]);
        NSLog(@"Volume of Box2: %f", [box2 vol]);
    
        [pool drain];
        return 0;
    }

    Output:

    Volume of Box1: 120.000000
    Volume of Box2: 240.000000

    Example 2: Creating a Person Object

    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject {
        NSString *name;
        int age;
    }
    - (void)setName:(NSString *)newName;
    - (NSString *)name;
    - (void)setAge:(int)newAge;
    - (int)age;
    @end
    
    @implementation Person
    - (void)setName:(NSString *)newName {
        name = newName;
    }
    - (NSString *)name {
        return name;
    }
    - (void)setAge:(int)newAge {
        age = newAge;
    }
    - (int)age {
        return age;
    }
    @end
    
    int main() {
        Person *person = [[Person alloc] init];
        [person setName:@"John"];
        [person setAge:30];
    
        NSLog(@"Name: %@, Age: %d", [person name], [person age]);
        [person release];
    
        return 0;
    }

    Output:

    Name: John, Age: 30
  • Command Line Arguments in Objective-C

    Command Line Arguments in Detail

    In Objective-C, command-line arguments are text strings passed to a program when it runs from the command line. They provide input parameters or options that control the program’s behavior or supply data for processing.

    The main function of an Objective-C program receives two parameters: argc and argv.

    • argc (short for “argument count”) is an integer representing the number of arguments passed to the program.
    • argv (short for “argument vector”) is an array of char pointers, each pointing to a command-line argument.
    ./example -debug config.json output.log

    In this case:

    1. argc will be 4, as there are four arguments.
    2. argv will hold the following values:

    • argv[0] = “./example”
    • argv[1] = “-debug”
    • argv[2] = “config.json”
    • argv[3] = “output.log”

    Example 1:

    // Objective-C program to demonstrate command-line arguments
    #import <Foundation/Foundation.h>
    
    int main (int argc, const char * argv[])
    {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        for (int i = 0; i < argc; i++)
        {
            NSString *arg = [NSString stringWithUTF8String:argv[i]];
            NSLog(@"Argument %d: %@", i, arg);
        }
        [pool drain];
        return 0;
    }

    Output:

    ./example -debug config.json output.log

    The output will be:

    Argument 0: ./example
    Argument 1: -debug
    Argument 2: config.json
    Argument 3: output.log
  • Error, Log and File Handling in Objective-C

    Log Handling in Objective-C

    Objective-C is widely used for building applications on Apple’s macOS, iOS, and iPadOS platforms. Logging plays a vital role in software development, helping developers understand the state of the program and diagnose issues effectively. In Objective-C, logging is often done using the built-in NSLog() function.

    The NSLog() function outputs information to both the console and a log file, making it a reliable tool for debugging and troubleshooting. This guide explores the basics of log handling in Objective-C, along with advanced third-party logging solutions.

    Methods of Log Handling in Objective-C

    NSLog() FunctionThe NSLog() function is the simplest and most commonly used method for logging in Objective-C. It supports logging messages at different levels of importance, such as informational messages, warnings, and errors. This function is part of the Cocoa framework.

    The NSLog() function accepts a format string as its first argument, followed by optional variables. Placeholders within the format string, like %d for integers and %@ for objects, are replaced with the actual values at runtime.

    Syntax:

    NSLog(@"This is a log message: %@", myString);

    Keywords Related to NSLog():

    • Console: Displays log messages in the terminal or IDE output window.
    • Log file: Stores log messages for future reference.
    • Placeholder: Symbols used to insert variable values into the log message.

    Example 1: Logging a Simple Message

    This example calculates the product of two numbers and logs the result:

    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int a = 7;
            int b = 8;
            int product = a * b;
            NSLog(@"The product of a and b is: %d", product);
        }
        return 0;
    }

    Output:

    The product of a and b is: 56

    Example 2: Logging User Input

    In this example, user input is captured and logged:

    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // Input user's favorite color
            NSLog(@"Please enter your favorite color:");
            char input[50];
            scanf("%s", input);
            NSString *favoriteColor = [NSString stringWithUTF8String:input];
    
            // Log the user's favorite color
            NSLog(@"Your favorite color is: %@", favoriteColor);
        }
        return 0;
    }

    Output:

    Please enter your favorite color:
    Blue
    Your favorite color is: Blue
    Third-Party Logging Libraries

    For more advanced logging features, third-party libraries like CocoaLumberjack are widely used. CocoaLumberjack offers extensive options, such as customizable log levels, file and console output, and log file rotation.

    Key Features of CocoaLumberjack:

    • Multiple log levels: DDLogError, DDLogWarn, DDLogInfo, DDLogDebug, and DDLogVerbose.
    • High performance, suitable for large-scale applications.
    • Ability to log messages to multiple destinations (console, file, remote server).

    Syntax:

    DDLogInfo(@"Informational message");

    Keywords Related to CocoaLumberjack:

    • Macros: Shortcuts like DDLogError simplify logging.
    • Log Level: Specifies message importance (e.g., info, warning, error).
    • Configuration: Customizable settings for logging output.
    • Log File Rotation: Automatic creation and management of log files.

    Example 1: Using CocoaLumberjack for Logging

    This program demonstrates how to log messages at different severity levels:

    #import <CocoaLumberjack/CocoaLumberjack.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            [DDLog addLogger:[DDTTYLogger sharedInstance]];
    
            DDLogInfo(@"App started successfully");
            DDLogWarn(@"This is a warning message");
            DDLogError(@"An error has occurred");
        }
        return 0;
    }

    Output:

    App started successfully
    This is a warning message
    An error has occurred

    File Handling in Objective-C

    File handling is a fundamental feature in programming, and Objective-C provides robust tools to manage it effectively. Developers can create, read, modify, and delete files on the file system. These operations are useful for saving user data, tracking application logs, or storing game progress. Objective-C uses the Foundation framework, which offers high-level interfaces for managing files and directories, hiding much of the complexity associated with file I/O.

    By leveraging these tools, developers can interact with the file system seamlessly and intuitively, making file handling in Objective-C a practical and efficient approach.

    Types and Subtypes

    File handling operations in Objective-C are generally categorized into two main types: reading and writing.

    Reading

    • Sequential Access: Reads data linearly from start to finish, ideal for processing entire files.
    • Random Access: Reads specific parts of a file, useful when only particular sections of data are required.

    Writing

    • Overwrite: Replaces existing content with new data.
    • Append: Adds new data to the end of an existing file.

    Example:

    // Writing to a file
    NSString *filePath = @"/path/to/sample.txt";
    NSString *content = @"Objective-C is amazing!";
    [content writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
    
    // Reading from a file
    NSString *fileContent = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"%@", fileContent);

    Output:

    Objective-C is amazing!

    Syntax and Keywords

    Objective-C provides several classes and methods for file handling via the Foundation framework. Commonly used classes include:

    • NSString and NSData: For working with textual and binary data.
    • NSFileManager: For managing file operations like creation, deletion, and moving files or directories.
    • NSFileHandle: For performing low-level file I/O operations.

    Example:

    // Creating a directory
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *directoryPath = @"/path/to/mydirectory";
    [fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:nil];
    
    // Writing binary data to a file
    NSString *filePath = [directoryPath stringByAppendingPathComponent:@"example.dat"];
    NSData *binaryData = [@"Binary data example" dataUsingEncoding:NSUTF8StringEncoding];
    [binaryData writeToFile:filePath atomically:YES];
    
    // Reading binary data from the file
    NSData *readData = [NSData dataWithContentsOfFile:filePath];
    NSString *dataContent = [[NSString alloc] initWithData:readData encoding:NSUTF8StringEncoding];
    NSLog(@"%@", dataContent);

    Output:

    Binary data example
    Methods Used in File Handling

    1. writeToFile:atomically:encoding:error:

    • Used to write string data to a file.
    NSString *filePath = @"/path/to/sample.txt";
    NSString *content = @"Learning Objective-C!";
    [content writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];

    2. stringWithContentsOfFile:encoding:error:

    • Used to read string data from a file.
    NSString *filePath = @"/path/to/sample.txt";
    NSString *content = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"%@", content); // Output: Learning Objective-C!

    3. createDirectoryAtPath:withIntermediateDirectories:attributes:error:

    • Creates a directory.
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *directoryPath = @"/path/to/newdirectory";
    [fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:nil];

    4. dataWithContentsOfFile:

    • Reads binary data from a file.
    NSString *filePath = @"/path/to/binaryfile.dat";
    NSData *data = [NSData dataWithContentsOfFile:filePath];
    NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@", content);

    Additional Examples

    Copying Data from One File to Another:

    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            if (argc < 3) {
                NSLog(@"Usage: %s source_file destination_file", argv[0]);
                return 1;
            }
    
            NSString *sourcePath = [NSString stringWithUTF8String:argv[1]];
            NSString *destinationPath = [NSString stringWithUTF8String:argv[2]];
    
            NSError *error;
            if ([[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:destinationPath error:&error]) {
                NSLog(@"File copied successfully.");
            } else {
                NSLog(@"Error copying file: %@", [error localizedDescription]);
            }
        }
        return 0;
    }

    Example of appending Data to a File :

    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            if (argc < 3) {
                NSLog(@"Usage: %s file_path data_to_append", argv[0]);
                return 1;
            }
    
            NSString *filePath = [NSString stringWithUTF8String:argv[1]];
            NSString *dataToAppend = [NSString stringWithUTF8String:argv[2]];
    
            NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
            if (fileHandle) {
                [fileHandle seekToEndOfFile];
                [fileHandle writeData:[dataToAppend dataUsingEncoding:NSUTF8StringEncoding]];
                [fileHandle closeFile];
                NSLog(@"Data appended successfully.");
            } else {
                NSLog(@"Error opening file.");
            }
        }
        return 0;
    }

    Example of checking If a File Exists and Deleting It:

    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            if (argc < 2) {
                NSLog(@"Usage: %s file_path", argv[0]);
                return 1;
            }
    
            NSString *filePath = [NSString stringWithUTF8String:argv[1]];
            NSFileManager *fileManager = [NSFileManager defaultManager];
    
            if ([fileManager fileExistsAtPath:filePath]) {
                NSError *error;
                if ([fileManager removeItemAtPath:filePath error:&error]) {
                    NSLog(@"File deleted successfully.");
                } else {
                    NSLog(@"Error deleting file: %@", [error localizedDescription]);
                }
            } else {
                NSLog(@"File does not exist.");
            }
        }
        return 0;
    }

    Error Handling in Objective-C

    In this article, we will explore error handling in Objective-C. Error handling is crucial for building robust applications. Various methods are available in Objective-C to handle errors, with examples and code snippets to guide you.

    What is Error Handling?

    Error handling refers to the process or mechanism in programming used to identify, catch, and respond to exceptional situations during program execution. In simple terms, it involves detecting errors and managing them effectively without impacting the program’s overall output.

    Errors can arise from multiple sources such as hardware failures, programming mistakes, incorrect input, etc. Thus, error handling is vital for creating reliable and stable software.

    In Objective-C, error handling ensures robust code by leveraging techniques such as the Cocoa error-handling pattern with NSError objects or exception handling using try and catch blocks. The Cocoa error-handling pattern is often the preferred approach for routine error handling, while exceptions are reserved for extraordinary situations.

    What is Exception Handling?

    While NSError is the standard for error handling, exception handling can be used for exceptional cases. Using try and catch blocks, errors are detected and handled without disrupting the program.

    Syntax:

    @try {
        // Code that may raise an exception
    }
    @catch (NSException *exception) {
        NSLog(@"An exception occurred: %@", [exception reason]);
    }
    @finally {
        // Code executed regardless of exceptions
    }
    NSError for Error Handling

    The NSError class is the most commonly used approach for error handling in Objective-C. It allows passing error information to the calling code via an out-parameter.

    Syntax:

    NSError *error = nil;
    NSData *data = [NSData dataWithContentsOfFile:@"example.txt" options:NSDataReadingUncached error:&error];
    
    if (error != nil) {
        NSLog(@"Error reading file: %@", [error localizedDescription]);
    } else {
        // Process data
    }

    Examples of Error Handling in Objective-C

    Example 1: Detecting Errors with NSError

    #import <Foundation/Foundation.h>
    
    BOOL performOperation(NSError **error) {
        if (error != NULL) {
            NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"Operation failed",
                                       NSLocalizedFailureReasonErrorKey: @"An unexpected condition occurred"};
            *error = [NSError errorWithDomain:@"com.example.error" code:100 userInfo:userInfo];
            return NO;
        }
        return YES;
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSError *error = nil;
            if (!performOperation(&error)) {
                NSLog(@"Error: %@", error.localizedDescription);
                NSLog(@"Reason: %@", error.localizedFailureReason);
            } else {
                NSLog(@"Operation completed successfully");
            }
        }
        return 0;
    }

    Example 2: Handling Division by Zero

    #import <Foundation/Foundation.h>
    
    @interface Calculator : NSObject
    - (NSInteger)divide:(NSInteger)numerator by:(NSInteger)denominator error:(NSError **)error;
    @end
    
    @implementation Calculator
    
    - (NSInteger)divide:(NSInteger)numerator by:(NSInteger)denominator error:(NSError **)error {
        if (denominator == 0) {
            if (error != NULL) {
                NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"Cannot divide by zero."};
                *error = [NSError errorWithDomain:@"CalculatorErrorDomain" code:101 userInfo:userInfo];
            }
            return 0;
        }
        return numerator / denominator;
    }
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Calculator *calculator = [[Calculator alloc] init];
            NSError *error = nil;
            NSInteger result = [calculator divide:10 by:0 error:&error];
    
            if (error) {
                NSLog(@"Error: %@", error.localizedDescription);
            } else {
                NSLog(@"Result: %ld", (long)result);
            }
        }
        return 0;
    }

    Output:

    Error: Cannot divide by zero.

    Example 3: Using NSException

    This example illustrates how to handle errors using exceptions:

    #import <Foundation/Foundation.h>
    
    void executeTask(void) {
        @throw [NSException exceptionWithName:@"TaskException"
                                       reason:@"An error occurred during task execution"
                                     userInfo:nil];
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            @try {
                executeTask();
            } @catch (NSException *exception) {
                NSLog(@"Exception: %@", exception.reason);
            }
        }
        return 0;
    }

    Example 4: Combining NSError and NSException

    This example demonstrates using both NSError and NSException together:

    #import <Foundation/Foundation.h>
    
    BOOL performComplexTask(NSError **error) {
        if (error != NULL) {
            NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"Complex task failed"};
            *error = [NSError errorWithDomain:@"com.example.error" code:102 userInfo:userInfo];
            return NO;
        }
        @throw [NSException exceptionWithName:@"ComplexTaskException"
                                       reason:@"An exception occurred"
                                     userInfo:nil];
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSError *error = nil;
    
            @try {
                if (!performComplexTask(&error)) {
                    NSLog(@"Error: %@", error.localizedDescription);
                } else {
                    NSLog(@"Task executed successfully");
                }
            } @catch (NSException *exception) {
                NSLog(@"Exception: %@", exception.reason);
            }
        }
        return 0;

    Output:

    Error: Complex task failed
    Exception: An exception occurred
  • Type Casting in Objective-C

    Type Casting and Their Types

    Objective-C is a programming language that was created in the 1980s and is widely used for developing software for the macOS and iOS platforms. One of Objective-C’s key features is its ability to perform typecasting. Type casting enables programmers to convert one data type to another, which is useful in a wide range of programming scenarios. This article provides an in-depth overview of type casting in Objective-C, including examples of all types and subtypes.

    What is Type Casting?

    Type casting in Objective-C is the process of converting one data type to another. Objective-C has several types and subtypes, including:

    • Implicit type conversion: The compiler performs this type of type casting automatically when a value is assigned to a variable of a different type.
    • Explicit type conversion: This is done by the programmer and necessitates the use of specific syntax and keywords.
    • Narrowing type conversion: Narrowing type conversion involves converting a larger data type to a smaller one, which can result in data loss.
    • Widening type conversion: In this type of type casting, a smaller data type is converted to a larger one without data loss.
    Syntax and Related Keywords

    In Objective-C, type casting is performed by using the appropriate keyword for the conversion. The most commonly used keywords for typecasting are:

    • (type) variableName: This syntax is used for explicit type conversion, where ‘type’ is the data type to which the variable is being converted and ‘variableName’ is the variable’s name.
    • intValue: Used to convert a string or float to an integer.
    • floatValue: Used to transform an integer or string into a floating-point number.
    • boolValue: Used to transform an object into a Boolean value.

    Examples of Type Casting in Objective-C

    Example 1: Implicit Type Conversion

    #import <Foundation/Foundation.h>
    
    int main (int argc, const char * argv[]) {
        @autoreleasepool {
            // Implicit type conversion example
            int number = 42;
            double result = number;
    
            NSLog(@"The value of number is %d", number);
            NSLog(@"The value of result is %f", result);
        }
        return 0;
    }

    Output:

    The value of number is 42
    The value of result is 42.000000

    Example 2: Explicit Type Conversion

    #import <Foundation/Foundation.h>
    
    int main (int argc, const char * argv[]) {
        @autoreleasepool {
            // Explicit type conversion example
            double value = 99.99;
            int roundedValue = (int)value;
    
            NSLog(@"The value of value is %.2f", value);
            NSLog(@"The value of roundedValue is %d", roundedValue);
        }
        return 0;
    }

    Output:

    The value of value is 99.99
    The value of roundedValue is 99

    Example 3: Narrowing Type Conversion

    #import <Foundation/Foundation.h>
    
    int main (int argc, const char * argv[]) {
        @autoreleasepool {
            // Narrowing type conversion example
            double largeValue = 12345.6789;
            float reducedValue = (float)largeValue;
    
            NSLog(@"The value of largeValue is %.4f", largeValue);
            NSLog(@"The value of reducedValue is %.4f", reducedValue);
        }
        return 0;
    }

    Output:

    The value of largeValue is 12345.6789
    The value of reducedValue is 12345.6797

    Examples of Type Casting in Objective-C

    Example 1: Implicit Type Conversion

    #import <Foundation/Foundation.h>
    
    int main (int argc, const char * argv[]) {
        @autoreleasepool {
            // Narrowing type conversion example
            double largeValue = 12345.6789;
            float reducedValue = (float)largeValue;
    
            NSLog(@"The value of largeValue is %.4f", largeValue);
            NSLog(@"The value of reducedValue is %.4f", reducedValue);
        }
        return 0;
    }

    Output:

    The value of number is 42
    The value of result is 42.000000

    Example 2: Explicit Type Conversion

    #import <Foundation/Foundation.h>
    
    int main (int argc, const char * argv[]) {
        @autoreleasepool {
            // Explicit type conversion example
            double value = 99.99;
            int roundedValue = (int)value;
    
            NSLog(@"The value of value is %.2f", value);
            NSLog(@"The value of roundedValue is %d", roundedValue);
        }
        return 0;
    }

    Output:

    The value of value is 99.99
    The value of roundedValue is 99

    Example 3: Narrowing Type Conversion

    #import <Foundation/Foundation.h>
    
    int main (int argc, const char * argv[]) {
        @autoreleasepool {
            // Narrowing type conversion example
            double largeValue = 12345.6789;
            float reducedValue = (float)largeValue;
    
            NSLog(@"The value of largeValue is %.4f", largeValue);
            NSLog(@"The value of reducedValue is %.4f", reducedValue);
        }
        return 0;
    }

    Output:

    The value of largeValue is 12345.6789
    The value of reducedValue is 12345.6797

    Example 4: Widening Type Conversion

    #import <Foundation/Foundation.h>
    
    int main (int argc, const char * argv[]) {
        @autoreleasepool {
            // Widening type conversion example
            char smallValue = 'A';
            int asciiValue = smallValue;
    
            NSLog(@"The value of smallValue is %c", smallValue);
            NSLog(@"The value of asciiValue is %d", asciiValue);
        }
        return 0;
    }

    Example 4: Widening Type Conversion

    #import <Foundation/Foundation.h>
    
    int main (int argc, const char * argv[]) {
        @autoreleasepool {
            // Widening type conversion example
            char smallValue = 'A';
            int asciiValue = smallValue;
    
            NSLog(@"The value of smallValue is %c", smallValue);
            NSLog(@"The value of asciiValue is %d", asciiValue);
        }
        return 0;
    }

    Output:

    The value of smallValue is A
    The value of asciiValue is 65