找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

311

积分

0

好友

37

主题
发表于 2025-12-26 11:16:27 | 查看: 29| 回复: 0

二、Objective-C 核心语法与运行时

APP构造函数与析构函数

__attribute__((constructor)) 用于声明构造函数,该函数会在 APP 进入 main 函数之前被调用。它常用于实现模块的自注册机制。

#include <mach-o/dyld.h>

static void dyld_register_func(const struct mach_header *mh, intptr_t vmaddr_slide) {
    // 实现模块自注册逻辑
}

__attribute__((constructor))
static void load_file(void) {
    _dyld_register_func_for_add_image(dyld_register_func);
}

__attribute__((destructor)) 用于声明析构函数,该函数会在 APP 进程被终止前调用。

__attribute__((destructor))
static void exit_app(void) {
    // 监听APP被杀掉的频率
}

objc_class 结构体

Objective-C 中类的底层表示是一个 objc_class 结构体。

struct objc_class {
    Class isa;                             // 指向所属类(元类)的指针
    Class super_class;                     // 指向父类的指针
    const char *name;                      // 类名
    struct objc_ivar_list *ivars;          // 成员变量列表
    struct objc_method_list **methodLists; // 方法列表
    struct objc_protocol_list *protocols;  // 协议列表
}

三、iOS 调试与 Crash 分析

查看与导出 Crash 日志

  1. 通过 Xcode 的 Window -> Organizer -> Crashes 查看用户上报的 Crash 日志。

OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 1

  1. 通过 Xcode -> Window -> Devices and Simulators -> View Device Logs 导出设备的本地 Crash 日志。

OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 2

深入分析 Crash

1. 解读堆栈信息
Crash 日志中的堆栈信息是定位问题的关键,其中包含内存地址信息。

OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 3

  • 内存地址:即 Mach-O 文件加载到内存后的原始地址,通常为16进制。
  • 偏移地址:指该地址相对于其所属镜像(如主二进制或动态库)加载地址的偏移量。

2. 使用 atos 命令符号化
atos 命令可以将内存地址还原为可读的函数名和行号。

# 符号化主工程中的地址
atos -arch arm64 -o ~/symbol/Rider.app.dSYM/Contents/Resources/DWARF/Rider -l 偏移地址 内存地址

# 符号化系统动态库中的地址
atos -arch arm64 -o ~/extracted_libs/usr/lib/system/libsystem_kernel.dylib -l 偏移地址 内存地址

3. 使用 symbolicatecrash 脚本符号化
Xcode 自带的 symbolicatecrash 脚本可以一键符号化整个 .crash 文件。

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash ~/crash/log.crash ~/symbol/Rider.app.dSYM > ~/crash/symbolLog.crash

4. 定位 Mach-O 文件
进行低级分析时,需要找到对应的 Mach-O 文件。

  • 应用 Mach-O: 解压 IPA 包后在 ~/Rider/Payload/Rider/Rider 路径下。
  • 系统 Framework: 位于 Xcode 的 DeviceSupport 目录中,例如 ~/Library/Developer/Xcode/iOS DeviceSupport/15.3.1 (19D52)/Symbols/System/Library/Frameworks/UIKit.framework/UIKit

5. 使用 Hopper 进行反汇编分析
对于无法符号化或需要深入汇编级别分析的 Crash,可以使用 Hopper Disassembler。

  • 导入 Mach-O 文件。
  • 通过 Goto 功能跳转到偏移地址。
  • 结合调用栈顺序,分析汇编指令以推断 Crash 原因。

OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 4

OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 5

OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 6

try-catch 的应用与局限

Objective-C 的 @try-@catch 只能捕获 NSException 异常,通常用于防护可能抛出此类异常的代码块,例如集成不稳定的第三方库或调用可能异常的系统 API。

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    @try {
        [super pushViewController:viewController animated:animated];
    } @catch (NSException *exception) {
        NSLog(@"页面堆栈异常");
    }
}

注意:在 ARC 环境下,@try-@catch 块内的代码可能导致内存管理问题(如内存泄漏),因此应谨慎使用,仅作为最后的防护手段。

四、内存管理与多线程

参数传递方式

Objective-C 中方法参数的传递遵循 C 语言的规则,主要分为值传递和指针传递。

  • 值传递:传递的是参数值的副本,方法内部修改不会影响外部原始变量。
  • 指针传递:传递的是变量的内存地址,方法内部通过指针修改会直接影响外部原始变量。
// 值传递示例
int num = 10;
[self valuePass:num]; // num 的值被拷贝一份传入
- (void)valuePass:(int)num {
    num = 20; // 只修改了内部的副本
    NSLog(@"%d", num); // 输出 20
}
NSLog(@"%d", num); // 输出 10,外部变量未变

// 指针传递示例
int num = 10;
[self pointerPass:&num]; // 传入 num 的地址
- (void)pointerPass:(int *)num {
    *num = 20; // 通过地址修改外部变量的值
}
NSLog(@"%d", num); // 输出 20,外部变量已被修改

理解虚拟内存与物理内存

  • 物理内存:设备实际的内存芯片容量。
  • 虚拟内存:操作系统为每个进程提供的连续的、独立的逻辑地址空间。它通过内存管理单元(MMU)映射到物理内存。
  • 映射关系:以“页”为单位进行映射。一页物理内存可能被映射到多页虚拟内存(共享内存);一页虚拟内存也可能暂时没有对应的物理内存(如未使用的代码段)。

内存管理单元(MMU)

OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 7

MMU 负责将进程访问的虚拟内存地址转换为物理内存地址。进程的页表存储在内核空间。当进程访问一个虚拟内存页,而其对应的物理内存页不存在(未加载或已被换出)时,会触发一个缺页中断(Page Fault)。操作系统内核会处理这个中断,分配物理内存、更新页表,然后恢复进程运行。

文件内存映射(mmap)

mmap 是一种将磁盘文件直接映射到进程虚拟地址空间的技术。其最大优势是高效和持久化。

  • 工作原理:建立映射关系,而非立即将文件内容全部加载到物理内存。访问文件数据如同访问内存,由操作系统按需通过缺页中断加载。
  • 应用场景
    • MMKV:基于 mmap 的高性能 key-value 存储组件。
    • 日志系统:避免应用崩溃或被杀时日志丢失。
    • 加载大型文件(如视频),实现“零拷贝”访问。

动态库与静态库

动态库和静态库是代码共享和模块化的两种主要形式。

特性 动态库 (Dynamic Library, .dylib/.framework) 静态库 (Static Library, .a/.framework)
命名空间 有独立命名空间,不同库的同名符号不冲突。使用 #import <Framework/Header.h> 无独立命名空间,链接时直接展开,同名符号会冲突。使用 #import "Header.h"
加载时机 应用启动时(或运行时)由 dyld 动态加载到内存。 编译链接时,代码被完整地拷贝到最终的可执行文件中。
依赖关系 可以依赖其他动态库,不能依赖静态库。 可以依赖动态库,也可以依赖其他静态库。
优势 多个应用可共享,节省内存;可独立更新。 链接后单一文件,启动快;模块化编译,加速开发。

编译构建设置
在 Xcode 的工程设置中,可以指定 Target 的 Mach-O Type 来生成动态库或静态库。

OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 8

iOS 中的多线程与锁

在多线程编程中,锁是保证数据安全访问的关键同步机制。了解并发与多线程的原理有助于选择合适的锁。

OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 9
上图展示了常见锁的性能开销对比(越小越好)。

锁的分类与应用场景:

  1. 互斥锁 (Mutex)

    • 原理:如果锁已被持有,尝试加锁的线程会进入休眠状态,等待唤醒。
    • 实现:pthread_mutex_t, NSLock, @synchronized
    • 场景:适用于临界区代码执行时间较长的场景,避免忙等消耗 CPU。
  2. 自旋锁 (Spin Lock)

    • 原理:如果锁已被持有,尝试加锁的线程会循环(“自旋”)检查锁状态,不会休眠。
    • 实现:os_unfair_lock (推荐),atomic (属性关键字)。
    • 注意:已废弃的 OSSpinLock 存在优先级反转问题,不应再使用。
    • 场景:适用于临界区代码执行非常短暂的场景,避免线程切换的开销。
  3. 信号量 (Semaphore)

    • 原理:一个计数器,用于控制访问特定资源的线程数量。
    • 实现:dispatch_semaphore_t
    • 场景:控制并发任务数量,或用于线程间同步。
  4. 递归锁 (Recursive Lock)

    • 原理:允许同一个线程多次获取同一把锁而不会死锁。
    • 实现:pthread_mutex_t(recursive), NSRecursiveLock
    • 场景:递归函数或可重入方法中需要加锁时。
  5. 读写锁 (Read-Write Lock)

    • 原理:允许多个读操作并发,但写操作是独占的。
    • 实现:pthread_rwlock_t, 使用 dispatch_barrier_async 在自定义并发队列上模拟。
    • 场景:读多写少的共享数据结构。
  6. 条件锁 (Condition Lock)

    • 原理:线程等待某个条件成立,当条件满足时被唤醒。
    • 实现:NSCondition, NSConditionLock
    • 场景:生产者-消费者模型,线程间依赖特定状态。

死锁的常见场景:
死锁是指两个或以上的线程因相互等待对方持有的资源而无法继续执行的状态。

// 场景1:同一把锁连续lock
NSLock *_lock = [[NSLock alloc] init];
[_lock lock];
[_lock lock]; // 第二次lock会永远等待 -> 死锁

// 场景2:在主队列同步派发任务
dispatch_sync(dispatch_get_main_queue(), ^{
    // 阻塞主线程,而任务又在等待主线程执行 -> 死锁
});

// 场景3:多线程竞争,长时间持有锁
for (int i=0; i<100; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [_lock lock];
        sleep(10); // 长时间占用
        [_lock unlock];
    });
}
// 大量线程堆积等待同一把锁,可能引发类似死锁的“锁竞争”瓶颈。

OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 10OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 11OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 12OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 13OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 14

五、图像处理与 UI 布局

UIImage 的高效裁剪与缩放

处理图片时,应优先使用 Core Graphics 或 Image I/O 框架,它们在性能和内存控制上优于 UIKit 的 UIGraphicsBeginImageContext

1. UIKit 方式(不推荐用于大量或大图处理)

- (UIImage *)resizeImage:(UIImage *)originImage size:(CGSize)size {
    UIGraphicsBeginImageContext(size);
    [originImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
    UIImage *resizeImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return resizeImage;
}

2. Core Graphics 方式(推荐)

- (UIImage *)resizeImage:(UIImage *)originImage size:(CGSize)size {
    CGImageRef imageRef = originImage.CGImage;
    size_t width = size.width;
    size_t height = size.height;
    size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
    size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
    CGColorSpaceRef space = CGImageGetColorSpace(imageRef);
    uint32_t bitmapInfo = CGImageGetBitmapInfo(imageRef);

    CGContextRef contextRef = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, space, bitmapInfo);
    CGContextDrawImage(contextRef, CGRectMake(0, 0, width, height), imageRef);
    CGImageRef resizeImageRef = CGBitmapContextCreateImage(contextRef);
    UIImage *resizeImage = [UIImage imageWithCGImage:resizeImageRef];

    CGContextRelease(contextRef);
    CGImageRelease(resizeImageRef);
    return resizeImage;
}

3. Image I/O 方式(最优,尤其适合生成缩略图)
Image I/O 框架在创建缩略图时,可以避免将完整尺寸的 Bitmap 解码到内存,极大降低峰值内存使用。

- (UIImage *)resizeImage:(UIImage *)originImage size:(CGSize)size {
    NSData *data = UIImagePNGRepresentation(originImage);
    CGFloat maxPixelSize = MAX(size.width, size.height);

    CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    NSDictionary *options = @{
        (__bridge id)kCGImageSourceCreateThumbnailWithTransform : @YES,
        (__bridge id)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
        (__bridge id)kCGImageSourceThumbnailMaxPixelSize : @(maxPixelSize)
    };

    CGImageRef resizeImageRef = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
    UIImage *resizeImage = [UIImage imageWithCGImage:resizeImageRef];
    CFRelease(imageSource);
    CGImageRelease(resizeImageRef);
    return resizeImage;
}

Masonry 自动布局应用详解

Masonry 是对 Auto Layout 的链式语法封装,极大简化了布局代码。

核心方法对比:

  • mas_equalTo(): 参数可以是对象(如 view),也可以是数值(如 @10)。参数为数值时,等同于设置固定值或偏移量。
  • equalTo(): 参数通常是视图 (view) 或视图的某个属性 (view.mas_bottom),或者 NSNumber 对象 (@10)。
  • offset(): 用于设置偏移量,类型为 CGFloat
// 等价调用示例
[subView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.right.top.bottom.mas_equalTo(0);
    // 等价于:
    make.edges.mas_equalTo(superView);
    make.left.right.top.bottom.equalTo(superView);
    make.left.right.top.bottom.equalTo(@0);

    make.bottom.mas_equalTo(-10);
    // 等价于:
    make.bottom.equalTo(superView).offset(-10);
    make.bottom.equalTo(@(-10));
    make.bottom.equalTo(superView.mas_bottom).offset(-10);
}];

// 常规布局示例
[subView mas_makeConstraints:^(MASConstraintMaker *make) {
    // 上方有其它视图时,应参照该视图,而非直接设置固定top值
    make.top.equalTo(topView.mas_bottom).offset(12);
    make.width.mas_equalTo(140);
    make.height.mas_equalTo(48);
    make.centerX.mas_equalTo(superView.mas_centerX).offset(10);
}];

// 通过子视图决定父视图高度
[subView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.left.equalTo(superView).offset(12);
    make.right.equalTo(superView).offset(-12);
    make.height.mas_equalTo(351);
    // 关键:约束到父视图底部
    make.bottom.equalTo(superView).offset(-10);
    // 注意:此时 superView 自身不能再有确定高度的约束,否则冲突。
}];

更新与重置约束:

  • mas_updateConstraints: 更新已有约束的值。通常需要配合 setNeedsUpdateConstraints
    [subView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.bottom.equalTo(superView).offset(-15);
    }];
    [subView setNeedsUpdateConstraints];
  • mas_remakeConstraints: 移除所有旧约束,添加新约束。
    [subView mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.top.left.equalTo(superView).offset(20);
        make.right.equalTo(superView).offset(-20);
        make.height.mas_equalTo(335);
        make.bottom.equalTo(superView).offset(-20);
    }];
    [subView setNeedsUpdateConstraints];

布局更新方法:

  • setNeedsUpdateConstraints: 标记视图需要更新约束,系统会在下一布局周期自动调用 updateConstraints
  • updateConstraintsIfNeeded: 立即检查并执行约束更新(如果有标记)。
  • updateConstraints: 子类重写此方法来实现自定义的约束更新逻辑。
  • needsUpdateConstraints: 查询视图当前是否有需要更新的约束标记。

六、响应式编程与高级运行时技巧

ReactiveObjC (RAC) 核心应用

RAC 为 Objective-C 带来了强大的响应式编程能力。

双向绑定 (RACChannelTo)

// 将 textField 的 text 属性与 model 的 title 属性双向绑定
RACChannelTo(_textField, text) = RACChannelTo(_model, title);

属性监听与单向绑定 (RACObserve, rac_textSignal)

// 监听 textField.text 的变化
[RACObserve(_textField, text) subscribeNext:^(id x) {
    NSLog(@"新值: %@", x);
}];

// 另一种监听方式
[_textField.rac_textSignal subscribeNext:^(id x) {
    NSLog(@"输入: %@", x);
}];

// 将输入信号直接绑定到 label.text
RAC(_label, text) = _textField.rac_textSignal;

// 实现 ViewModel 到 View 的单向绑定
RAC(_label, text) = RACObserve(_viewModel.model, title);

通知监听

[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"kNotificationLogin" object:nil] subscribeNext:^(NSNotification* notification) {
    NSLog(@"收到通知: %@", notification.name);
}];

信号组合
RACSubject 是一个可以手动发送信号的类,常用于替代 delegate 或 block。

// 合并多个信号(任一信号有新值即触发)
RACSubject *subjectA = [RACSubject subject];
RACSubject *subjectB = [RACSubject subject];
RACSignal *mergeSignal = [RACSignal merge:@[subjectA, subjectB]];
[mergeSignal subscribeNext:^(id x) {
    NSLog(@"合并信号值: %@", x);
}];
[subjectA sendNext:@"A"];
[subjectB sendNext:@"B"];

// 组合最新值(所有信号都有值后,任一更新即触发)
RACSignal *combineSignal = [RACSignal combineLatest:@[subjectA, subjectB]];
[combineSignal subscribeNext:^(RACTuple *x) {
    RACTupleUnpack(NSString *a, NSString *b) = x;
    NSLog(@"组合信号值: A=%@, B=%@", a, b);
}];
[subjectA sendNext:@"A1"];
[subjectB sendNext:@"B1"]; // 触发,输出 A1, B1
[subjectA sendNext:@"A2"]; // 再次触发,输出 A2, B1

实战:登录按钮状态控制

// 结合手机号和验证码输入框,控制登录按钮的 enable 状态
RACSignal *combineSignal = [RACSignal combineLatest:@[RACObserve(_phoneInput, text), RACObserve(_codeInput, text)]];
RAC(_loginButton, enabled) = [combineSignal map:^id _Nullable(RACTuple *value) {
    RACTupleUnpack(NSString *phone, NSString *code) = value;
    return @(phone.length == 11 && code.length == 6);
}];

RACCommand:封装操作
RACCommand 通常用于封装按钮点击、网络请求等会产生副作用的行为。

@weakify(self);
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    @strongify(self);
    // 创建并返回一个执行实际操作的信号(如网络请求)
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        @strongify(self);
        [self GET:@"api/url" parameters:nil success:^(NSDictionary* response) {
            [subscriber sendNext:response]; // 发送成功数据
            [subscriber sendCompleted];     // 发送完成信号
        } failure:^(NSError *error) {
            [subscriber sendError:error];   // 发送错误信号
        }];
        return nil;
    }];
}];

// 允许命令并发执行(默认不允许)
command.allowsConcurrentExecution = YES;

// 订阅命令执行状态(skip:1 跳过初始的 @NO 状态)
[[command.executing skip:1] subscribeNext:^(NSNumber *executing) {
    if (executing.boolValue) {
        NSLog(@"正在执行...");
    } else {
        NSLog(@"执行结束");
    }
}];

// 订阅命令执行成功的结果(switchToLatest 取最新一次执行的信号)
[command.executionSignals.switchToLatest subscribeNext:^(NSDictionary *response) {
    NSLog(@"请求成功: %@", response);
}];

// 订阅命令执行中产生的错误
[command.errors subscribeNext:^(NSError *error) {
    NSLog(@"请求失败: %@", error);
}];

// 执行命令
[command execute:nil];

使用 NSInvocation 进行动态调用

NSInvocation 提供了在运行时动态调用方法的能力,常用于组件化通信或消息转发。

+ (BOOL)invokeTarget:(id)target
              action:(SEL)selector
           arguments:(NSArray *_Nullable)arguments
         returnValue:(void *_Nullable)result {
    if (target && [target respondsToSelector:selector]) {
        NSMethodSignature *signature = [target methodSignatureForSelector:selector];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget:target];
        [invocation setSelector:selector];

        // 设置参数
        for (NSUInteger i = 0; i < arguments.count; i++) {
            NSUInteger argIndex = i + 2; // 0:target, 1:_cmd
            id argument = arguments[i];
            if ([argument isKindOfClass:NSNumber.class]) {
                // 处理基本数据类型
                NSNumber *num = (NSNumber *)argument;
                const char *type = [signature getArgumentTypeAtIndex:argIndex];
                if (strcmp(type, @encode(BOOL)) == 0) {
                    BOOL rawNum = [num boolValue];
                    [invocation setArgument:&rawNum atIndex:argIndex];
                    continue;
                } else if (strcmp(type, @encode(int)) == 0 ||
                           strcmp(type, @encode(short)) == 0 ||
                           strcmp(type, @encode(long)) == 0) {
                    NSInteger rawNum = [num integerValue];
                    [invocation setArgument:&rawNum atIndex:argIndex];
                    continue;
                } else if (strcmp(type, @encode(float)) == 0) {
                    float rawNum = [num floatValue];
                    [invocation setArgument:&rawNum atIndex:argIndex];
                    continue;
                }
            }
            if ([argument isKindOfClass:[NSNull class]]) {
                argument = nil;
            }
            [invocation setArgument:&argument atIndex:argIndex];
        }

        [invocation invoke];

        // 处理返回值
        if (result) {
            const char *returnType = signature.methodReturnType;
            if (strcmp(returnType, @encode(void)) != 0) { // 返回值不是 void
                if (strcmp(returnType, @encode(id)) == 0 || strcmp(returnType, @encode(Class)) == 0) {
                    // 返回对象类型,需要正确处理内存管理
                    CFTypeRef cfResult = nil;
                    [invocation getReturnValue:&cfResult];
                    if (cfResult) {
                        CFRetain(cfResult);
                        *(void**)result = (__bridge_retained void *)((__bridge_transfer id)cfResult);
                    }
                } else {
                    // 返回基本数据类型
                    [invocation getReturnValue:result];
                }
            }
        }
        return YES;
    }
    return NO;
}

运行时:获取 Class 与 Protocol 的详细信息

Objective-C 的运行时 API 可以动态查询类、协议的方法、属性和协议列表。

// 1. 基础判断
- (void)verifyFunction {
    // 判断类是否符合某个协议
    BOOL conforms = [NSArray conformsToProtocol:@protocol(NSObject)];
    // 判断类是否响应某个方法(类方法)
    BOOL responds = [NSArray respondsToSelector:@selector(array)];
}

// 2. 获取 Class 的所有方法、属性、协议
- (void)classMethods_Propertys_Protocols {
    unsigned int count;
    Class class = NSClassFromString(@"NSArray"); // 或 [NSArray class]

    // 获取实例方法列表
    Method *methodList = class_copyMethodList(class, &count);
    for (int i = 0; i < count; i++) {
        NSLog(@"方法: %@", NSStringFromSelector(method_getName(methodList[i])));
    }
    free(methodList);

    // 获取属性列表
    objc_property_t *propertyList = class_copyPropertyList(class, &count);
    for (int i = 0; i < count; i++) {
        NSLog(@"属性: %s", property_getName(propertyList[i]));
    }
    free(propertyList);

    // 获取遵循的协议列表
    Protocol * __unsafe_unretained *protocolList = class_copyProtocolList(class, &count);
    for (int i = 0; i < count; i++) {
        NSLog(@"协议: %@", NSStringFromProtocol(protocolList[i]));
    }
    free(protocolList);
}

// 3. 获取 Protocol 的所有方法和属性
- (void)protocolMethods_Propertys {
    unsigned int count;
    Protocol *protocol = NSProtocolFromString(@"NSObject"); // 或 @protocol(NSObject)

    // 获取协议中的方法(必需方法+可选方法)
    struct objc_method_description *methodList = protocol_copyMethodDescriptionList(protocol, NO, YES, &count);
    for (int i = 0; i < count; i++) {
        NSLog(@"协议方法: %@", NSStringFromSelector(methodList[i].name));
    }
    free(methodList);

    // 获取协议中声明的属性
    objc_property_t *propertyList = protocol_copyPropertyList(protocol, &count);
    for (int i = 0; i < count; i++) {
        NSLog(@"协议属性: %s", property_getName(propertyList[i]));
    }
    free(propertyList);
}

为 Category 添加“属性”(关联对象)

Category 无法直接添加带有 ivar 的属性,但可以通过运行时关联对象 (Associated Object) 模拟 getter 和 setter,实现存储效果。

// UIViewController+Category.h
@interface UIViewController (Category)
@property (nonatomic, strong) NSMutableArray *datas;
@end

// UIViewController+Category.m
#import <objc/runtime.h>
static const void *kDatasKey = &kDatasKey; // 作为关联的键

@implementation UIViewController (Category)

- (NSMutableArray *)datas {
    NSMutableArray *datas = objc_getAssociatedObject(self, kDatasKey);
    if (!datas) {
        datas = [[NSMutableArray alloc] init];
        // 惰性初始化,并在初始化后保存
        objc_setAssociatedObject(self, kDatasKey, datas, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return datas;
}

- (void)setDatas:(NSMutableArray *)datas {
    objc_setAssociatedObject(self, kDatasKey, datas, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

七、实用技巧与平台差异

获取 Bundle 资源

在模块化开发或使用资源包时,正确获取 Bundle 对象是关键。

// 1. 获取主工程内自定义 Bundle(如 app-image.bundle)
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"app-image" ofType:@"bundle"];
NSBundle *resourceBundle = [NSBundle bundleWithPath:bundlePath];

// 2. 获取 Pod 库中的 Bundle(兼容 Pod 1.x 和 更早版本)
// 通过库中的任一 Class 定位其所在的 Bundle
NSString *podBundlePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"lib-image" ofType:@"bundle"];
NSBundle *podResourceBundle = [NSBundle bundleWithPath:podBundlePath];

HTTP 请求绕过全局代理

在某些需要防止请求被代理工具(如 Charles、Fiddler)抓包的安全场景下,可以配置 NSURLSessionConfiguration 来忽略系统的全局代理设置。

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.connectionProxyDictionary = @{}; // 设置为空字典,意味着不使用任何代理
// 使用此 configuration 创建 NSURLSession

WebKit 加载本地 H5 资源包

当使用 WKWebView 加载本地 HTML 文件及其相关资源(JS, CSS, 图片)时,需要允许文件访问,以解决跨域安全限制问题。

WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
// 允许通过 file URL 加载的页面访问其他 file URL 资源
[config.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"];
WKWebView *webView = [[WKWebView alloc] initWithFrame:frame configuration:config];

图片 MD5 的平台差异

iOS 系统在生成或处理图片(尤其是通过 UIImagePNGRepresentation)时,可能会在 PNG 数据中添加一些平台特定的元数据(如时间戳)。这会导致同一个视觉内容在 iOS 上计算出的 MD5 值与在 Android、Windows 等平台计算的结果不一致。如果需要进行跨平台的图片一致性校验,建议先对图片进行标准化处理(如转换为统一的尺寸、格式、并剥离元数据)后再计算哈希值。

八、C 语言基础

内存的动态申请与释放

C 语言中,使用 malloccallocreallocfree 进行堆内存管理。

#include <stdlib.h>
#include <string.h>

void allocInit() {
    // 1. malloc + memset 分配并清零内存
    char *str = malloc(sizeof(char) * 5);
    memset(str, 0, sizeof(char) * 5); // 初始化内存为0
    // 等价于:char *str = calloc(5, sizeof(char)); // calloc 分配并自动清零

    strcpy(str, "abcde"); // 拷贝字符串

    // 2. realloc 调整已分配内存块的大小
    // 注意:realloc 可能会移动内存块到新地址,并释放旧内存。
    str = (char *)realloc(str, sizeof(char) * 10);
    strcat(str, "fghij"); // 拼接字符串

    // 3. 释放内存并将指针置NULL,防止“悬垂指针”
    free(str);
    str = NULL;
}

// 数组的初始化占位符
void arrayInit() {
    int nums[10]; // 未显式初始化,元素值为随机数(栈内存)
    char str[10] = {0}; // 部分初始化,剩余元素自动填充为 '\0'
    // char数组的结束符是 '\0'
}

结构体(Struct)的使用

结构体用于将不同类型的数据组合成一个整体。

#include <stdlib.h>
#include <string.h>

// 定义个人信息结构体
typedef struct sPersonInfo {
    char name[20];
    char telNumber[20];
    int  age;
} PersonInfo;

// 定义列表结构体,包含一个PersonInfo数组
typedef struct sList {
    PersonInfo infos[20];
    int count;
} List;

void testStruct() {
    // 1. 在栈上创建结构体变量
    PersonInfo personInfo;
    strcpy(personInfo.telNumber, "13676399598");
    strcpy(personInfo.name, "刘帅");
    personInfo.age = 27;

    // 2. 在堆上动态分配结构体内存
    List *list = (List *)malloc(sizeof(List));
    memset(list, 0, sizeof(List)); // 初始化结构体
    list->infos[0] = personInfo;   // 结构体赋值
    list->count = 1;

    // 3. 访问结构体成员
    printf("Name: %s\n", list->infos[0].name);

    // 4. 释放堆内存
    free(list);
    list = NULL;
}

Xcode 工程中引用 C 代码

在混合开发时,需要在 Objective-C 项目中正确引用 C 函数。通常通过头文件声明,并使用 extern "C" 防止 C++ 编译器进行名称修饰(mangle)。

// demo.h
#ifndef DEMO_H
#define DEMO_H

#ifdef __cplusplus
extern "C" { // 如果是 C++ 环境,使用 C 语言的链接规则
#endif

// 声明一个供外部调用的 C 函数
extern int demo_main(int argc, char * argv[]);

#ifdef __cplusplus
}
#endif

#endif // DEMO_H

在 Objective-C 的 .m 文件中包含头文件并调用:

// ViewController.m
#import "ViewController.h"
#import "demo.h" // 引入C头文件

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // 调用 C 函数
    int result = demo_main(0, NULL);
}
@end

九、JavaScript 基础

构造函数与原型链

在 JavaScript 中,构造函数用于创建特定类型的对象,而原型(prototype)是实现继承和共享方法的关键。这是JavaScript面向对象编程的基础。

1. 定义构造函数

function MyClass() {
  // 实例属性
  this.myProperty = "it is a property!";
  // 实例方法 (每个实例都会创建自己的方法副本,不推荐,应放在原型上)
  this.call = function(parameter) {
    console.log(parameter);
  };
}

2. 通过原型实现继承

// 父类构造函数
function SuperClass() {
  this.value = "it is a value!";
}
// 父类方法定义在原型上
SuperClass.prototype.setValue = function(value) {
  this.value = value;
  console.log(this.value);
};

// 子类继承父类:将父类实例作为子类的原型
MyClass.prototype = new SuperClass();
// 修正 constructor 指针,使其指向子类构造函数本身
MyClass.prototype.constructor = MyClass;

3. 在原型上扩展方法

MyClass.prototype.callBack = function(result) {
  // 可以访问实例属性
  console.log(this.myProperty);
  console.log(result);
};

4. 创建实例并调用方法

let instance = new MyClass();
instance.call('直接调用实例方法'); // 输出:直接调用实例方法
instance.setValue('新值');         // 输出:新值 (调用继承的方法)
instance.callBack('成功');         // 输出:it is a property! \n 成功 (调用原型方法)

// 5. 动态为实例添加方法
instance.handleMessage = function(message) {
  console.log('处理消息:', message);
};
instance.handleMessage(JSON.stringify({type: 'notify'}));

OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 15OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 16OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 17OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 18OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 19OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 20OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 21OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 22OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 23OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 24OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 25OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 26OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 27OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 28OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 29OC/C/JS 基础手册:iOS开发必备的内存管理、多线程与调试技巧 - 图片 - 30




上一篇:Rsync文件同步完全指南:从配置到实战应用(CentOS 7)
下一篇:ARM Cortex-M单片机启动流程深度解析:从零地址映射到分散加载
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 19:35 , Processed in 0.209433 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表