日常开发随记

40 阅读7分钟

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的响应式框架,还处在云里雾里中,先记录下自己看的好文章

www.infoq.cn/article/eaq…

整个数据流的启动,是 订阅者,只有 订阅者 发出请求,才会启动数据流.

如果没有任何订阅者,发布者是不会提供任何数据的.

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

onevcat.com/2021/07/swi…