proxy:代理权,代表权;代理人,代表;(测算用的)代替物,指标;投票委托书
proxy和delegate两个英文单词的区别:
- proxy源自拉丁语"procuratio",有"照料事务"的含义,重点在"代替行动"
- delegate来自"delegare",强调"授权派遣",有任务转交的意味
举个🌰:比如你有其他要忙不能参会怎么办?
方案A:让同事替你发言(proxy)
方案B:把议题交给同事负责(delegate)
举个🌰:
-
总统选举中投票给代表 → proxy
-
经理把项目交给下属负责 → 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 就是帮你测量这个盒子的工具,然后把测量结果(尺寸、位置)告诉盒子里的内容。我们来看下基本的流程:
- 父视图告诉
GeometryReader:“你可以用这么大的空间”(比如 300x200) GeometryReader测量这个空间,把结果包装成GeometryProxy类型的结构体变量:geometry- 把这个测量结果
geometry传递给你的内容闭包 - 你的视图根据
geometry.size等数据决定如何显示
HStack {
GeometryReader { p in
Text("w = \(p.size.width), h = \(p.size.height)")
} // 会挤占右边文本的空间
Text("说明文字")
}
//GeometryReader 会吃掉尽可能多的空间
GeometryProxy不叫做GeometryDelegate?
GeometryProxy是一个包含了布局信息的不可变结构体(struct),它提供的是当前容器空间的信息快照(如尺寸、安全区域、坐标转换等)。
它并不具备委托模式中常见的回调功能(比如事件回调),而仅仅是提供数据(测量信息)。因此,它更符合“代理”的含义——作为空间信息的替身(Proxy),而不是委托(Delegate)模式中的委托对象。
接下来看下GeometryReader的弊端
-
GeometryReader默认会扩张到所有可用空间(类似Color.clear),如果不想占满空间,需要用.frame等修饰符限制。
-
频繁使用GeometryReader(尤其是在复杂布局中)可能会影响性能,因为它会在每次布局时重新计算。
-
避免多层嵌套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.
stand-ins在这里就是“替身”的意思:
-
完全透明的转发
-
无中间层的替身
-
一对一 的代理关系
对应设计模式:
Proxy 模式
“为其他对象提供一种代理以控制对这个对象的访问” - 同上文中访问size的GeometryProxy一曲同工
Delegate 模式 (实际是 Strategy 变体)
“将算法/行为委托给辅助对象”
NSProxy 为什么不会走消息发送,直接进行消息转发的第三步?
当执行 [proxy someMethod] 时,底层转换为:
objc_msgSend(proxy, @selector(someMethod))
普通对象查找链:
NSProxy:
还记得NSObject协议中的这个方法吗,它的作用显而易见,就是用来判断消息发送和转发应当选择上述哪个流程
- (BOOL)isProxy; //是否是NSProxy的实例
关注我,从另一个角度认识iOS开发