普通视图

发现新文章,点击刷新页面。
昨天 — 2025年12月18日iOS

1V1 社交精准收割 3.6 亿!40 款马甲包 + 国内社交难度堪比史诗级!

作者 iOS研究院
2025年12月18日 21:06

背景

“她说明年就结婚,转头就把我拉黑了!”2024 年 9 月,山东鱼台县居民王某攥着手机账单冲进警局,声音颤抖。这位常年打工攒下 5 万积蓄的单身汉,从未想过自己在 “念梦”“冬梦” 两款交友 App 上邂逅的 “化妆品店老板娘”,竟是一场精心设计的骗局。

三个月里,这位昵称 “为你而来” 的 “女神” 温柔体贴,频频描绘二人未来的家,却以 “解锁视频聊天”“线下见面需充值刷亲密度” 为由,分三次榨干了他的全部积蓄。当王某停止充值后,昔日热情的恋人瞬间蒸发,只留下 27177 元、9592 元、13794 元三笔冰冷的充值记录。他不知道的是,自己只是这场 3.6 亿诈骗大案中,上千名受害者之一。

40 款马甲包背后:堪比上市公司的诈骗 “工厂”

山东济宁公安破获特大网络交友诈骗案,40余款App全是陷阱。王某的报警,像一把钥匙打开了潘多拉魔盒。警方顺着涉诈 App 的线索深挖,一个隐藏在合法公司外壳下的犯罪集团逐渐浮出水面。团伙头目王某某是正规大学毕业生,曾因运营 “来遇” App 涉诈被查处,却在 2023 年卷土重来,注册多家空壳公司,一口气推出 40 余款交友 App,形成 “换汤不换药” 的马甲矩阵。

这个诈骗团伙的运作模式堪称 “产业化”:运营部负责招募培训 5000 余名女聊手,定制从 “初遇暧昧” 到 “诱导充值” 的全套话术;客服部专门安抚投诉用户,用 “系统维护”“亲密度未达标” 等借口掩盖骗局;甚至设立法务部,钻法律空子规避监管。女聊手们则按照统一剧本,虚构 “单身富婆”“温柔贤妻” 等人设,精准瞄准三、四线城市的大龄单身男性,用暧昧言语和虚假承诺编织情感牢笼。

更令人咋舌的是平台设计的 “吸血机制”:文字消息 10-100 金币 / 条,视频通话 100-2000 金币 / 分钟,充值 1 元仅能兑换 100 金币。女聊手与公司按 4:6 分成,为了多赚钱,她们会用平台发放的免费金币给用户刷礼物,制造 “双向奔赴” 的假象,引诱受害者不断充值。警方后续查获的聊天记录显示,团伙内部流传着 “养鱼玩法拉高点,大哥刷一你刷两” 的黑暗话术。

62 亿条数据剥茧:千人跨省追缉 15 天破局

“这不是零散诈骗,是有组织、有预谋的犯罪网络。” 济宁市公安局迅速成立 “10.14” 专案组,抽调百余名警力攻坚。面对团伙设置的多层数据加密、定期删除证据、核心骨干分散办公等障碍,民警自主编写分析程序,从 8T 容量、超 62 亿条聊天记录和资金明细中抽丝剥茧。

合规化势在必行

立足当前行业大环境,存量社交产品必须将合规化置于开发工作的核心首位。

若不存在关键性的功能迭代需求,建议尽量减少版本更新频次,甚至暂停更新,以此规避审核环节可能出现的风险,避免给产品运营增添不必要的阻碍。

当前国内市场的恶性竞争态势,必然会导致社交类产品在App Store平台面临更严峻的监管压力与发展困境。因此,尽早布局出海业务、开拓海外新市场,已成这类产品突破发展瓶颈的关键方向

合规化的价值懂的无需多言,不懂得多说无益。

遵守规则,方得长治久安,最后祝大家大吉大利,今晚过审!

相关推荐

# 苹果开发者续费大坑及成功续费方案!亲测有效

# AppStore敏感词排查手册,多维度分析Guideline 2.3.1隐藏功能,轻松过审。

# 如何主动提防苹果3.2f的进攻,自查防御手册(代码篇)

# 如何主动提防苹果3.2f的进攻,自查防御手册(ASO篇)

# 苹果加急审核是“绿色通道”还是“死亡陷阱”?

# 苹果开发者邮箱,突然收到11.2通知严重么?

# 不想被苹果卡审最好错开这两个提审时间

# 手撕苹果审核4.3是代码问题还是设计问题?

# 有幸和Appstore审核人员进行了一场视频会议特此记录。

【iOS】如何在 iOS 26 的UITabBarController中使用自定义TabBar

2025年12月18日 16:19

Demo地址:ClassicTabBarUsingDemo(主要实现代码可搜索“📌”查看)

前言

苹果自 iOS 26 起就使用全新的UI --- Liquid Glass,导致很多系统组件也被迫强制使用,首当其冲就是UITabBarController,对于很多喜欢使用自定义TabBar的开发者来说,这很是无奈:

  • 强行给你套个玻璃罩子

那如何在 iOS 26UITabBarController继续使用自定义TabBar呢?这里介绍一下两种方案。

方案一

来自大佬网友分享的方案 💪

  1. 自定义TabBar使用UITabBar,通过KVC设置(老方法):
setValue(customTabBar, forKeyPath: "tabBar")
  1. 重写UITabBaraddSubviewaddGestureRecognizer方法:
- (void)addSubview:(UIView *)view {
    if ([view isKindOfClass:NSClassFromString(@"UIKit._UITabBarPlatterView")]) {
        view.hidden = YES;
    }
    [super addSubview:view];
}

- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
    if ([gestureRecognizer isKindOfClass:NSClassFromString(@"_UIContinuousSelectionGestureRecognizer")]) {
        gestureRecognizer.enabled = NO;
    }
    [super addGestureRecognizer:gestureRecognizer];
}

解释一下:

_UITabBarPlatterView这个是显示当前Tab的玻璃罩子:

  • 把它隐藏掉就行了

_UIContinuousSelectionGestureRecognizer这个是系统用来处理TabBar切换时的动画手势,触发时会在TabBar上添加_UIPortalView这个跟随手势的玻璃罩子:

  • 同样把它禁止掉就行了

这样就相当于把UITabBar的液态玻璃“移除”掉了,是可以实现以往的显示效果👏。

只不过这个方案在pop手势滑动时,TabBar会被「置顶」显示:

  • 这是苹果新UI的显示逻辑,暂时无法改动

这跟我的预期还差了一点,我是希望连pop手势也能像以前那样:

接下来介绍另一个方案,虽然麻烦很多,但能兼顾pop手势。

方案二

经观察,以往TabBar的显示效果,个人猜测系统是把TabBar放到当前子VC的view上:

按照这个思路可以这么实现:

  1. 首先自定义TabBar要使用UIView(如果使用的是私自改造的UITabBar,得换成UIView了),并且隐藏系统TabBar。
class MainTabBarController: UITabBarController {
    ......
    
    /// 自定义TabBar
    private let customTabBar = WLTabBar()
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // 隐藏系统TabBar
        setTabBarHidden(true, animated: false)
    }
    
    ......
}

  1. TabBarController及其子VC都创建一个专门存放自定义TabBar的容器,且层级必须是最顶层(之后添加的子视图都得插到TabBar容器的下面)。
class BaseViewController: UIViewController {
    /// 专门存放自定义TabBar的容器
    private let tabBarContainer = TabBarContainer()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        tabBarContainer.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tabBarContainer)
        NSLayoutConstraint.activate([
            tabBarContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tabBarContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tabBarContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            tabBarContainer.heightAnchor.constraint(equalToConstant: Env.tabBarFullH) // 下巴+TabBar高度
        ])
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        // 层级必须是最顶层
        view.bringSubviewToFront(tabBarContainer)
    }
    
    // 将自定义TabBar放到自己的TabBar容器上
    func addTabBar(_ tabBar: UIView) {
        tabBar.superview?.isUserInteractionEnabled = false
        tabBarContainer.addSubview(tabBar)
        tabBarContainer.isUserInteractionEnabled = true
    }
}
  1. 最后,TabBarController当前显示哪个子VC,就把自定义TabBar放到对应子VC的TabBar容器上,这样则不会影响pushpresent其他VC。

OK,完事了😗。

注意点

核心实现就是以上3点,接下来讲一下注意点。

上面说到,TabBarController也得创建一个TabBar容器,这主要是用来切换子VC的:

在切换子VC前,自定义TabBar必须先放到TabBarController的TabBar容器上,切换后再放到目标子VC的TabBar容器上。

🤔为什么?

一般子VC的内容都是懒加载(看到才构建),如果是很复杂的界面,不免会有卡顿的情况,如果直接把自定义TabBar丢过去,TabBar会闪烁一下,效果不太好;另外自 iOS 18 起切换子VC会带有默认的系统动画,其动画作用于子VC的view上,即便该子VC早就构建好,立马转移TabBar也会闪烁一下。

因此个人建议先把自定义TabBarTabBarControllerTabBar容器上(层级在所有子VC的view之上),延时一下(确保子VC完全构建好且已完全显示,同时避免被系统动画影响)再放到目标子VC的TabBar容器上,这样就能完美实现切换效果了。

核心代码如下:

// MARK: - 挪动TabBar到目标子VC
private extension MainTabBarController {
    func moveTabBar(from sourceIdx: Int, to targetIdx: Int) {
        guard Env.isUsingLiquidGlassUI else { return }
        
        // #1 取消上一次的延时操作
        moveTabBarWorkItem?.cancel()
        moveTabBarWorkItem = nil
        
        guard let viewControllers, viewControllers.count > 0 else {
            addTabBar(customTabBar)
            return
        }
        
        guard sourceIdx != targetIdx else {
            _moveTabBar(to: targetIdx)
            return
        }
        
        // #2 如果「当前子VC」现在不是处于栈顶,就把tabBar直接挪到「目标子VC」
        let sourceNavCtr = viewControllers[sourceIdx] as? UINavigationController
        if (sourceNavCtr?.viewControllers.count ?? 0) > 1 {
            _moveTabBar(to: targetIdx)
            return
        }
        
        // #3 能来这里说明「当前子VC」正处于栈顶,如果「目标子VC」此时也处于栈顶,就把tabBar放到层级顶部(不受系统切换动画的影响)
        let targetNavCtr = viewControllers[targetIdx] as? UINavigationController
        if (targetNavCtr?.viewControllers.count ?? 0) == 1 {
            addTabBar(customTabBar)
        } else {
            _moveTabBar(to: sourceIdx)
        }
        
        // #3.1 延迟0.5s后再放入到「目标子VC」,给VC有足够时间去初始化和显示(可完美实现旧UI的效果;中途切换会取消这个延时操作#1)
        moveTabBarWorkItem = Asyncs.mainDelay(0.5) { [weak self] in
            guard let self, self.selectedIndex == targetIdx else { return }
            self.moveTabBarWorkItem = nil
            self._moveTabBar(to: targetIdx)
        }
    }
    
    func _moveTabBar(to index: Int) {
        let tab = MainTab(index: index)
        switch tab {
        case .videoHub:
            videoHubVC.addTabBar(customTabBar)
        case .channel:
            channelVC.addTabBar(customTabBar)
        case .live:
            liveVC.addTabBar(customTabBar)
        case .mine:
            mineVC.addTabBar(customTabBar)
        }
    }
}

如果想移除系统切换动画可以这么做:

// MARK: - <WLTabBarDelegate>
extension MainTabBarController: WLTabBarDelegate {
    func tabBar(_ tabBar: WLTabBar!, didSelectItemAt index: Int) {
        moveTabBar(from: selectedIndex, to: index)
        // 想移除系统自带的切换动画就👇🏻
        UIView.performWithoutAnimation {
            self.selectedIndex = index
        }
    }
}

小结

方案一是比较激进的魔改方案,直接把系统的玻璃罩子和手势给移除掉了,缺点是如果苹果以后改动了这些私有类名或行为,可能会导致失效。

方案二是我能想到最完美的方案了,起码不用自定义UITabBarController,简单粗暴,个人感觉能应付80%的应用场景吧,除非你有非常特殊的过场动画需要挪动TabBar的。

以上就是我的方案了,起码不用自定义UITabBarController,简单粗暴,个人感觉能应付80%的应用场景吧,除非你有非常特殊的过场动画需要挪动TabBar的。

更多细节可以参考Demo,以上两种方案都有提供,只需要在WLTabBar.h中选择使用哪一种父类并注释另一个即可:

@interface WLTabBar : UITabBar // 方案一
@interface WLTabBar : UIView // 方案二

希望苹果以后能推出兼容自定义TabBar的API,那就不用这样魔改了😩。

重温UTF-8和UTF-16

作者 _阿南_
2025年12月18日 13:37

一、UTF-8

UTF-8 的全称为: Unicode Transformation Format - 8 - bit

  • Unicode 统一字符集,给世界上所有文字分配唯一编号
  • Transformation Format  转换格式
  • 8-bit  以8位(一个字节) 为基本单位进行编码

UTF-8 是一种把Unicode字符编码成1~4个字节的编码方式

变长编码:

  1. 英文:  1个字节
  2. 拉丁字母: 2个字节 (拉丁字母和希腊字母)
  3. 中文: 3个字节 (大多数常见汉字)
  4. Emoji: 4字节 (包括生僻字符)

编码规则:

  1. 对于长度为1字节的字符,将最高位设置为0.
  2. 对于长度为n字节的字符(n > 1),将首个字节的高n位都设置为1,第n+1位设置为0;从第二个字节开始,将每个字节的高2位都设置为10;

理解: 如果读到一个Byte,那么就取第一位,如果是0,那么就是Ascii码。如果是1,那么继续取值,直到0,有几个1就是几个字符长度。读取后面字符后,组装成一个有效的Unicode码。

举例: “算”字的

Unicode是 U+7B97:

7      B      9      7

0111 1011 1001 0111

UTF-8的编码为 0xE7AE97

E        7      A      E      9      7

1110 0111 1010 1110 1001 0111

UTF-16的编码为 0x7B97

7      B      9      7

0111 1011 1001 0111

二、 UTF-16编码: 2或4字节

  1. 2个字节: 当码点在 U+0000 到 U+FFFF,不在代理区 U+D800 到 U+FFFF
  2. 4个字节:  当码点在U+10000 到 U+10FFFF时,使用代理对计算

代理对计算规则:

Unicode有一段特别的区间:  U+D800 到 U+DFFFF  不是字符,仅仅是UTF-16的代理对。

当码点 >= 0x10000 时:

  1. 减去0x10000   

    1. U = codePoint - 0x10000
  2. 拆成高10位和低10位

    1. high = U >> 10
    2. low = U & 0x3FF
  3. 加上代理基值

    1. 高代理 = 0xD800 + high
    2. 低代理 = 0xDC00 + low
  4. 得到4字节的UTF-16编码

解析UTF-16逻辑:

  1. 先取2个字节;
  2. 如果该值落在0xD800到0xDBFF,说明这是高代理,需要再取2个字节组成4个字节字符。
  3. 再取的2个字节在0xDC00到0xDFFF内。
  4. 否则该字符就是2字节表示。

举例:

  1. 读取到 0xD83D
  2. 因为0xD83D在 0xD800到0xDBFF之间,说明是高代理  0xD83D - 0xD800 = 0x3D
  3. 再获取2字节,读取到0xDE00
  4. 因为0xDE00在0xDC00到0xDFFFF之间,说明是低代理 0xDE00 - 0xDC00 = 0x200
  5. 计算 0x3D << 10 + 0x200 + 0x10000 = 0xF400 + 0x10200 = 0x1F600

结果为 0xD83D的Unicode值为 😀(U+1F600)

知识点:

  1. 使用UTF-16编码的语言有Java、JavaScript、TypeScript和C#。
  2. 网络传输时使用UTF-8格式,以达到最优的兼容性和空间效率。
❌
❌