淡蓝网

淡蓝网

1.SDK体积优化,无用代码检测怎么做的?

2.加载速度优化,怎么发现的多次刷新?

3.高考项目

4.循环引用怎么解决?NSTimer 为什么用 weak 不能解决循环引用

5.Swift 中 weak 和 unowned 的区别?什么场景下用 unowned?或者说 unowned 有什么好处?

6.什么场景下用分类?Category 能添加属性吗?为什么不能添加成员变量?苹果为什么这么设计?

7.为什么要设计元类?

8.SDK中有个类有私有成员变量,怎么改它的值?

9.数组的方法被 hook 了,怎么让有些数组不生效,使用系统的默认方法

参考答案

1.SDK体积优化,无用代码检测怎么做的?

通过使用otool工具对编译产生的Mach-O文件,结合项目源码进行分析。

第一步:
找到项目中所有的protocol中的方法<包括系统<UITabaleViewDelegate>的和项目的>

第二步:
通过Mach-O,找到项目中所有被引用的方法

第三步:
通过Mach-O,找到项目中所有的方法

第四步:
遍历第三步查出的所有方法,如果当前方法不在代理方法集合,也不在引用方法集合中,那么就认为是没有用到的方法。

参考链接:简书-iOS 脚本查看项目未使用到的方法

2.加载速度优化,怎么发现的多次刷新

3.高考项目

4.循环引用怎么解决?NSTimer为什么用weak不能解决循环引用

解决循环引用的方法

1. 使用 weak 修饰符

在使用闭包时,可以使用weakunowned关键字来解决循环引用问题。

1
2
3
4
__weak typeof(self) weakSelf = self;
self.someBlock = ^{
[weakSelf doSomething];
};

在Swift中:

1
2
3
4
weak var weakSelf = self
self.someBlock = { [weak weakSelf] in
weakSelf?.doSomething()
}

2. 使用 delegateweak 修饰符

在使用委托模式时,可以将委托声明为weak

1
@property (nonatomic, weak) id<MyDelegate> delegate;

在Swift中:

1
weak var delegate: MyDelegate?

3. 使用 __block 修饰符

在某些情况下,可以使用__block修饰符来解决循环引用问题。

1
2
3
4
5
__block typeof(self) blockSelf = self;
self.someBlock = ^{
[blockSelf doSomething];
blockSelf = nil; // 解除循环引用
};

为什么 NSTimer 使用 weak 不能解决循环引用

NSTimer 是一个常见的导致循环引用的源头,因为它会强引用其目标对象。即使你在目标对象中使用weak修饰符,NSTimer仍然会强引用目标对象,导致循环引用。

使用 NSTimer 的正确方法

  1. 使用中介对象

你可以创建一个中介对象,这个对象不会强引用目标对象,从而打破循环引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@interface TimerProxy : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@end

@implementation TimerProxy
- (void)timerDidFire:(NSTimer *)timer {
if (self.target) {
[self.target performSelector:self.selector withObject:timer];
} else {
[timer invalidate];
}
}
@end

// 使用中介对象
TimerProxy *proxy = [TimerProxy new];
proxy.target = self;
proxy.selector = @selector(timerDidFire:);

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:proxy
selector:@selector(timerDidFire:)
userInfo:nil
repeats:YES];
  1. 使用 GCD 定时器

GCD 提供了一种无需手动管理循环引用的定时器。

1
2
3
4
5
6
7
8
9
10
11
dispatch_queue_t queue = dispatch_get_main_queue();
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

dispatch_source_set_timer(self.timer, dispatch_time(DISPATCH_TIME_NOW, 0), 1.0 * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10);

__weak typeof(self) weakSelf = self;
dispatch_source_set_event_handler(self.timer, ^{
[weakSelf timerDidFire];
});

dispatch_resume(self.timer);

在Swift中:

1
2
3
4
5
6
7
8
9
10
11
let queue = DispatchQueue.main
let timer = DispatchSource.makeTimerSource(queue: queue)

timer.schedule(deadline: .now(), repeating: 1.0)

timer.setEventHandler { [weak self] in
self?.timerDidFire()
}

timer.resume()
self.timer = timer

5.swift中weak和unowned的区别?什么场景下用unowned?或者说unowned有什么好处?

在Swift中,weakunowned 是两种用于解决强引用循环(retain cycle)问题的引用类型修饰符。虽然它们都可以用来打破循环引用,但它们的行为和使用场景有所不同。

weakunowned 的区别

weak

  1. 可选类型weak 修饰的引用会被自动设置为 nil 当其引用的对象被释放时。因此,weak 引用必须是可选类型(Optional)。

  2. 自动解除引用:使用 weak 修饰符可以防止强引用循环,因为当被引用对象被释放时,weak 引用会自动变成 nil

  3. 用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Person {
    var name: String
    weak var spouse: Person?

    init(name: String) {
    self.name = name
    }
    }

    var john: Person? = Person(name: "John")
    var jane: Person? = Person(name: "Jane")

    john?.spouse = jane
    jane?.spouse = john

    john = nil
    // At this point, jane?.spouse is automatically set to nil

unowned

  1. 非可选类型unowned 修饰的引用在其引用的对象被释放后不会自动变成 nil,并且它必须是非可选类型(Non-Optional)。

  2. 无自动解除引用:使用 unowned 修饰符不会自动变成 nil,如果你尝试访问一个已经被释放的对象,会导致运行时崩溃。

  3. 用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Person {
    var name: String
    var car: Car?

    init(name: String) {
    self.name = name
    }
    }

    class Car {
    var model: String
    unowned var owner: Person

    init(model: String, owner: Person) {
    self.model = model
    self.owner = owner
    }
    }

    var john: Person? = Person(name: "John")
    var johnsCar: Car? = Car(model: "Tesla", owner: john!)

    john?.car = johnsCar

    john = nil
    // At this point, accessing johnsCar?.owner will cause a runtime crash because john has been deallocated

使用场景

什么时候使用 weak

  • 当引用的对象可能在其生命周期中变为 nil 时。
  • 常见的场景包括委托(delegates)、UI元素的引用(例如:视图控制器对视图的引用),以及任何你预计会变为 nil 的引用。

什么时候使用 unowned

  • 当引用的对象在其生命周期中不会变为 nil,并且你确定引用的对象会一直存在直到被引用对象的生命周期结束。
  • 常见的场景包括:两个对象具有相同的生命周期,或一个对象的生命周期完全包含另一个对象的生命周期。
  • 例如,闭包对 self 的引用可以使用 unowned 修饰符,因为闭包的生命周期通常与其持有者相同或更短。

unowned 的好处

  • 性能:由于 unowned 引用不会自动设置为 nil,它的性能稍微优于 weak 引用,因为不需要额外的nil检查。
  • 非可选类型unowned 引用不需要解包,因为它是非可选类型,这可以简化代码。

然而,使用 unowned 引用时需要非常小心,因为如果引用的对象被释放后再访问该引用,会导致运行时崩溃。一般来说,除非你非常确定引用的对象在其生命周期中不会变为 nil,否则建议使用 weak 引用。

6.什么场景下用分类?Category能添加属性吗?为什么不能添加成员变量?苹果为什么这么设计?

在 Objective-C 中,Category 是用于为现有类添加方法的一种机制,但它不能直接添加成员变量(实例变量)。不过,可以通过一些间接方式为 Category 添加属性。让我们深入探讨一下为什么 Category 不能添加成员变量、苹果为什么设计成这样,以及如何在 Category 中添加属性。

为什么 Category 不能添加成员变量

Category 主要设计目的是为了扩展已有类的功能,而不是修改类的内部结构。添加成员变量会涉及到修改类的内存布局,这在运行时会带来很多复杂性和潜在的问题。

  1. 内存布局问题:Objective-C 的内存布局是在编译时确定的,而成员变量会影响对象的内存布局。如果允许在 Category 中添加成员变量,那么就意味着当一个新 Category 被加载时,需要重新计算和调整所有实例的内存布局,这在运行时是非常复杂和低效的。
  2. 二进制兼容性:允许 Category 添加成员变量会破坏二进制兼容性。假设一个类库在某个版本中添加了一个包含成员变量的 Category,而在早期版本中没有这个 Category,那么在不同版本之间切换时可能会导致崩溃或未定义行为。
  3. 动态加载:Objective-C 支持动态加载类和 Category。如果 Category 能够添加成员变量,那么动态加载一个新的 Category 时,需要重新分配和调整现有对象的内存,这在设计和实现上是非常困难的。

苹果为什么这么设计

苹果设计 Category 的目的是为了提供一种灵活且轻量级的方式来扩展类的功能,而不改变类的基本结构。这样设计的主要原因包括:

  1. 简化实现:不允许 Category 添加成员变量使得内存管理和对象模型的实现更加简单和可靠。
  2. 动态特性:Objective-C 是一种动态语言,Category 提供了在运行时扩展类功能的能力,而无需重新编译类文件。这种灵活性是Objective-C的一个重要特性。
  3. 稳定性和兼容性:通过限制 Category 的能力,可以确保类的内存布局在整个应用程序生命周期内保持稳定,从而提高程序的稳定性和兼容性。

如何在 Category 中添加属性

虽然 Category 不能直接添加成员变量,但可以通过关联对象(Associated Objects)来间接实现属性的添加。

1. 使用关联对象

你可以使用 objc_setAssociatedObjectobjc_getAssociatedObject 函数来为 Category 添加属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import <objc/runtime.h>

@interface MyClass (CategoryName)
@property (nonatomic, strong) id property;
@end

@implementation MyClass (CategoryName)

- (void)setProperty:(id)value {
objc_setAssociatedObject(self, @selector(property), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)property {
return objc_getAssociatedObject(self, @selector(property));
}

@end

在上面的代码中,我们使用了关联对象来为 Category 添加属性。objc_setAssociatedObject 函数将一个对象与一个键关联起来,而 objc_getAssociatedObject 函数则根据键获取关联的对象。

2. 关联对象策略

objc_setAssociatedObject 的第四个参数是一个关联策略,常见的策略包括:

  • OBJC_ASSOCIATION_ASSIGN:相当于 assign 属性。
  • OBJC_ASSOCIATION_RETAIN_NONATOMIC:相当于 strong 非原子属性。
  • OBJC_ASSOCIATION_COPY_NONATOMIC:相当于 copy 非原子属性。
  • OBJC_ASSOCIATION_RETAIN:相当于 strong 原子属性。
  • OBJC_ASSOCIATION_COPY:相当于 copy 原子属性。

结论

虽然 Category 不能直接添加成员变量,但通过关联对象,我们可以间接地为 Category 添加属性。这种设计使得 Category 的实现更加简单和稳定,同时保留了扩展类功能的灵活性。苹果选择不允许 Category 添加成员变量,是为了简化内存管理、提高运行时性能和确保二进制兼容性。

7.为什么要设计元类?

8.SDK中有个类有私有成员变量,怎么改它的值?

9.数组的方法被hook了,怎么让有些数组不生效,使用系统的默认方法

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×