阅读视图

发现新文章,点击刷新页面。

不踩坑!苹果开发者账号:公司号和个人号,到底该怎么选?

背景

对Appstore来说,账号主要分为3种身份,每种身份都对应着不同的产品需求。

  • 企业开发者账号
  • 公司开发者账户
  • 个人开发者账号

企业性质

企业性质的开发者账号也就是江湖俗称的企业签账号,最大的职能作用公司内部的产品分发,无法上架AppStore。一般常见的为大厂内部的OA系统或者内部仓储管理等。

企业性质的开发账号,在江湖中颇具盛名。最初AppStore的初心是将此性质账号提供内部服务,但是现状主要被用作无法过审AppStore的产品分发 (那些有着满脑子骚操作的产品)

同时,企业账户还有一句传说:账号在手,别墅靠海!

其实严格意义上来讲并不过分,早在2022年申请企业性质的开发者账号就需要提供5000+员工的在职社保证明,仅此一点足矣劝退大多数公司。

在2020年,企业账户市场价大概在30w。截止2023年,售价可高达80w+。基本上可以说是有市无价的局面。

公司性质

公司性质的账号主要用于AppStore产品上架和多人团队管理。

有更加完善的职能划分,区分开发、运营、财务等众多身份,便于产品数据的保护与各个职权之间的数据分离。

注册需要额外准备邓白氏编码,可正常访问的公司官网,以及688元的开发者资格年费。

个人性质

个人性质的账号,基本上是以独立开发者的身份面向AppStore。

但其实很多公司或组织,也在以个人性质维护迭代产品。

注册只需要一个未注册苹果开发者的人,以及688元的开发者资格年费。

公司和个人选哪个?

坦白讲对于正规公司业务或需要版权要求的,必须使用公司账号。【特指金融属性,例如:股票、期货、贷款等】

当然,也有根据公司业务老板心情,比如说需要打造品牌IP或者服务政府项目,也建议采用公司性质,可以减少不必要的麻烦和信任问题。

还有一种情况那就是运营要求,比如有些大厂的投放平台需要产品、账号、软著一一匹配,否则无法正常投放产品或者无法将账户绑定。

除非以上特殊需求,其实建议就用个人性质足矣。

我自己手里上架60+产品,分别归属在7个开发者账号中,均为个人性质。因为对于我来讲,工具类和参考类的产品没有必要用公司。毕竟注册公司的成本高,时间周期也长过于繁琐。工具类的产品可能一年都赚不到开公司的钱,大可不必增加试错成本。

于私来讲,如果账号凉了被AppStore判定违规封号且公司资质不用于安卓市场的产品,那基本上公司就可走注销流程了。

于公来讲,用公司账号是明牌,更容易被人盯上。比如说名称侵权,字体侵权。公对公的官司,代理律师就可以搞定,即使是无心之举也不得不割地赔款

所以,爱惜好自己手里的账号,做一名干净合规的开发者。

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

相关推荐

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

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

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

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

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

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

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

脱离 SwiftUI 也能用 @Observable:深入理解 withObservationTracking 的玩法、坑点与 Swift 6 突围

前言

iOS 17 引入的 @Observable 宏让 SwiftUI 刷新机制大变天,但官方文档只告诉你“在 View 里用就行”。

如果我们想在 非 SwiftUI 场景(比如 NetworkLayer、ViewModel、Unit Test)里监听属性变化,就只能靠 withObservationTracking

@Observable 是什么

特性 @Observable ObservableObject + @Published
适用系统 iOS 17+ iOS 13+
刷新粒度 属性级(仅变更的属性触发视图更新) 对象级(整个 ObjectWillChange 触发更新)
依赖协议 无需额外协议 必须继承 ObservableObject 协议
非 SwiftUI 监听 使用 withObservationTracking 闭包 使用 objectWillChange 发布者(Publisher)

一句话: @Observable 是 Swift 5.9 宏加持的“轻量级可观测对象”,专为 SwiftUI 优化,但 宏本身不限制使用场景,所以我们可以在任意线程/任意模块里手动订阅。

withObservationTracking 原理解剖

函数签名(简化):

func withObservationTracking<T>(
  _ apply: () -> T,          // ① 访问属性 → 被记录
  onChange: @autoclosure () -> @Sendable () -> Void  // ② 属性“即将”变时回调
) -> T

关键行为:

  1. apply 闭包里访问到的任何被 @Observable 标记类的存储属性,都会被加入“本次观测清单”。
  2. 当清单里任意属性发生 willSet 时,系统会执行 onChange;
  3. onChange 只会被触发一次,之后如果想继续监听,必须手动重新调用 withObservationTracking,即“递归订阅”。

最小可运行示例

import Observation

// 1. 声明被观测的模型
@Observable
class Counter {
    var count = 0
}

// 2. 声明监听者
class CounterObserver {
    let counter: Counter
    
    init(counter: Counter) {
        self.counter = counter
    }
    
    /// 开始监听,打印最新值
    func observe() {
        // ① apply 闭包:读取属性 → 被系统记录
        // ② onChange:count 即将改变时调用(旧值仍可见)
        withObservationTracking {
            print("当前计数:\(counter.count)")
        } onChange: { [weak self] in
            // 系统只给一次通知,想持续监听必须重新调用 observe()
            print("检测到变化,重新订阅")
            self?.observe()
        }
    }
}

// 3. 客户端代码
let counter = Counter()
let observer = CounterObserver(counter: counter)

// 启动监听
observer.observe()

// 制造变化
counter.count = 1   // 控制台:检测到变化,重新订阅 → 当前计数:1
counter.count = 2   // 同上

坑点 1:onChange 给出的是“旧值”

因为 onChange 触发在 willSet 阶段,所以闭包里再读属性仍是老数据。

解决思路:把读取动作推迟到下一个 RunLoop → 拿到 didSet 之后的新值。

func observe() {
    withObservationTracking {
        print("RunLoop 前读取 → 旧值:\(counter.count)")
    } onChange: { [weak self] in
        // 关键:异步到下一轮
        DispatchQueue.main.async {
            print("RunLoop 后读取 → 新值:\(self?.counter.count ?? -1)")
            self?.observe() // 继续监听
        }
    }
}

坑点 2:模板代码太多 → 二次封装

每次手写“递归 + 异步”太烦,可以提炼成一个可重用的泛型助手:

import Observation

/// 让任意闭包“持续”被监听,自动 re-subscribe
/// - Parameter execute: 需要跟踪属性的读取闭包
public func keepObserving<T: AnyObject>(
    target: T,
    execute: @escaping @Sendable () -> Void
) {
    Observation.withObservationTracking {
        execute()
    } onChange: {
        DispatchQueue.main.async {
            keepObserving(target: target, execute: execute)
        }
    }
}

使用姿势:

class CounterObserver {
    let counter: Counter
    init(counter: Counter) { self.counter = counter }
    
    func start() {
        // 捕获 [weak target] 防止循环引用
        keepObserving(target: self) { [weak self] in
            guard let self else { return }
            print("封装后读取:\(self.counter.count)")
        }
    }
}

坑点 3:Swift 6 语言模式 + Sendable

开启 Swift 6 后,编译器会报错:

Capture of 'self' with non-sendable type 'CounterObserver?' in a `@Sendable` closure

原因:

withObservationTracking 的 onChange 要求 @Sendable,意味着闭包里不能捕获“非 Sendable”的引用类型。

@Observable 类默认包含可变状态,无法自动符合 Sendable;如果我们把 Observer 标成 @MainActor,又会触发“MainActor隔离属性不能出现在 Sendable 闭包”的新错误。

折中方案

  1. 把 Observer 整体标为 @MainActor
  2. 在闭包内部再包一层 Task { @MainActor in ... } 异步读取;
  3. 或者 给 Observer 打上 @unchecked Sendable 并自行保证线程安全(例如全部属性都通过 DispatchQueue 或 Actor 同步)。

示例:@MainActor 内再开 Task

@MainActor
class CounterObserver: Sendable /* 手动保证 */ {
    init(counter: Counter) {
        self.counter = counter
    }
    
    let counter: Counter
    
    nonisolated func start() {
        Observation.withObservationTracking { [weak self] in
            // 这里不能直接接触 counter,因为它被隔离在 @MainActor
            // 只记录“我关心”的事实,不读取值
            return () // 仅触发跟踪
        } onChange: { [weak self] in
            Task { @MainActor in
                guard let self else { return }
                print("Swift6 模式新值:\(self.counter.count)")
                self.start() // 继续
            }
        }
    }
}

注意:由于 apply 闭包不能跨 Actor 读取,我们只能“空跑”跟踪,再在 Task 里安全取值。

这会导致 一次 onChange 只能异步拿到值,且如果属性变化非常快,中间事件可能丢失。

在 Swift 6 严苛模式下,Combine 的 @Published 仍是更成熟的答案。

完整可落地模板

//
//  ObservableUtils.swift
//  用前导入 Observation 模块
//

import Foundation
import Observation

/// 线程安全且支持 Swift 6 的“属性监听”工具箱
public actor ObservableListener {
    
    private var token: (() -> Void)? // 用于将来做手动取消
    
    /// 持续监听目标对象指定 KeyPath 的新值
    /// - Parameters:
    ///   - object: 被 @Observable 标记的实例
    ///   - keyPath: 要读取的 KeyPath
    ///   - handler: 变化后异步回调新值
    public func keep<T: Any, V>(
        object: T,
        keyPath: KeyPath<T, V>,
        handler: @escaping (V) -> Void
    ) where T: Observable {
        // 用 Task 保证与 actor 隔离
        Task {
            Observation.withObservationTracking {
                // 空读取,只为登记依赖
                _ = object[keyPath: keyPath]
            } onChange: { [weak object] in
                guard let object else { return }
                Task { @MainActor in
                    // 下一轮 RunLoop 拿到新值
                    handler(object[keyPath: keyPath])
                }
                // 继续监听
                self.keep(object: object, keyPath: keyPath, handler: handler)
            }
        }
    }
}

// ====== 使用示例 ======
@Observable
class User {
    var name = "Tom"
    var age  = 18
}

let listener = ObservableListener()
let user = User()

Task {
    await listener.keep(object: user, keyPath: \.name) { newName in
        print("用户名已变更为:\(newName)")
    }
}

user.name = "Jerry"   // → 用户名已变更为:Jerry
user.name = "Spike"   // → 用户名已变更为:Spike

总结与思考

  1. 宏 ≠ 魔法

    @Observable 只是帮你自动生成 ObservationRegistrar 代码,真正的订阅逻辑仍依赖 withObservationTracking

  2. willSet 语义是最大绊脚石

    这意味着它更适合“触发刷新”而不是“精确拿到新值”。如果你必须依赖“每一次新值”,要么异步到下一轮,要么回到 Combine。

  3. 递归订阅是官方默许的“官方模式”

    别嫌它丑,目前 API 就是这样设计的;封装后可以让调用方无感。

  4. Swift 6 的 Sendable 检查让“跨 Actor 读取”几乎无解

    除非苹果将来放宽 withObservationTracking@Sendable 要求,否则在严苛并发场景下,Combine/AsyncSequence 才是更稳妥的事件源。

  5. 适用场景推荐

    • ✅ 局部刷新:日志面板、调试计数器、调试 UI。
    • ✅ SwiftUI 外部但主线程内:Widget 的 Timeline 生成、Preview 更新。
    • ⚠️ 高吞吐实时数据:音频采样、传感器 120Hz 上报 → 建议 Combine + 环形缓冲区。
    • ❌ 需要线程跳变/Actor 隔离:Swift 6 模式下成本

学习资料

  1. www.donnywals.com/observing-p…

iOS 知识点 - Category / Extension / Protocol 小合集

谈到 OC 基础,错不开的三种机制:Category / Extension / Protocol

它们分别解决了:

  • Category:不修改类源代码、不继承的前提下,给已有类 “添加方法”(组织文件、系统类加功能、AOP 风格 hook 等)。
  • Extention: 在实现文件里补充声明 私有 属性、实例变量。
  • Protocol: 只定义 “接口规范”(方法/属性的声明),不提供实现,用于解耦(代码只依赖协议,不依赖具体类)& 多态(不同类实现同一协议,都可赋给协议限定类型id<Protocol>

Category

  • 概念:category 是一种给已有类(包括系统类)增加实例方法/类方法的机制,不需要子类化,也不需要访问原类源码。

  • 限制

    • 不能直接增加新实例变量(ivar),但是可以通过 关联对象 间接添加 “类似属性” 的存储。
        #import <objc/runtime.h>
    
        static const void *kNameKey = &kNameKey;
    
        @implementation NSObject (Name)
    
        - (void)setName:(NSString *)name {
            objc_setAssociatedObject(self, kNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
        }
    
        - (NSString *)name {
            return objc_getAssociatedObject(self, kNameKey);
        }
        
        @end
    
    • 不能直接访问原类 pivate 类型的变量/方法(同子类),必须要原类在 .h 中公开声明。
  • 编译后的本质category 在编译后,额外的方法会被编译器 “合并” 到原类的 method_list 中,runtime 加载类时一起注册。

    • 简化过程
      1. 编译:每个 .m 中的 @implementation ClassName (CategoryName) 生成一个 category_t 结构,其中包含:
        • class name
        • instance methods list
        • class methods list
        • protocol list
      2. 程序加载:runtimeload_images 时遍历所有 category
        • 找到对应 class
        • 把 category 的 methods_list 插入到 class 的 methods_list 列表前方。(同名方法覆盖原来的实现)
  • ⚠️注意事项

    • 如果分类与原类(或其他分类)有同名方法,后加载的 method 会 覆盖前面的实现(行为依赖于加载顺序)
  • 经典用途

    • 给类添加方法(NSArray+Utils.h 等)
    • 拆分类的实现,按照功能分块(常用于 swift 代码风格)
    • 方法交换(日志、埋点、hook 等)

runtime 讲解篇:juejin.cn/post/757172…

延伸名词 runtimeload_images(面向切面编程)

  • 概念load_images 是 runtime 的一个内部函数,在 dyld 把一个新的 Mach-O image (主程序 / 动态库 / 插件) 加载进来时,会回调 runtime 的 map_images / load_images 这一整套流程。

  • 接口作用

    • 注册 image 里的 类列表、分类列表、协议列表、选择子 等;
    • 把 category 的 方法/协议 挂载到对应的类上;
    • 收集并按照一定顺序调用 +load 方法(先类,后分类)。

你也可以简化理解成:每当一块儿新的二进制文件被载入进程,runtime 就用 load_images 把这块儿里的 oc 元数据接入到系统里。

延伸名词 AOP(面向切面编程)

含义:一种编程思想,能够在「不改动原有代码逻辑」的前提下,在指定的“切面点”上插入额外逻辑(如埋点、日志、监控、权限校验等)。

核心概念 含义
切点(Pointcut) 想“切入”的位置,比如方法调用前/后
通知(Advice) 在切点执行的额外逻辑(before、after、around)
切面(Aspect) 切点 + 通知 的组合
织入(Weaving) 把这些逻辑动态插入代码执行流程的过程

iOS 实现 AOP 的方法

  1. 方法交换:在交换方法中实现新的逻辑
  2. 消息转发:利用 forwardInvocation:resolveInstanceMethod: 在运行时拦截消息,再“转发”到自己的处理逻辑。
  3. 三方库:Aspects

Extension

  • 概念: 在类的实现被编译前,给它再“补充”一些

    • 方法声明
    • 属性声明
    • 额外 ivar(实例变量)
  • ⚠️注意事项:

    • Extension 必须和 @implementation MyClass 在同一个编译单元或可见范围,这样编译器才会把它当成类定义的一部分,生成 ivar 和访问器方法 (setter/getter)。

Protocol

  • 概念:接口规范与解耦,只写签名、不写实现。
  • 可以包含
    • 实例/类方法
    • Property 声明。

@required@optional 的意义和成本

编译期行为

  • @required:类遵循了这个协议,但是没实现 required 方法 → 编译器 warning
  • @optional:完全由你自己决定是否实现,编译器不强制。

运行时行为

  • 协议本身只是一堆元数据(protocol_t),runtime 存储了:
    • 协议有哪些 @required@optional 的方法;
    • 哪些是实例 / 类方法;
    • 哪些是 property。
  • 但是消息派发是不看协议的:
    • 派发时只看这个对象的方法列表里有没有该 selector;
    • 至于这个 selector 来自哪个协议、是否声明在协议里,派发阶段都不关系。
    /// 因此,在调用时常配合:
    if ([self.delegate respondsToSelector:@selector(doOptionalThing)]) {
        [self.delegate doOptionalThing];
    }
    

协议里的 property 到底是什么?

前置知识:协议只写签名,不写实现。

  • 实际上在协议中声明的 @property (nonatomic, copy) NSString *name; 仅仅等价于 setter、getter 的声明(无实现、无 ivar)。
- (NSString *)name;
- (void)setName:(NSString *)name;

protocol 经典用途

代理模式

  • 例如:vc 处理在 view 上的交互事件,view 通过代理调用 vc 实际处理事件的方法。
/// View.h
@interface View : UIView

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

@end

/// View.m
- (void)buttonTapped {
    if ([self.delegate respondsToSelector:@selector(didClickButton:)]) {
        [self.delegate didClickButton:self];
    }
}

/// ViewController.m
@interface ViewController () <MyViewDelegate>
@end

@implementation ViewController

#pragma mark - MyViewDelegate
- (void)didClickButton:(MyView *)view {
    // 处理事件
}
  • 这样就做到了:解耦 + 多态 + 依赖倒置

模块/组件之间的接口抽象 类型约束/API设计

protocol vs 继承

协议 继承
本质 接口集合,不带实现、不带存储 类型扩展机制,带实现、带存储
语义关系 描述 “能做” 某些事情 描述 “就是” 某种事物
数量 一个类可实现 个协议 一个类只能有 个直接父类(多继承例外)
主要用途 解耦、抽象、多态 复用、建立层次结构
编译期检查 检查是否实现 required 方法(warning) 检查 override 签名、类型转换等
运行时检查 conformsToProtocol:
 class_conformsToProtocol
isKindOfClass:
 isMemberOfClass:

延伸知识点 isKindOfClass:isMemberOfClass:

  • isKindOfClass:: 是不是这个类或它的子类
- (BOOL)isKindOfClass:(Class)aClass {
    for (Class c = object_getClass(self); c; c = class_getSuperclass(c)) {
        if (c == aClass) return YES;
    }
    return NO;
}
  • isMemberOfClass:: 是不是这个类本身(不包括子类)
- (BOOL)isMemberOfClass:(Class)aClass {
    return object_getClass(self) == aClass;
}

对于实例对象(instance)

@interface Animal : NSObject
@end

@interface Dog : Animal
@end

Animal *a = [Animal new];
Dog    *d = [Dog new];

// isKindOfClass:
[a isKindOfClass:[Animal class]]; // YES  (Animal 本身)
[a isKindOfClass:[Dog class]];    // NO   (Animal 不是 Dog 家族)

[d isKindOfClass:[Animal class]]; // YES  (Dog 是 Animal 的子类,被认为是 Animal 家族的一员)
[d isKindOfClass:[Dog class]];    // YES  (Dog 本身)

// isMemberOfClass:
[a isMemberOfClass:[Animal class]]; // YES  (a 的 class 恰好是 Animal)
[a isMemberOfClass:[Dog class]];    // NO   (class 是 Animal,不是 Dog)

[d isMemberOfClass:[Animal class]]; // NO   (class 是 Dog,不是 Animal)
[d isMemberOfClass:[Dog class]];    // YES  (class 恰好是 Dog)

对于类对象(class)

/// ❌错误的用法
// [Animal isKindOfClass:Animal]; // 左侧的 “Animal” 被 oc 语法解释为消息接受者,右侧的 “Animal” 会被认作 “类型名”,编译器报错参数异常。
// 

// ✅正确的用法
[Animal isSubclassOfClass:[Animal class]]; // YES (正经用法)
[Animal isKindOfClass:[Animal class]];     // NO  (不正经用法)
  • [Animal isKindOfClass:[Animal class]]; 为什么输出 NO?
    • 首先,在 runtime 讲解中已知,类本身也是对象,类型为 Class
    • 再结合 isKindOfClass: 方法的实现:
      1. object_getClass(self) 开始循环向上查询
      2. self 是类对象本身(class object), 首次查询的结果就是 metaClass object;
      3. class object != metaClass object,已经错过了类对象本身,因此返回 NO。
❌