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 的条件?
取决于该对象是否遵循 NSCopying 或 NSMutableCopying 协议。
@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: 方法的区别
-
setObject:forKey:- NSMutableDictionary 的方法,用于将指定的值与指定的键相关联
- 如果传入的值为 nil,会导致抛出异常
- Key同名会替换原来键所对应的值
-
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 实现
- 继承实现
- 创建继承
UICollectionViewLayout的子类。
- 创建继承
- 重写布局方法
-
prepareLayout:预先存储布局信息,提高性能。 -
collectionViewContentSize:基于 prepareLayout 缓存布局信息,计算并返回collectionView的内容尺寸。 -
layoutAttributesForElementsInRect:返回给定矩形范围内所有单元格和视图的布局属性。 -
layoutAttributesForItemAtIndexPath:返回给定索引路径的单元格或视图的布局属性。 -
layoutAttributesForSupplementaryViewOfKind: atIndexPath:返回指定补充视图种类(比如header或footer)在指定索引路径处的布局属性。 -
layoutAttributesForDecorationViewOfKind: atIndexPath:返回指定装饰视图种类(比如背景或分隔线)在指定索引路径处的布局属性。
-
- 处理布局更新
shouldInvalidateLayoutForBoundsChange:重写方法来响应UICollectionView的滚动操作。
- 注册自定义布局
self.collectionView.collectionViewLayout = [[CustomLayout alloc] init];