iOS 常用属性和方法总结

153 阅读6分钟

1 关键字

1 id、instancetype、NSObject * 和 void * 类型声明的区别

id

  • OC 对象的通用指针类型,指向任何 Objective-C 对象
  • 在编译时不会进行对象类型检查,在运行时进行动态类型识别和消息发送
  • 可以用在变量声明、方法形参和返回类型声明

instancetype

  • 表示当前类的实例类型

  • 在编译时确定对象的真实类型

  • 只能用在返回类型声明

NSObject *

  • NSObject * 指向继承自 NSObject 对象的指针类型。
  • 在编译时会进行对象类型检查,因此只能指向 NSObject 或其子类的实例对象

void *

  • 通用的指针类型,指向任何数据类型,包括 C 语言的基本数据类型、结构体
  • 不会进行类型检查,使用时需要进行显式的类型转换

2 NULL、nil、Nil 和 NSNull 的区别

NULL 用于表示空指针,通常在 C 语言中使用

nil 用于表示空对象,通常在 Objective-C 中使用

Nil 用于表示空类,通常在 Objective-C 中使用

NSNull 是Foundation 框架中的一个特殊类,用于表示空值,并可用于集合中区分真正的 nil 值

2 方法

1 +load 和 +initialize 的区别

+load 方法

  • 在程序启动时自动调用 +load,在 main 函数之前被调用,且只调用一次
  • 如果一个类的多个类别实现了 +load 方法,先调用主类的 +load,再调用分类的 +load
  • +load 是根据方法地址直接调用,并不是经过 objc_msgSend 函数调用

+initialize 方法

  • 当类或其子类接收到第一条消息时调用,在 init 函数之前被调用,每个类只会调用一次

  • 如果一个类的多个类别实现了+initialize方法,会调用最后一个被编译的类别的+initialize方法

  • 继承的情况下,父类的 +initialize 方法会在子类的 +initialize 方法调用之前调用。父类的 +initialize 方法可能会被多次调用(如果有多个子类),而子类的 +initialize 方法只会被调用一次

同一个类有多个类别或多个继承时,其编译顺序:Xcode 中的 Build Phases 中的 Compile Sources 中的文件从上到下的顺序加载的。

2 alloc-init 和 new 的区别

alloc-init:alloc 方法分配内存空间, init 方法对对象进行初始化。

new:new 是一个类方法,结合了分配内存和初始化对象的两个步骤。

3 copy 与 mutableCopy 的区别

copy 方法:

  • 不可变拷贝,返回的对象类型是不可变的

  • 在非集合类对象中,对不可变对象进行 copy 操作是指针复制;对可变对象进行 copy 操作是内容复制

  • 在集合类对象中,对不可变对象进行 copy 操作是指针复制;对可变对象进行 copy 操作是内容复制

mutableCopy 方法:

  • 可变拷贝,返回的对象类型是可变的

  • 在非集合类对象中,对不可变对象和可变对象进行 mutableCopy 操作是内容复制

  • 在集合类对象中,对不可变对象和可变对象进行 mutableCopy 操作是内容复制

集合对象的内容复制仅限于对象本身,对集合内的对象元素仍然是指针复制(即单层内容复制)。

Objective-C 对象能否被 copy 的条件?

取决于该对象是否遵循 NSCopyingNSMutableCopying 协议。

@interface MyClass : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@end

@implementation MyClass

- (id)copyWithZone:(NSZone *)zone {
    MyClass *class = [[MyClass allocWithZone:zone] init];    
    class.name = [self.name copy]; // 深拷贝属性
    return class;
}

@end 

使用 copy 修饰符声明属性会发生什么?

使用 copy 修饰一个属性时,编译器会在对象被赋值给这个属性时,执行一次深拷贝(deep copy)。这意味着,不管原始对象是可变的还是不可变的,被赋值给属性的对象都是不可变的。确保属性的值在设置后不会被修改,从而提高程序的安全性和稳定性。

4 isMemberOfClass 和 isKindOfClass 联系与区别

两者都可以用于判断对象的类型,即判断对象是否属于某个特定的类或其子类。

isMemberOfClass 检查对象是否是某个特定类的实例(不包括子类)。

isKindOfClass 检查对象是否是某个特定类或其子类的实例。

5 self = [super init] 方法有什么作用?

容错处理。当父类的初始化方法返回了 nil(表示初始化失败),当前对象也会被置为 nil,从而避免出现野指针错误。

- (instancetype)init {
    self = [super init]; // 调用父类的初始化方法
    if (self) {
        // 进行子类的初始化工作
    }
    return self;
}

6 [super dealloc] 方法调用时机?

ARC 环境:

不需要手动调用 [super dealloc] 方法。编译器会自动插入内存管理代码,包括正确处理父类的资源释放。

MRC 环境:

子类释放一个对象时,需要手动调用父类的 [super dealloc] 方法,来确保父类的资源得到正确释放。

- (void)dealloc {
    // 释放子类自己的资源
    [self.object release];
    
    // 最后调用父类的 dealloc 方法释放父类的资源,避免内存泄漏等问题
    [super dealloc];
} 

3 其他

1 Objective-C 调用静态方法时,需要对对象进行 release 吗?

静态方法是类方法,直接通过类名来调用,不需要创建类的实例对象,因此不会涉及到对象的创建和内存管理。

2 self.name = _name 和 name = _name 的区别

self.name = _name 是通过属性访问器(setter)方法来设置属性的值。这种方式会触发属性的 KVC(Key-Value Coding)机制,可以执行额外的操作,比如内存管理、触发 KVO(Key-Value Observing)等。
name = _name 是直接对实例变量进行赋值,绕过了属性的 setter 方法。

3 Objective-C 是否有二维数组,如何实现二维数组?

没有内置的二维数组类型,可以使用 NSArray 嵌套来模拟二维数组的结构。

// 创建一个二维数组
NSMutableArray *A = [NSMutableArray array];
for (int i = 0; i < 3; i++) {
    NSMutableArray *B = [NSMutableArray array];
    for (int j = 0; j < 3; j++) {
        [B addObject:@(i * 3 + j)];
    }
    [A addObject:B];
}

// 访问二维数组中的元素
NSInteger element = [A[1][2] integerValue];

4 实现一个线程安全的 NSMutableArray

线程锁实现

#import <Foundation/Foundation.h>

@interface ThreadSafeArray : NSObject

- (void)addObject:(id)object;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (id)objectAtIndex:(NSUInteger)index;

@end

@implementation ThreadSafeArray {
    NSMutableArray *_array;
    NSLock *_lock;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _array = [NSMutableArray array];
        _lock = [[NSLock alloc] init];
    }
    return self;
}

- (void)addObject:(id)object {
    [_lock lock];
    [_array addObject:object];
    [_lock unlock];
}

- (void)removeObjectAtIndex:(NSUInteger)index {
    [_lock lock];
    if (index < _array.count) {
        [_array removeObjectAtIndex:index];
    }
    [_lock unlock];
}

- (id)objectAtIndex:(NSUInteger)index {
    [_lock lock];
    id object = nil;
    if (index < _array.count) {
        object = _array[index];
    }
    [_lock unlock];
    return object;
}

@end 

GCD 实现(更加高效)

#import <Foundation/Foundation.h>

@interface ThreadSafeArray : NSObject

- (void)addObject:(id)object;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (id)objectAtIndex:(NSUInteger)index;

@end

@implementation ThreadSafeArray {
    NSMutableArray *_array;
    dispatch_queue_t _concurrentQueue;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _array = [NSMutableArray array];
        _concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    }
    return self;
}

- (void)addObject:(id)object {
    dispatch_barrier_async(_concurrentQueue, ^{
        [_array addObject:object];
    });
}

- (void)removeObjectAtIndex:(NSUInteger)index {
    dispatch_barrier_async(_concurrentQueue, ^{
        if (index < _array.count) {
            [_array removeObjectAtIndex:index];
        }
    });
}

- (id)objectAtIndex:(NSUInteger)index {
    __block id object = nil;
    dispatch_sync(_concurrentQueue, ^{
        if (index < _array.count) {
            object = _array[index];
        }
    });
    return object;
}

@end 

5 NSMutableDictionary 中的 setObject:forKey: 与 setValue:forKey: 方法的区别

  1. setObject:forKey:

    • NSMutableDictionary 的方法,用于将指定的值与指定的键相关联
    • 如果传入的值为 nil,会导致抛出异常
    • Key同名会替换原来键所对应的值
  2. setValue:forKey:

    • NSObject 类的方法,用于设置任何对象的属性值,而不仅仅是字典
    • 如果传入的值为 nil,不会引发异常
    • Key同名会触发 KVC机制,可能会影响属性的其他行为

6 谓词使用

谓词是用于描述条件的对象,通常用于数据筛选。iOS中 NSPredicate 类提供了创建和评估谓词的功能。

NSArray *usersArray = @[
    @{@"name": @"Alice", @"age": @20},
    @{@"name": @"Bob", @"age": @16},
    @{@"name": @"Charlie", @"age": @25}
];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age >= 18"];
NSArray *filteredUsers = [usersArray filteredArrayUsingPredicate:predicate];

NSLog(@"Users with age >= 18: %@", filteredUsers);

7 UICollectionView 自定义 layout 实现

  1. 继承实现
    • 创建继承 UICollectionViewLayout 的子类。
  2. 重写布局方法
    • prepareLayout: 预先存储布局信息,提高性能。

    • collectionViewContentSize: 基于 prepareLayout 缓存布局信息,计算并返回collectionView的内容尺寸。

    • layoutAttributesForElementsInRect: 返回给定矩形范围内所有单元格和视图的布局属性。

    • layoutAttributesForItemAtIndexPath: 返回给定索引路径的单元格或视图的布局属性。

    • layoutAttributesForSupplementaryViewOfKind: atIndexPath: 返回指定补充视图种类(比如header或footer)在指定索引路径处的布局属性。

    • layoutAttributesForDecorationViewOfKind: atIndexPath: 返回指定装饰视图种类(比如背景或分隔线)在指定索引路径处的布局属性。

  3. 处理布局更新
    • shouldInvalidateLayoutForBoundsChange: 重写方法来响应UICollectionView的滚动操作。
  4. 注册自定义布局
    • self.collectionView.collectionViewLayout = [[CustomLayout alloc] init];