Cơ chế chuyển đổi từ OC sang C
Các ngôn ngữ lập trình cao cấp cần được biên dịch thành mã máy mới có thể thực thi. Tuy nhiên, Objective-C không thể biên dịch trực tiếp thành mã máy mà phải qua bước chuyển đổi sang C thuần. Quá trình này được thực hiện bởi Runtime. Điều này dẫn đến việc chuyển đổi từ lập trình hướng đối tượng sang hướng thủ tục, yêu cầu biến đổi các lớp thành cấu trúc dữ liệu.
Quá trình truyền tin (Message Passing)
Khi gọi phương thức `[obj foo]`, trình biên dịch chuyển thành `objc_msgSend(obj, foo)`. Runtime thực thi theo quy trình:
- Tìm lớp của đối tượng qua con trỏ
isa
- Tìm phương thức
foo trong danh sách phương thức của lớp
- Nếu không tìm thấy, tiếp tục tìm trong lớp cha
- Tìm thấy phương thức, thực thi phương thức thông qua
IMP
Để tối ưu hiệu năng, Runtime sử dụng bộ đệm
objc_cache để lưu trữ các phương thức thường xuyên gọi. Khi nhận tin nhắn, nó sẽ kiểm tra trước trong bộ đệm thay vì duyệt danh sách phương thức.
Định nghĩa hàm
objc_msgSend:
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable instance, SEL _Nonnull selector, ...)
Cấu trúc dữ liệu cốt lõi
Các cấu trúc quan trọng trong Runtime:
// Đối tượng
struct objc_object {
Class _Nonnull isa;
};
// Lớp
struct objc_class {
Class _Nonnull isa;
Class _Nullable superclass;
const char * _Nonnull name;
struct objc_method_list * _Nullable * _Nullable methodLists;
struct objc_cache * _Nonnull cache;
};
// Bộ đệm phương thức
struct objc_cache {
unsigned int mask;
unsigned int occupied;
Method _Nullable buckets[1];
};
// Phương thức
struct objc_method {
SEL _Nonnull method_name;
char * _Nullable method_types;
IMP _Nonnull method_imp;
};
Các khái niệm chính
- Class object: Cấu trúc
objc_class chứa thông tin về lớp (tên, kích thước, phương thức...)
- Instance object: Đối tượng có con trỏ
isa trỏ đến class object
- Meta class: Class object của class, chứa thông tin về phương thức lớp
- SEL: Chọn phương thức (trường hợp giống ID)
- IMP: Địa chỉ thực thi phương thức
- Category: Thêm phương thức vào lớp mà không sửa đổi lớp gốc
Phương thức SEL và IMP
SEL là định danh phương thức, được tạo bằng
@selector() hoặc
sel_registerName. Nó chỉ chứa tên phương thức, không chứa thông tin tham số. Điều này dẫn đến việc không thể ghi đè phương thức cùng tên nhưng khác tham số.
IMP là con trỏ trỏ đến địa chỉ thực thi phương thức. Cấu trúc
objc_method chứa cả
SEL và
IMP, giúp tìm nhanh phương thức.
Chuyển tiếp tin nhắn (Message Forwarding)
Nếu không tìm thấy phương thức, Runtime sẽ thực hiện 3 bước chuyển tiếp:
- Phân giải động: Gọi
+resolveInstanceMethod: để thêm phương thức mới
- Chuyển tiếp mục tiêu: Gọi
-forwardingTargetForSelector: để chuyển tin nhắn cho đối tượng khác
- Chuyển tiếp đầy đủ: Gọi
-forwardInvocation: để xử lý tin nhắn hoàn chỉnh
Ví dụ phân giải động:
+ (BOOL)resolveInstanceMethod:(SEL)selector {
if (selector == @selector(handleMethod:)) {
class_addMethod([self class], selector, (IMP)handleMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:selector];
}
void handleMethod(id instance, SEL _cmd) {
NSLog(@"Phương thức xử lý động");
}
Ứng dụng thực tế của Runtime
1. Gắn thuộc tính vào Category
Sử dụng
objc_setAssociatedObject để thêm thuộc tính vào Category:
@interface UIView (ColorExtension)
@property (nonatomic, strong) UIColor *colorValue;
@end
@implementation UIView (ColorExtension)
@dynamic colorValue;
static char colorKey;
- (void)setColorValue:(UIColor *)color {
objc_setAssociatedObject(self, &colorKey, color, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIColor *)colorValue {
return objc_getAssociatedObject(self, &colorKey);
}
2. Thay đổi phương thức (Method Swizzling)
Thay đổi phương thức bằng cách trao đổi thực thi:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL original = @selector(viewDidLoad);
SEL swizzled = @selector(customViewDidLoad);
Method originalMethod = class_getInstanceMethod(self, original);
Method swizzledMethod = class_getInstanceMethod(self, swizzled);
if (class_addMethod(self, original, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod(self, swizzled, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)customViewDidLoad {
NSLog(@"Phương thức đã thay đổi");
[self customViewDidLoad];
}
3. Tự động hóa NSCoding
Tự động mã hóa và giải mã thuộc tính:
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
unsigned int count;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[decoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
unsigned int count;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[encoder encodeObject:[self valueForKey:key] forKey:key];
}
}
4. Chuyển đổi từ Dictionary sang Model
Tự động gán giá trị từ Dictionary:
- (instancetype)initWithDictionary:(NSDictionary *)dict {
if (self = [self init]) {
unsigned int count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
objc_property_t property = properties[i];
NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
if ([dict objectForKey:propertyName]) {
[self setValue:[dict objectForKey:propertyName] forKey:propertyName];
}
}
free(properties);
}
return self;
}