MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C基础语法与核心概念解析

2023-11-133.4k 阅读

一、Objective-C 概述

Objective-C 是一种通用、高级、面向对象的编程语言,它是在 C 语言的基础上添加了面向对象的特性而形成的。Objective-C 是 macOS 和 iOS 开发的主要语言,许多知名的应用程序,如 Safari、iTunes 等都是使用 Objective-C 编写的。它采用动态运行时系统,这意味着很多面向对象的特性,如方法调用,是在运行时而不是编译时确定的。

二、基础语法

2.1 数据类型

Objective-C 基于 C 语言,所以它支持 C 语言的基本数据类型,如整型(int)、浮点型(floatdouble)、字符型(char)和布尔型(BOOL,实际上是 signed charYES 对应 1,NO 对应 0)。

// 整型变量声明
int age = 25;
// 浮点型变量声明
float height = 175.5f;
double weight = 70.0;
// 字符型变量声明
char grade = 'A';
// 布尔型变量声明
BOOL isStudent = YES;

此外,Objective-C 还引入了对象类型,对象类型使用指针表示,最基本的对象类型是 id,它可以指向任何 Objective-C 对象。

id someObject;

2.2 变量与常量

变量在使用前需要声明,声明包括变量类型和变量名。变量可以在声明时初始化,也可以后续赋值。

int number;
number = 10;

// 声明并初始化
float price = 9.99f;

常量在声明时必须初始化,并且其值在程序运行过程中不能改变。在 Objective-C 中,可以使用 #define 预处理指令定义常量,也可以使用 const 关键字。

// 使用 #define 定义常量
#define PI 3.141592653589793

// 使用 const 关键字定义常量
const float gravity = 9.81f;

2.3 运算符

Objective-C 支持 C 语言的所有运算符,包括算术运算符(+-*/)、赋值运算符(=+=-= 等)、比较运算符(==!=>< 等)、逻辑运算符(&&||!)、位运算符(&|^~<<>>)等。

int a = 5;
int b = 3;
int sum = a + b;
BOOL isGreater = a > b;

2.4 控制语句

Objective-C 提供了常见的控制语句,如 if - elseswitch - caseforwhiledo - while 等。

if - else 语句

int score = 85;
if (score >= 90) {
    NSLog(@"A 等级");
} else if (score >= 80) {
    NSLog(@"B 等级");
} else {
    NSLog(@"其他等级");
}

switch - case 语句

int day = 3;
switch (day) {
    case 1:
        NSLog(@"星期一");
        break;
    case 2:
        NSLog(@"星期二");
        break;
    case 3:
        NSLog(@"星期三");
        break;
    default:
        NSLog(@"其他日期");
        break;
}

for 循环

for (int i = 0; i < 5; i++) {
    NSLog(@"当前值: %d", i);
}

while 循环

int count = 0;
while (count < 3) {
    NSLog(@"计数: %d", count);
    count++;
}

do - while 循环

int num = 0;
do {
    NSLog(@"数值: %d", num);
    num++;
} while (num < 3);

三、面向对象编程

3.1 类与对象

在 Objective-C 中,类是对象的蓝图,定义了对象的属性(成员变量)和行为(方法)。类的定义包括接口(.h 文件)和实现(.m 文件)两部分。

接口部分(.h 文件)

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

- (void)introduce;

@end

在上述代码中,@interface 关键字开始定义一个类,类名是 Person,它继承自 NSObject 类(Objective-C 中所有类的根类)。@property 声明了两个属性,nameagenonatomic 表示非原子性访问,copy 用于字符串属性的拷贝,assign 用于基本数据类型属性的赋值。- (void)introduce; 声明了一个实例方法。

实现部分(.m 文件)

#import "Person.h"

@implementation Person

- (void)introduce {
    NSLog(@"我叫 %@,今年 %ld 岁。", self.name, (long)self.age);
}

@end

@implementation 关键字开始类的实现,在这个部分实现了 introduce 方法。

创建对象并使用:

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.name = @"张三";
        person.age = 30;
        [person introduce];
    }
    return 0;
}

main 函数中,首先使用 alloc 分配内存,然后使用 init 初始化对象,接着设置对象的属性并调用 introduce 方法。

3.2 继承

继承是面向对象编程的重要特性之一,它允许一个类从另一个类继承属性和方法。子类可以重写父类的方法,也可以添加新的属性和方法。

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

@interface Student : Person

@property (nonatomic, copy) NSString *school;

- (void)study;

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

@implementation Student

- (void)study {
    NSLog(@"%@ 在 %@ 学习。", self.name, self.school);
}

@end
// main.m
#import <Foundation/Foundation.h>
#import "Student.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *student = [[Student alloc] init];
        student.name = @"李四";
        student.age = 20;
        student.school = @"北京大学";
        [student introduce];
        [student study];
    }
    return 0;
}

在上述代码中,Student 类继承自 Person 类,它不仅拥有 Person 类的 nameage 属性以及 introduce 方法,还添加了 school 属性和 study 方法。

3.3 多态

多态是指同一个方法调用,根据对象类型的不同,会有不同的行为。在 Objective-C 中,多态主要通过继承和方法重写来实现。

假设我们有一个父类 Animal 和两个子类 DogCatAnimal 类有一个 makeSound 方法,DogCat 类重写这个方法。

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

@interface Animal : NSObject

- (void)makeSound;

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

@implementation Animal

- (void)makeSound {
    NSLog(@"动物发出声音");
}

@end
// Dog.h
#import <Foundation/Foundation.h>
#import "Animal.h"

@interface Dog : Animal

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

@implementation Dog

- (void)makeSound {
    NSLog(@"汪汪汪");
}

@end
// Cat.h
#import <Foundation/Foundation.h>
#import "Animal.h"

@interface Cat : Animal

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

@implementation Cat

- (void)makeSound {
    NSLog(@"喵喵喵");
}

@end
// main.m
#import <Foundation/Foundation.h>
#import "Animal.h"
#import "Dog.h"
#import "Cat.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Animal *animal1 = [[Dog alloc] init];
        Animal *animal2 = [[Cat alloc] init];

        [animal1 makeSound];
        [animal2 makeSound];
    }
    return 0;
}

main 函数中,虽然 animal1animal2 都被声明为 Animal 类型,但实际上它们分别指向 DogCat 对象,调用 makeSound 方法时会执行各自类中重写的方法,体现了多态性。

四、内存管理

4.1 引用计数原理

Objective-C 使用引用计数(Reference Counting)来管理内存。每个对象都有一个引用计数,当对象被创建时,引用计数为 1。当有新的变量指向该对象时,引用计数加 1;当指向对象的变量不再使用该对象(如变量超出作用域或被赋值为 nil)时,引用计数减 1。当引用计数为 0 时,对象所占用的内存被释放。

4.2 手动内存管理

在手动内存管理模式下,开发人员需要手动调用 retainreleaseautorelease 方法来管理对象的引用计数。

// 创建对象,引用计数为 1
NSString *string1 = [[NSString alloc] initWithString:@"Hello"];
// 手动增加引用计数
[string1 retain];
// 手动减少引用计数
[string1 release];
// 使用 autorelease,将对象放入自动释放池,在自动释放池销毁时减少引用计数
NSString *string2 = [[[NSString alloc] initWithString:@"World"] autorelease];

4.3 自动引用计数(ARC)

ARC 是 Xcode 4.2 引入的一项功能,它自动管理对象的内存,开发人员无需手动调用 retainreleaseautorelease 方法。ARC 会在编译时自动插入合适的内存管理代码。

// ARC 模式下,无需手动管理内存
NSString *message = @"Hello, ARC!";

五、消息传递

5.1 动态方法解析

在 Objective-C 中,方法调用实际上是向对象发送消息。当一个对象接收到一个消息时,它会在运行时查找对应的方法实现。如果对象在其方法列表中找不到该方法,会进入动态方法解析阶段。

@interface MyClass : NSObject
- (void)unknownMethod;
@end

@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(unknownMethod)) {
        class_addMethod(self, sel, (IMP)customMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void customMethod(id self, SEL _cmd) {
    NSLog(@"动态添加的方法被调用");
}
@end

在上述代码中,MyClass 类在接收到 unknownMethod 消息且找不到该方法实现时,会尝试通过 resolveInstanceMethod: 方法动态添加方法实现。

5.2 备用接收者

如果动态方法解析没有找到方法实现,对象会尝试寻找备用接收者。即对象会调用 forwardingTargetForSelector: 方法,看是否能返回一个可以处理该消息的对象。

@interface MyClass : NSObject
- (void)unknownMethod;
@end

@interface OtherClass : NSObject
- (void)handleUnknownMethod;
@end

@implementation OtherClass
- (void)handleUnknownMethod {
    NSLog(@"OtherClass 处理未知方法");
}
@end

@implementation MyClass
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(unknownMethod)) {
        return [[OtherClass alloc] init];
    }
    return nil;
}
@end

在这个例子中,MyClass 接收到 unknownMethod 消息且未找到直接实现时,会返回 OtherClass 的实例作为备用接收者来处理该消息。

5.3 完整的消息转发

如果备用接收者也没有找到,就会进入完整的消息转发阶段。对象会创建一个 NSInvocation 对象,封装消息的参数和选择器,然后调用 forwardInvocation: 方法进行处理。

@interface MyClass : NSObject
- (void)unknownMethod;
@end

@implementation MyClass
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;
    if ([self respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:self];
    } else {
        NSLog(@"无法处理的消息: %@", NSStringFromSelector(sel));
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(unknownMethod)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
@end

在上述代码中,MyClass 通过 methodSignatureForSelector: 方法获取方法签名,然后在 forwardInvocation: 方法中根据签名来处理消息,如果无法处理则打印提示信息。

六、类别与协议

6.1 类别(Category)

类别可以在不子类化的情况下,为已有的类添加新的方法。类别通常用于将类的实现分散到多个文件中,或者为系统类添加自定义方法。

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

@interface NSString (Additions)
- (NSString *)reversedString;
@end
// NSString+Additions.m
#import "NSString+Additions.h"

@implementation NSString (Additions)
- (NSString *)reversedString {
    NSMutableString *reversed = [NSMutableString stringWithCapacity:self.length];
    for (NSUInteger i = self.length - 1; i < self.length; i--) {
        [reversed appendFormat:@"%C", [self characterAtIndex:i]];
    }
    return [NSString stringWithString:reversed];
}
@end

使用类别:

#import <Foundation/Foundation.h>
#import "NSString+Additions.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *original = @"Hello";
        NSString *reversed = [original reversedString];
        NSLog(@"反转后的字符串: %@", reversed);
    }
    return 0;
}

在上述代码中,为 NSString 类添加了一个 reversedString 方法,用于返回字符串的反转版本。

6.2 协议(Protocol)

协议定义了一组方法声明,但不提供实现,任何类都可以遵守协议并实现协议中的方法。协议常用于实现代理模式等设计模式。

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

@protocol DelegateProtocol <NSObject>
@required
- (void)dataReceived:(NSString *)data;
@optional
- (void)connectionClosed;
@end
// NetworkManager.h
#import <Foundation/Foundation.h>
@protocol DelegateProtocol;

@interface NetworkManager : NSObject
@property (nonatomic, weak) id<DelegateProtocol> delegate;
- (void)fetchData;
@end
// NetworkManager.m
#import "NetworkManager.h"

@implementation NetworkManager
- (void)fetchData {
    // 模拟数据获取
    NSString *data = @"Some data";
    if ([self.delegate respondsToSelector:@selector(dataReceived:)]) {
        [self.delegate dataReceived:data];
    }
}
@end
// ViewController.h
#import <UIKit/UIKit.h>
#import "DelegateProtocol.h"

@interface ViewController : UIViewController <DelegateProtocol>

@end
// ViewController.m
#import "ViewController.h"
#import "NetworkManager.h"

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    NetworkManager *manager = [[NetworkManager alloc] init];
    manager.delegate = self;
    [manager fetchData];
}

- (void)dataReceived:(NSString *)data {
    NSLog(@"接收到的数据: %@", data);
}

- (void)connectionClosed {
    NSLog(@"连接已关闭");
}
@end

在上述代码中,定义了一个 DelegateProtocol 协议,NetworkManager 类持有一个遵守该协议的代理对象,并在数据获取后调用代理的 dataReceived: 方法。ViewController 类遵守该协议并实现了相关方法,作为 NetworkManager 的代理来处理数据。