Hiểu về Objective-C: Ngôn ngữ Kế thừa Bạn Vẫn Có Thể Cần

Chào mừng các bạn trở lại với series “Lộ trình học Lập trình viên iOS 2025“! Sau khi khám phá sức mạnh và sự thống trị của Swift, tìm hiểu những kiến thức cơ bản và nâng cao về ngôn ngữ này, cũng như cách xây dựng giao diện với UIKit và quản lý luồng, có thể bạn sẽ nghĩ rằng Swift là tất cả những gì bạn cần biết.

Thực tế, trong hành trình trở thành một lập trình viên iOS chuyên nghiệp, bạn chắc chắn sẽ gặp phải Objective-C – ngôn ngữ “cha đẻ” của hệ sinh thái Apple trong nhiều thập kỷ. Mặc dù Swift là tương lai, việc hiểu và có thể làm việc với Objective-C vẫn là một kỹ năng quý giá, thậm chí là cần thiết trong nhiều trường hợp. Bài viết này sẽ đưa bạn đi sâu vào thế giới của Objective-C, giúp bạn hiểu tại sao nó vẫn tồn tại và làm thế nào để làm quen với nó.

Tại sao Lập trình viên iOS Mới Vẫn Cần Quan tâm đến Objective-C?

Đối với nhiều lập trình viên iOS mới bắt đầu vào năm 2025, việc tập trung vào Swift là điều hiển nhiên và hợp lý. Như chúng ta đã thảo luận trong các bài viết trước như “Swift vs Objective-C: Ngôn Ngữ Nào Là Điểm Khởi Đầu Tốt Nhất Cho Lập Trình Viên iOS Vào Năm 2025?” và “Tại sao Swift Thống Trị Lập trình iOS: Lịch sử và Lợi ích“, Swift hiện là ngôn ngữ chính thức và được Apple đẩy mạnh. Tuy nhiên, bỏ qua Objective-C hoàn toàn sẽ là một thiếu sót lớn trong bộ kỹ năng của bạn.

Lý do chính rất đơn giản: Thế giới thực vẫn đầy rẫy mã nguồn Objective-C.

  • Dự án Kế thừa (Legacy Projects): Hàng nghìn ứng dụng iOS hiện có trên App Store được xây dựng hoàn toàn hoặc một phần đáng kể bằng Objective-C. Các công ty vẫn cần bảo trì, cập nhật và phát triển thêm tính năng cho những ứng dụng này. Nếu bạn gia nhập một công ty có lịch sử phát triển iOS lâu đời, khả năng cao bạn sẽ phải làm việc với Objective-C.
  • Thư viện Bên thứ Ba (Third-Party Libraries): Mặc dù nhiều thư viện phổ biến đã chuyển sang Swift hoặc cung cấp phiên bản Swift, vẫn có những thư viện, SDK hoặc các dự án mã nguồn mở quan trọng chỉ có sẵn dưới dạng Objective-C. Việc hiểu cách tích hợp và sử dụng chúng là cần thiết.
  • Hệ điều hành và Khung làm việc của Apple: Mặc dù các API mới của Apple thường được thiết kế thân thiện với Swift (như SwiftUI), phần lớn nền tảng của iOS, macOS, watchOS, tvOS được xây dựng trên các khung làm việc (framework) ban đầu viết bằng Objective-C và C. Việc hiểu Objective-C giúp bạn có cái nhìn sâu sắc hơn về cách hoạt động của hệ thống, đặc biệt khi bạn cần debug sâu hoặc tối ưu hiệu năng.
  • Cầu nối (Bridging): Swift có khả năng tương tác tuyệt vời với Objective-C. Để tận dụng hết sức mạnh của cả hai ngôn ngữ trong cùng một dự án, bạn cần hiểu cách cầu nối hoạt động, và điều này đòi hỏi kiến thức về Objective-C.

Tóm lại, việc học Objective-C không phải là để viết ứng dụng mới từ đầu bằng nó, mà là để trang bị cho mình khả năng làm việc với mã nguồn hiện có, mở rộng cơ hội nghề nghiệp và hiểu rõ hơn về nền tảng mà bạn đang phát triển trên đó.

Lịch sử Sơ lược về Objective-C

Objective-C ra đời vào những năm 1980 bởi Brad Cox và Tom Love, kết hợp hai thứ: ngôn ngữ C và các khái niệm hướng đối tượng từ Smalltalk. Ý tưởng là mang lập trình hướng đối tượng (OOP) – một chủ đề chúng ta đã thảo luận trong bài “OOP và Lập trình Hàm trong iOS: Chọn Lựa Nào Cho Từng Tình Huống?” – vào ngôn ngữ C vốn là ngôn ngữ thủ tục mạnh mẽ.

Ngôn ngữ này sau đó được NeXT Computer (công ty do Steve Jobs thành lập sau khi rời Apple) sử dụng làm ngôn ngữ chính cho hệ điều hành NeXTSTEP của họ. Khi Apple mua lại NeXT vào năm 1996, Objective-C cùng với các framework của NeXT (được gọi là Cocoa) trở thành nền tảng cho macOS và sau này là iOS. Trong hơn một thập kỷ, Objective-C là ngôn ngữ mặc định để phát triển ứng dụng trên các nền tảng của Apple, cho đến khi Swift ra đời vào năm 2014.

Sự pha trộn giữa C (ngôn ngữ cấp thấp với quản lý bộ nhớ thủ công và con trỏ) và Smalltalk (ngôn ngữ hướng đối tượng năng động với “messaging”) tạo nên sự khác biệt độc đáo cho Objective-C.

Objective-C và Swift: Những Khác biệt Cơ bản

Sự chuyển đổi từ Objective-C sang Swift đánh dấu một bước tiến lớn về cú pháp, an toàn và hiệu năng. Dưới đây là một số khác biệt cốt lõi:

Cú pháp (Syntax)

  • Objective-C: Sử dụng cú pháp C cho các phần thủ tục và cú pháp kiểu Smalltalk cho các thông điệp hướng đối tượng. Rất nhiều dấu ngoặc vuông `[]`, dấu chấm phẩy `;` ở cuối mỗi câu lệnh, và tiền tố `@` cho các từ khóa của Objective-C (như `@interface`, `@implementation`, `@property`).
  • Swift: Cú pháp hiện đại, sạch sẽ hơn, lấy cảm hứng từ nhiều ngôn ngữ khác. Không cần dấu chấm phẩy (trừ khi viết nhiều câu lệnh trên một dòng), sử dụng dấu chấm `.` để gọi phương thức/truy cập thuộc tính, sử dụng từ khóa rõ ràng (như `class`, `func`, `var`, `let`).
// Objective-C
NSString *greeting = @"Hello, World!";
NSLog(@"%@", greeting);

// Tạo đối tượng và gọi phương thức
MyClass *myObject = [[MyClass alloc] init];
[myObject doSomethingWithArgument:123];
// Swift
let greeting = "Hello, World!"
print(greeting)

// Tạo đối tượng và gọi phương thức
let myObject = MyClass() // Swift objects don't need explicit alloc/init like this
myObject.doSomething(withArgument: 123)

Thông điệp (Messaging) vs. Gọi phương thức (Method Calls)

  • Objective-C: Sử dụng “messaging” (gửi thông điệp) thay vì gọi phương thức trực tiếp. Khi bạn viết [object message], hệ thống runtime sẽ tìm kiếm phương thức phù hợp để thực thi. Điều này mang lại tính năng động mạnh mẽ.
  • Swift: Sử dụng gọi phương thức truyền thống, giống như hầu hết các ngôn ngữ hướng đối tượng hiện đại khác. Việc này thường nhanh hơn và an toàn hơn do được kiểm tra tại thời điểm biên dịch.

Dispatch

  • Objective-C: Mặc định sử dụng dispatch động (dynamic dispatch) thông qua runtime.
  • Swift: Mặc định sử dụng dispatch tĩnh (static dispatch) cho hiệu năng, nhưng hỗ trợ cả dispatch động (ví dụ: với `dynamic` keyword hoặc các class kế thừa từ `NSObject`).

Quản lý Bộ nhớ (Memory Management)

  • Objective-C (trước đây): Manual Reference Counting (MRC) – lập trình viên phải tự quản lý bộ nhớ bằng cách gọi `retain`, `release`, `autorelease`. Dễ gây ra lỗi memory leak hoặc crash.
  • Objective-C (hiện tại) & Swift: Automatic Reference Counting (ARC) – trình biên dịch tự động thêm các lệnh quản lý bộ nhớ tại thời điểm biên dịch. Điều này giúp giảm đáng kể các lỗi liên quan đến bộ nhớ. (Chúng ta đã tìm hiểu sâu về ARC trong Swift tại bài “Quản Lý Bộ Nhớ trong Swift: ARC, Tham Chiếu và Các Thực Hành Tốt Nhất“).

Tính an toàn (Safety)

  • Objective-C: Kém an toàn hơn. Ví dụ, gửi thông điệp đến một con trỏ `nil` không gây crash mà chỉ đơn giản là không làm gì cả (lập trình viên phải tự kiểm tra `nil`). Không có khái niệm Optional chặt chẽ như Swift.
  • Swift: An toàn hơn nhiều nhờ hệ thống Optional, kiểm tra kiểu mạnh mẽ tại thời điểm biên dịch, và loại bỏ các lỗi phổ biến như null pointer dereference.

Khả năng tương tác (Interoperability)

  • Cả hai ngôn ngữ đều có khả năng tương tác tốt với nhau trong cùng một dự án Xcode thông qua Bridging Header và Module.

Dưới đây là bảng tóm tắt các khác biệt chính:

Tính năng Objective-C Swift
Cú pháp Kiểu C và Smalltalk ([], @, ;) Hiện đại, sạch sẽ (., không cần ;)
Kiểu dữ liệu Động (id), con trỏ (*), thiếu an toàn kiểu Tĩnh, mạnh mẽ, Optional (?, !), an toàn kiểu
Gọi phương thức Messaging (dispatch động) Method calls (dispatch tĩnh/động)
Quản lý bộ nhớ MRC (thủ công, cũ), ARC (tự động) ARC (tự động), giá trị kiểu (struct, enum) quản lý stack
An toàn Kém an toàn hơn (nil messaging, không Optional) An toàn hơn (Optional, kiểm tra kiểu, loại bỏ con trỏ trần)
Tốc độ Dispatch động có thể chậm hơn Thường nhanh hơn nhờ dispatch tĩnh
File .h (Header), .m (Implementation), .mm (mix C++) .swift

Các Khái niệm Cốt lõi của Objective-C

Để đọc hiểu mã Objective-C, bạn cần nắm vững một số khái niệm cơ bản:

1. Messaging (Gửi thông điệp)

Đây là đặc điểm độc đáo nhất của Objective-C. Thay vì gọi một phương thức trực tiếp, bạn gửi một “thông điệp” đến một đối tượng. Cú pháp là [receiver messageName:argument1 argument2:argument2Value ...].

// Gửi thông điệp "addObject" đến đối tượng "myArray" với đối số "anObject"
[myArray addObject:anObject];

// Gửi thông điệp "stringWithFormat:" đến lớp NSString (thông điệp lớp)
NSString *formattedString = [NSString stringWithFormat:@"Value: %d", someInt];

Khi thông điệp được gửi, runtime của Objective-C sẽ tìm kiếm phương thức tương ứng với `messageName` trên đối tượng `receiver`. Điều này cho phép tính linh hoạt cao, ví dụ: bạn có thể thay đổi hành vi của một lớp tại runtime.

2. Interface và Implementation (.h và .m files)

Mỗi lớp Objective-C thường được chia thành hai file:

  • .h (Header File): Chứa phần Interface (`@interface … @end`). Đây là nơi bạn khai báo lớp, các thuộc tính (`@property`) và các phương thức (`-` cho phương thức instance, `+` cho phương thức lớp) mà lớp đó công khai cho bên ngoài sử dụng.
  • .m (Implementation File): Chứa phần Implementation (`@implementation … @end`). Đây là nơi bạn định nghĩa (viết mã thực thi) cho các phương thức đã khai báo trong file .h.
// MyClass.h
#import <Foundation/Foundation.h>

@interface MyClass : NSObject

// Thuộc tính
@property (nonatomic, strong) NSString *name;

// Phương thức instance
- (void)sayHello;

// Phương thức lớp
+ (instancetype)sharedInstance;

@end
// MyClass.m
#import "MyClass.h"

@implementation MyClass

// Triển khai phương thức sayHello
- (void)sayHello {
    NSLog(@"Hello from %@", self.name);
}

// Triển khai phương thức lớp sharedInstance
+ (instancetype)sharedInstance {
    static MyClass *shared = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shared = [[self alloc] init];
        shared.name = @"Singleton"; // Thiết lập tên mặc định
    });
    return shared;
}

// Init method (Optional, often needed for setup)
- (instancetype)init {
    self = [super init]; // Always call super's init
    if (self) {
        // Custom initialization here
    }
    return self;
}

@end

3. Protocols

Tương tự như protocols trong Swift (hoặc interfaces trong Java), Objective-C protocols (`@protocol … @end`) định nghĩa một tập hợp các phương thức mà một lớp nào đó có thể lựa chọn để triển khai. Điều này hỗ trợ đa hình và thiết kế ủy quyền (delegation), một pattern rất phổ biến trong UIKit mà bạn đã gặp trong các bài về ViewController hay Tương tác người dùng.

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

@protocol MyDelegateProtocol <NSObject> // <NSObject> makes it an Objective-C protocol

@required // Mặc định là required
- (void)didFinishTaskWithResult:(NSString *)result;

@optional // Phương thức tùy chọn
- (BOOL)shouldBeginTask;

@end

4. Categories

Categories (`@interface ClassName (CategoryName) … @end`) cho phép bạn thêm các phương thức mới vào một lớp đã tồn tại mà không cần kế thừa nó. Đây là một cách mạnh mẽ để mở rộng chức năng của các lớp có sẵn, bao gồm cả các lớp trong framework của Apple.

// NSString+Utils.h
#import <Foundation/Foundation.h>

@interface NSString (Utils)

- (NSString *)stringByReversing;

@end
// NSString+Utils.m
#import "NSString+Utils.h"

@implementation NSString (Utils)

- (NSString *)stringByReversing {
    NSMutableString *reversedString = [NSMutableString string];
    NSInteger charIndex = [self length];
    while (charIndex > 0) {
        charIndex--;
        NSRange range = NSMakeRange(charIndex, 1);
        [reversedString appendString:[self substringWithRange:range]];
    }
    return reversedString;
}

@end

5. Selectors

Một selector (`SEL`) là một kiểu dữ liệu opaque (không nhìn thấy cấu trúc bên trong) đại diện cho tên của một phương thức. Bạn có thể lấy selector của một phương thức bằng `@selector(methodName:)`. Selectors thường được sử dụng trong các API động của Objective-C, ví dụ như mục tiêu/hành động (target/action) của UIControl, hoặc trong các thư viện reflection.

// Lấy selector của phương thức "sayHello"
SEL helloSelector = @selector(sayHello);

// Kiểm tra xem đối tượng có phản hồi lại selector này không
if ([myObject respondsToSelector:helloSelector]) {
    // Thực thi phương thức bằng selector
    [myObject performSelector:helloSelector];
}

6. Objective-C Runtime

Một phần quan trọng làm cho Objective-C trở nên năng động là runtime của nó. Đây là một thư viện C nhỏ chịu trách nhiệm thực thi các hành động động như gửi thông điệp, kiểm tra kiểu, thay đổi cấu trúc lớp tại runtime, v.v. Hiểu về runtime giúp bạn hiểu cách messaging hoạt động và tại sao Objective-C lại linh hoạt như vậy.

7. Pointers

Vì Objective-C là một tập hợp con của C, nó sử dụng con trỏ (`*`) để quản lý các đối tượng và dữ liệu. Khi bạn thấy `NSString *myString;`, điều đó có nghĩa là `myString` là một con trỏ tới một đối tượng `NSString` trên heap. Việc quản lý các con trỏ này (đặc biệt là trong thời kỳ MRC) là nguồn gốc của nhiều lỗi. Mặc dù ARC giúp tự động hóa phần lớn công việc này, việc đọc hiểu mã cũ vẫn đòi hỏi bạn phải quen thuộc với cú pháp con trỏ của C.

Làm việc với Mã nguồn Objective-C

Khi bạn bắt gặp mã Objective-C, mục tiêu chính ban đầu không nhất thiết là phải viết lại từ đầu, mà là khả năng đọc, hiểu và điều chỉnh nó.

  • Đọc hiểu: Hãy làm quen với cú pháp. Nhận diện các phần `@interface`, `@implementation`, `@property`, `@selector`, cú pháp `[]` để gửi thông điệp. Chú ý đến các kiểu dữ liệu của Cocoa (bắt đầu bằng NS hoặc UI, ví dụ: `NSString`, `NSArray`, `UIView`, `UIViewController`).
  • Bridging Header: Nếu bạn đang làm việc trong một dự án Swift và cần sử dụng mã Objective-C hiện có, Xcode sẽ giúp bạn tạo một “Bridging Header” (thường có tên dạng `YourProjectName-Bridging-Header.h`). Trong file này, bạn chỉ cần `#import` các file Objective-C header (`.h`) mà bạn muốn sử dụng trong Swift. Trình biên dịch sẽ tự động tạo ra một lớp wrapper Swift cho các lớp Objective-C đó. Ngược lại, để sử dụng Swift trong Objective-C, bạn cần `@import YourModuleName;` và mã Swift cần được đánh dấu là `@objc` hoặc kế thừa từ `NSObject` để có thể nhìn thấy được từ Objective-C.
  • Sửa lỗi (Debugging): Debugging Objective-C cũng tương tự như Swift trong Xcode. Bạn có thể đặt breakpoint, kiểm tra giá trị biến. Công cụ debug của Xcode hoạt động tốt với cả hai ngôn ngữ. Tuy nhiên, việc hiểu runtime của Objective-C có thể hữu ích khi gặp các lỗi liên quan đến messaging hoặc retain cycle (vấn đề quản lý bộ nhớ tuần hoàn mà ARC cũng có thể gặp phải).
  • Viết mã: Ban đầu, bạn có thể chỉ cần thực hiện những thay đổi nhỏ, sửa lỗi hoặc thêm các phương thức đơn giản vào mã Objective-C hiện có. Việc này sẽ giúp bạn làm quen dần với cú pháp và các API. Cố gắng viết thêm các unit test để đảm bảo các thay đổi của bạn không phá vỡ chức năng hiện tại.

Khi nào Bạn sẽ Gặp Objective-C?

Ngoài các dự án kế thừa đã đề cập, bạn còn có thể gặp Objective-C khi:

Tài nguyên Học Objective-C

Để học Objective-C, bạn có thể tham khảo:

  • Tài liệu của Apple: Mặc dù tập trung vào Swift, Apple vẫn duy trì tài liệu cho Objective-C. Hướng dẫn “Programming with Objective-C” là một điểm khởi đầu tốt.
  • Các khóa học và tutorial cũ: Nhiều tài nguyên được tạo ra trước khi Swift ra đời vẫn còn giá trị. Tìm kiếm các khóa học giới thiệu Objective-C cơ bản.
  • Khám phá mã nguồn: Cách tốt nhất để học là đọc mã nguồn thực tế. Tìm các dự án mã nguồn mở Objective-C trên GitHub (có rất nhiều).
  • Tạo một dự án nhỏ: Thử tạo một dự án iOS mới bằng Objective-C (bạn có thể chọn Objective-C khi tạo project trong Xcode) và thử xây dựng một giao diện đơn giản với UIKit để làm quen với cú pháp.

Đừng quên rằng khả năng tìm kiếm và đọc tài liệu là kỹ năng cốt lõi của mọi lập trình viên. Khi gặp một đoạn mã Objective-C khó hiểu, hãy sử dụng Google hoặc Stack Overflow – bạn sẽ tìm thấy rất nhiều thông tin hỗ trợ.

Việc làm quen với các công cụ như Git và GitHub cũng rất quan trọng khi làm việc với các dự án hiện có, bất kể ngôn ngữ là gì.

Kết luận

Objective-C có thể không phải là ngôn ngữ bạn sẽ sử dụng để bắt đầu một dự án iOS mới vào năm 2025, nhưng việc hiểu nó là một phần quan trọng trong hành trình trở thành một lập trình viên iOS toàn diện.

Nó giúp bạn: trang bị cho việc làm việc với các dự án kế thừa, tích hợp các thư viện cũ, hiểu sâu hơn về nền tảng Apple, và đơn giản là mở rộng tầm nhìn về lập trình. Hãy coi Objective-C như một công cụ trong hộp đồ nghề của bạn. Bạn có thể không dùng nó hàng ngày, nhưng khi cần, bạn sẽ rất vui vì mình có nó.

Việc đưa Objective-C vào “Lộ trình học Lập trình viên iOS 2025” không phải là bắt bạn phải viết lại toàn bộ ứng dụng bằng ngôn ngữ này, mà là chuẩn bị cho bạn những tình huống thực tế mà bạn có thể gặp phải trong sự nghiệp. Hãy tiếp tục hành trình khám phá iOS cùng chúng tôi trong các bài viết tiếp theo của series!

Chỉ mục