从英文的角度认识iOS开发中的proxy

117 阅读7分钟

proxy:代理权,代表权;代理人,代表;(测算用的)代替物,指标;投票委托书

proxy和delegate两个英文单词的区别:

  • proxy源自拉丁语"procuratio",有"照料事务"的含义,重点在"代替行动"
  • delegate来自"delegare",强调"授权派遣",有任务转交的意味

举个🌰:比如你有其他要忙不能参会怎么办?

方案A:让同事替你发言(proxy)

方案B:把议题交给同事负责(delegate)

举个🌰:

  1. 总统选举中投票给代表 → proxy

  2. 经理把项目交给下属负责 → delegate

    总结:

  • proxy = 替代出席
  • delegate = 分配任务

接下来看个proxy单词的使用


// 自定义一个ViewModifier来获取视图尺寸
struct SizeInfoModifier: ViewModifier {
    @Binding var size: CGSize
    func body(content: Content) -> some View {
        content
            .background(
                GeometryReader { proxy in
                    Color.clear
                        .task(id: proxy.size) {
                            size = proxy.size
                        }
                }
            )
    }
}
extension View {
    func sizeInfo(_ size: Binding<CGSize>) -> some View {
        self
            .modifier(SizeInfoModifier(size: size))
    }
}

//今天我们重点看其中GeometryReader的init方法声明, 参数content是一个闭包,其参数的类型为GeometryProxy
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen public struct GeometryReader<Content> : View where Content : View {
    public var content: (GeometryProxy) -> Content
    @inlinable public init(@ViewBuilder content: @escaping (GeometryProxy) -> Content)
    /// The type of view representing the body of this view.
    ///
    /// When you create a custom view, Swift infers this type from your
    /// implementation of the required ``View/body-swift.property`` property.
    @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
    public typealias Body = Never
}

这里的GeometryProxy应当就是代理的意思,我们来看下GeometryProxy的定义,可以看出它保存了一些size,insets等信息`

/// A proxy for access to the size and coordinate space (for anchor resolution)
/// of the container view.
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public struct GeometryProxy {
    /// The size of the container view.
    public var size: CGSize { get }
    /// Resolves the value of an anchor to the container view.
    public subscript<T>(anchor: Anchor<T>) -> T { get }
    /// The safe area inset of the container view.
    public var safeAreaInsets: EdgeInsets { get }
    /// Returns the container view's bounds rectangle, converted to a defined
    /// coordinate space.
    @available(iOS, introduced: 13.0, deprecated: 100000.0, message: "use overload that accepts a CoordinateSpaceProtocol instead")
    @available(macOS, introduced: 10.15, deprecated: 100000.0, message: "use overload that accepts a CoordinateSpaceProtocol instead")
    @available(tvOS, introduced: 13.0, deprecated: 100000.0, message: "use overload that accepts a CoordinateSpaceProtocol instead")
    @available(watchOS, introduced: 6.0, deprecated: 100000.0, message: "use overload that accepts a CoordinateSpaceProtocol instead")
    @available(visionOS, introduced: 1.0, deprecated: 100000.0, message: "use overload that accepts a CoordinateSpaceProtocol instead")
    public func frame(in coordinateSpace: CoordinateSpace) -> CGRect
}

为了更好的理解GeometryProxy,我们先来看GeometryReader,它是一个容器视图(View),它允许你访问其父视图建议的尺寸(size)和坐标空间(coordinate space),GeometryReader会填满父视图给它的所有空间。

GeometryReader的使用情景都是需要根据可用空间来调整子视图的布局,比如:

  • 让一个视图占据父视图的一半宽度

  • 根据父视图的高度调整字体大小

  • 按比例绘制图形:比如绘制一个宽高比为16:9的矩形

  • 根据容器尺寸调整布局:比如横竖屏切换时改变排列方式

  • 获取安全区域:例如让内容避开刘海区域

  • 实现其他自定义的布局效果

GeometryReader的初始化器要求我们传递一个闭包,这个闭包接收一个参数:GeometryProxy。GeometryProxy是一个结构体,它提供了以下信息:

  • size: CGSize:GeometryReader本身被分配的大小(即父视图给它的空间大小)

  • safeAreaInsets: EdgeInsets:安全区域的边距

  • frame(in: CoordinateSpace)->CGRect:可以获取GeometryReader在不同坐标空间中的frame

想象你有一个盒子📦,但不知道盒子有多大。GeometryReader 就是帮你测量这个盒子的工具,然后把测量结果(尺寸、位置)告诉盒子里的内容。我们来看下基本的流程:图片

  1. 父视图告诉 GeometryReader:“你可以用这么大的空间”(比如 300x200)
  2. GeometryReader 测量这个空间,把结果包装成 GeometryProxy类型的结构体变量:geometry
  3. 把这个测量结果 geometry 传递给你的内容闭包
  4. 你的视图根据 geometry.size 等数据决定如何显示
HStack {
        GeometryReader { p in
            Text("w = \(p.size.width), h = \(p.size.height)")
        } // 会挤占右边文本的空间
        Text("说明文字")
    }
    //GeometryReader 会吃掉尽可能多的空间

GeometryProxy不叫做GeometryDelegate?

GeometryProxy是一个包含了布局信息的不可变结构体(struct),它提供的是当前容器空间的信息快照(如尺寸、安全区域、坐标转换等)。

它并不具备委托模式中常见的回调功能(比如事件回调),而仅仅是提供数据(测量信息)。因此,它更符合“代理”的含义——作为空间信息的替身(Proxy),而不是委托(Delegate)模式中的委托对象。

接下来看下GeometryReader的弊端

  1. GeometryReader默认会扩张到所有可用空间(类似Color.clear),如果不想占满空间,需要用.frame等修饰符限制。

  2. 频繁使用GeometryReader(尤其是在复杂布局中)可能会影响性能,因为它会在每次布局时重新计算。

  3. 避免多层嵌套GeometryReader,这可能导致布局计算复杂化。

接下来看另一种Proxy:NSProxy

它是Objective-C中一个实现了NSObject协议的代理类,也是一个抽象类,必须继承实例化其子类才能使用,NSProxy 是 iOS/macOS 开发中唯一不继承自 NSObject 的根类,专门为「消息转发」而生。

想象这个场景:你需要一个「万能秘书」:

  • 客户找秘书问技术问题 👉 秘书转给工程师
  • 客户找秘书问合同问题 👉 秘书转给法务
  • 秘书自己啥都不懂,但能精准路由问题
// 1. 方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
// 2. 消息转发
- (void)forwardInvocation:(NSInvocation *)invocation;
// 3. 备用接收者,这个在NSProxy的定义中被注释掉了,NSProxy不使用这个步骤
- (id)forwardingTargetForSelector:(SEL)aSelector;

相比NSObject类来说NSProxy更轻量级,通过NSProxy可以帮助Objective-C间接的实现多重继承的功能。NSObject协议中的属性和方法:

@property (readonly) NSUInteger hash;
@property (readonly) Class superclass;
- (Class)class;
- (BOOL)isEqual:(id)object;
- (instancetype)self;
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
- (BOOL)isProxy;  //是否是NSProxy的实例
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;

除了NSObject协议,其他所有的协议都必须得遵守NSObject协议。这是为什么呢?如果不遵守会怎样?

会崩溃,会报"unrecognized selector sent to instance 0x15d78700"错误。因为代理通常会调用NSObject协议里的方法performSelector。声明代理的时候是id类型的,不确定是NSObject的实例,所以不能调用NSObject协议里的方法。

关于NSProxy的妙用,可以看:juejin.cn/post/698210…

为什么说NSProxy是一个抽象类呢?为什么不把nsproxy定义为一个protocol,而是一个抽象类呢?

Java、C++ 等 OOP 语言有一个抽象类的概念,即一个类实现了部分方法,另一部分的方法必须由继承它的子类来实现。Objective-C 在设计上没有这个概念,转而提供了用途类似的协议,除了不能给方法加默认实现以外,与抽象类的用法大体相同。但是在实际项目中,让一个协议实现一些共通的方法还是很有必要的,比如很多类都遵守了某一个协议,而这个协议中某一个方法的实现大体上都一样的时候,在每一个子类内部都 copy 一份同样的代码就不太合适了。

另外当NSProxy被设计时,Objective-C 没有正式 Protocol 概念(Protocol 在1988年草案中才出现)

NSProxy 需要特殊运行时处理,跳过消息发送,直接进入消息转发流程

为什么NSProxy不叫做NSDelegate呢?

An abstract superclass defining an API for objects that act as stand-ins for other objects or for objects that don’t exist yet.

developer.apple.com/documentati…

stand-ins在这里就是“替身”的意思:

  • 完全透明的转发

  • 无中间层的替身

  • 一对一 的代理关系

对应设计模式:

Proxy 模式
“为其他对象提供一种代理以控制对这个对象的访问” - 同上文中访问size的GeometryProxy一曲同工

Delegate 模式 (实际是 Strategy 变体)
“将算法/行为委托给辅助对象”

NSProxy 为什么不会走消息发送,直接进行消息转发的第三步?

当执行 [proxy someMethod] 时,底层转换为:

objc_msgSend(proxy, @selector(someMethod))

普通对象查找链图片 NSProxy: 图片

还记得NSObject协议中的这个方法吗,它的作用显而易见,就是用来判断消息发送和转发应当选择上述哪个流程

- (BOOL)isProxy;  //是否是NSProxy的实例

关注我,从另一个角度认识iOS开发