1、FBKVOController
最近正好在做跟KVO相关的内容,目的是多个对象监听同一个对象的属性,并使用block的方式回调.
研究了下FBKVOController,整理出逻辑.
1、NSObject:通过关联技术, 添加属性 FBKVOController, 其中 observer = self
2、FBKVOController
addObserver:将参数包装成 _FBKVOInfo
内部维护一个mapTable, 可以监听多个target或多个path
key = target
value = Set[_FBKVOInfo]
3、_FBKVOInfo
controller 指向 FBKVOController
内部记录path,以及block
重写isEqual和hash, 用于准确定位
4、FBKVOSharedManager
全局管理类
维护集合Set[_FBKVOInfo]
给target添加KVO, context=_FBKVOInfo
回调
通过context, 找到 _FBKVOInfo
通过 _FBKVOInfo.controller, 找到FBKVOController
通过 FBKVOController, 找到observer
关键点
1、涉及到资源的写操作,使用到互斥锁 pthread_metux_t
2、使用context, 作为准确定位的依据
发现一个小case
_FBKVOInfo 内部在重写isEqual和hash时,主要使用的是path.
- (BOOL)isEqual:(id)object
{
if (nil == object) {
return NO;
}
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
return NO;
}
return [_keyPath isEqualToString:((_FBKVOInfo *)object)->_keyPath];
}
FBKVOSharedManager中存放的是[_FBKVOInfo]的集合.
场景:
对象A和对象B,都监听C的path
1.A添加监听,A的map中,存放 _FBKVOInfo_A
2.FBKVOSharedManager添加 _FBKVOInfo_A
3.B添加监听,B的map中,存放 _FBKVOInfo_B
4.FBKVOSharedManager需要添加 _FBKVOInfo_B,但是在查找是否有相同时,因为 _FBKVOInfo_A 中的path 和 _FBKVOInfo_B 的path 一样,导致_FBKVOInfo_B并没有被添加到 FBKVOSharedManager 中.
5.所以B对象 并没有获取到回调.
调整:
isEqual 中 增加了 controller 的比较.
2、MLeaksFinder
以前的项目中,引入过这个三方库,一直没有读过源码,用于检测内存泄漏.
MLeakedObjectProxy
有个集合单例,用于存放leakedObject的指针地址.
集合中,每个元素的类型为MLeakedObjectProxy,记录了object、objectPtr,以及viewStack
同时,为object关联proxy,关联key为 kLeakedObjectProxyKey
//addLeakedObject
MLeakedObjectProxy *proxy = [[MLeakedObjectProxy alloc] init];
proxy.object = object;
proxy.objectPtr = @((uintptr_t)object);
proxy.viewStack = [object viewStack];
static const void *const kLeakedObjectProxyKey = &kLeakedObjectProxyKey;
objc_setAssociatedObject(object, kLeakedObjectProxyKey, proxy, OBJC_ASSOCIATION_RETAIN);
[leakedObjectPtrs addObject:proxy.objectPtr];
MLeaksMessenger:比较简单,通过alertView的形式,展示内存泄漏的数据信息.
NSObject (MemoryLeak):没有hook方法,提供一些基础方法.
1、提供 swizzleSel 方法
2、willDealloc
- (BOOL)willDealloc {
NSString *className = NSStringFromClass([self class]);
if ([[NSObject classNamesWhitelist] containsObject:className])
return NO;
NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey);
//如果有事件,则不执行后续操作
if ([senderPtr isEqualToNumber:@((uintptr_t)self)])
return NO;
__weak id weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong id strongSelf = weakSelf;
[strongSelf assertNotDealloc];
});
return YES;
}
assertNotDealloc中,将self添加到leakedObjectPtrs,此处将弹出alert,提示可能存在泄漏.
UIApplication (MemoryLeak): 方法交换,用于记录最后一次的点击sender
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleSEL: @selector(sendAction:to:from:forEvent:) withSEL: @selector(swizzled_sendAction:to:from:forEvent:)];
});
}
- (BOOL)swizzled_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event {
objc_setAssociatedObject(self, kLatestSenderKey, @((uintptr_t)sender), OBJC_ASSOCIATION_RETAIN);
return [self swizzled_sendAction:action to:target from:sender forEvent:event];
}
UINavigationController (MemoryLeak):同样存在方法交换,主要针对push和pop.重写willDealloc,需要检查viewControllers
交换以下方法:
pushViewController
popViewControllerAnimated
popToViewController
popToRootViewControllerAnimated
pop时,通过关联对象,为self增加属性kHasBeenPoppedKey,设置为YES
UIView (MemoryLeak):重写willDealloc,需要检查子views
UIViewController (MemoryLeak)
交换以下方法:
viewDidDisappear
如果kHasBeenPoppedKey为YES,则调用willDealloc进行检查
viewWillAppear
kHasBeenPoppedKey设置为NO
dismissViewControllerAnimated
重写willDealloc,需要检查childViewController和presentedViewController
3、MVP
Model-View/ViewController-Presenter
Model:用于数据的获取、存储
View/ViewController:展示、事件
Presenter:将Model处理成View/ViewController需要的结构和逻辑
View/ViewController ->(持有)Presenter,调用A方法
Presenter ->(持有)Model,调用Model中的A方法
Model通过代理/block,将数据传递给Presenter
Presenter 通过代理/block的方式,将数据返回,View/ViewController进行展示.
4、DispatchTime.now() 和 DispatchWallTime.now()
在延迟执行的场景,我们一般会使用 DispatchTime.now() + 5,表示延迟5s后执行.
两者都可以用,不同点在于基准不同.
DispatchTime.now():使用当前时间作为基准,计算未来时间.
DispatchWallTime.now():使用系统时间作为基准(墙钟时间),计算未来时间,如果修改了系统时间,此时就不确定是5了,还需要包含两次系统时间的差值.
dispatch_time stops running when your computer goes to sleep. dispatch_walltime continues running. So if you want to do an action in one hour minutes, but after 5 minutes your computer goes to sleep for 50 minutes, dispatch_walltime will execute an hour from now, 5 minutes after the computer wakes up. dispatch_time will execute after the computer is running for an hour, that is 55 minutes after it wakes up. [转自链接:juejin.cn/post/721812… ]
5、最长回文子串
"babad" -> "bab"
解题思路,两种方式
1、中心扩散法
这种比较容易理解
遍历字符串,针对每个下标i, 往两边扩散找回文串
因为回文串存在奇数和偶数两种场景,所以需要 分别按照 奇数场景(left=i-1,right=i+1)、偶数场景(left=i,right=i+1)
for i in 0..<count {
//奇数
left=i-1,right=i+1
while left>=0, right<count, s[left]==s[right] {
left-=1
right+=1
}
//统计出长度
//偶数
left=i,right=i+1
//按照上面的while,进行扩散,统计出长度
//更新最长
}
2、动态规划
重点是理解dp数组的含义
dp数组,二维,bool类型,含义代表:字符串从i-j,是不是回文串
a:
s[i]==s[j],dp[i][j]是需要依赖(i+1)-(j-1)是不是回文串,dp[i][j]=dp[i+1][j-1],
b:
s[i]!=s[j],d[i][j]=false
初始化:
每个字符本身,就是一个回文串,dp[i][i]=true
遍历:
按照长度遍历,最短=2,最长=count [这个地方比较巧妙,之前一直没有想到]
for l in 2 ... count {
for i in 0 ..< count {
let j = i + l - 1 //通过l找到j
//...递推公式
//更新最长
}
}
6、associateType 关联类型
正常我们在使用泛型时,如下操作
struct Stack<Element> {
var items = [Element]()
mutating func push(_ val: Element) {
items.append(val)
}
}
func someFunction<T: someClass, U: someProtocol>(_ value: T, _ pro: U) {
}
如果想要在protocol中使用泛型,是不能直接写<T>,要使用 associateType,本质也是占位
protocol Container {
associateType ItemType
mutating func append(_ val: ItemType)
}
使用时
struct XX: Container {
typealias ItemType = Int //这句话也可以不加,swift会自动推断
mutating func append(_ val: Int) {
}
}
7、防抖和节流
都是用于处理频繁调用的技术,用于优化性能和用户体验.
不同点在于:调用的时机,目的,适用的场景
防抖
目的: 减少触发次数
调用时机: 事件停止发生一段时间后
适用场景: 用户输入,按钮点击
存在的问题: 一直触发事件,则会没有合适的时机执行
class Debounce {
var timer: DispatchWorkItem?
var queue = DispatchQueue(label: "com.debounce", qos: .userInteractive)
func debounce(delay: DispatchTimeInterval, action: @escaping () -> Void) {
timer?.cancel()
let cur = DispatchWorkItem(block: action)
queue.asyncAfter(deadLine: .now() + delay, execute: timer)
}
}
节流
目的: 限制频率
调用时机: 距离上一次执行后,满足一定的间隔时间,则可以触发
适用场景:
class Throttle {
var interval: TimeInterval = 0.5
var lastTime: Date?
func throttle(action: @escaping () -> Void) {
let cur = Date()
if let last = lastTime {
if cur.timeIntervalSince(last) > interval {
action()
lastTime = cur
}
} else {
action()
lastTime = cur
}
}
}
8、swift中如何创建单例
class Single {
//!!!public的
public static let shared = Single()
//!!! 私有的init
private init() {}
}
9、Combine
Swift的响应式框架,还处在云里雾里中,先记录下自己看的好文章
整个数据流的启动,是 订阅者,只有 订阅者 发出请求,才会启动数据流.
如果没有任何订阅者,发布者是不会提供任何数据的.
class MySubscription: Subscription {
func cancel() {
//第6️⃣步
print("MySubscription cancel")
}
func request(_ demand: Subscribers.Demand) {
//第3️⃣步
//来源于subscriber
print("MySubscription request demand = \(demand)")
}
}
class MyPublisher: Publisher {
typealias Output = Int
typealias Failure = Never
func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, Int == S.Input {
//第1️⃣步
//当 publisher.subscribe(subscriber)时,publisher 接收到订阅者
let subscription = MySubscription()
//订阅者 接收到 订阅对象
subscriber.receive(subscription: subscription)
}
}
class MySubscriber: Subscriber {
typealias Input = Int
typealias Failure = Never
func receive(subscription: any Subscription) {
//第2️⃣步
//订阅者 接收到 订阅对象, 设置 request的需求量
subscription.request(.max(2))
}
func receive(_ input: Int) -> Subscribers.Demand {
//第4️⃣步
print("MySubscriber receive 数据: \(input), 并 返回 Demand")
return .unlimited
}
func receive(completion: Subscribers.Completion<Never>) {
//第5️⃣步
print("MySubscriber receive completion")
}
}
还是需要实践,多练习
10、Concurrency