[iOS] 关于 self = [super init];

本文详细解析了Objective-C中使用self=[super init]的原因及应用场景,包括单件、类簇、共享资源和父类初始化可能释放当前对象等情况,并提供了避免父类改变对象内存地址导致self指针失效的建议。

先看一下官方文档给出的初始化示例代码:

- (id)init {
    self = [super init];  // Call a designated initializer here.
    if (self != nil) {
          // 省略其他细节
    }
    return self;
}

容易让人困惑的地方在于,将父类初始化之后,将其返回的对象指针覆盖当前对象的指针。

这种方式令人费解,目前暂时找不到官方解释这么做的原因。

官方文档 有解释。

我们先分以下几种情况分别分析:(假设superSelf是[super init]的返回值)

1 superSelf == nil

     此时父类初始化失败,self随之被赋值为nil并返回,表现正常。

2 superSelf == self

     大部分类的初始化都是这个结果。此时赋值没有任何影响。 

3 superSelf != self

     这种情况正是大部分人疑惑的地方。执行self = [super init]之后,

     我们创建的对象将会被重定向到另外一块内存上。接下来重点解释这种情况。


首先,出现父类指针跟子类指针不一样的情况其实就是父类的init方法返回的对象跟原先创建的对象不一样,分为以下几种:

1 单件。

     此时如果执行self = [super init]将使所有子类指向这个单件的内存。

     这不仅使所有子类互相修改数据,甚至访问子类自己增加的变量的时候,可能会崩溃。

     建议不要继承单件、或者保证单件的子类也是单件。

2 ClassClusters(类簇),初始化方法返回了不同的子类。

     以下是对象初始化后,返回不同的子类的例子:

NSString *str1 = [NSString alloc];
NSString *str2 = [str1 initWithString:@"hello"];

上面的str1和str2是不一样的对象,存在于不同的内存块中,在GNUStep里面,str1是GSPlaceholderString对象,str2是GSCInlineString对象。

3 共享。

     先看例子:

NSNumber *n1 = [[NSNumber alloc] initWithInt:1];
NSNumber *n2 = [[NSNumber alloc] initWithInt:1];

以上的n1和n2,指向了同一块内存!

由于NSNumber是创建之后就不能修改的对象,所以Foundation在这里做了一些优化,相同数值的NSNumber对象将共享同一块内存。

4 父类可能在初始化中释放了当前的对象并创建了新的内存区域。

这时,子类需要将self指向新的内存区域才能正常工作。所以一定要执行self = [super init];


总结:

在初始化方法中使用self = [super init]语句是Objective-C的标准做法。

一般情况下都要用以上语句来防止父类改变对象的内存地址导致self指针指向无效内存。

在父类是单件、类簇或者有共享资源的时候,必须依照实际情况考虑是否加上这行代码。

总之,当需要继承父类的时候,调用父类的init之前,必须知道父类的init方法的工作方式。

在 `TPBPopover` 的初始化方法 `initWithContentViewController:` 中,传入的 `NSViewController` 参数(`contentViewController`)代表 **弹出窗口的内容控制器**,其作用是为弹出窗口提供动态内容视图和逻辑管理。以下是详细解析: --- ### **1. `contentViewController` 的核心作用** #### **(1) 提供内容视图 (`view`)** - `NSViewController` 的 `view` 属性会自动作为弹出窗口的根视图。 - 无需手动创建 `NSView`,控制器会管理其视图的生命周期(如加载、卸载)。 #### **(2) 动态内容更新** - 通过控制器可以动态更新弹出窗口的内容(例如切换子视图、响应数据变化)。 - 示例:在控制器中添加按钮点击事件: ```objectivec // ContentViewController.m - (void)viewDidLoad { [super viewDidLoad]; NSButton *button = [[NSButton alloc] initWithFrame:NSMakeRect(10, 10, 80, 30)]; button.title = @"Click"; button.target = self; button.action = @selector(buttonClicked); [self.view addSubview:button]; } - (void)buttonClicked { NSLog(@"Button tapped in popover!"); } ``` #### **(3) 生命周期管理** - 当弹出窗口关闭时,`TPBPopover` 可能会释放 `contentViewController`(需确认内部实现是否强引用)。 - 可在控制器中实现 `viewWillAppear`/`viewDidDisappear` 等方法处理显示/隐藏逻辑。 --- ### **2. 与 `initWithContentView:` 的区别** | 方法 | 适用场景 | 优势 | |-------------------------------|----------------------------------|-------------------------------| | `initWithContentViewController:` | 需要动态内容或复杂交互的弹出窗口 | 自动管理视图生命周期,支持 MVC | | `initWithContentView:` | 静态内容或简单布局 | 轻量级,直接操作视图 | --- ### **3. 实际应用示例** #### **(1) 创建自定义内容控制器** ```objectivec // PopoverContentViewController.h @interface PopoverContentViewController : NSViewController @end // PopoverContentViewController.m @implementation PopoverContentViewController - (void)loadView { self.view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 200, 150)]; self.view.wantsLayer = YES; self.view.layer.backgroundColor = [NSColor lightGrayColor].CGColor; } - (void)viewDidLoad { [super viewDidLoad]; NSTextField *label = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 10, 180, 30)]; label.stringValue = @"Controller-based Popover"; label.bezeled = NO; label.editable = NO; [self.view addSubview:label]; } @end ``` #### **(2) 在 `TPBPopover` 中使用** ```objectivec PopoverContentViewController *vc = [[PopoverContentViewController alloc] init]; TPBPopover *popover = [[TPBPopover alloc] initWithContentViewController:vc]; popover.closesWhenOtherWindowClicked = YES; // 显示弹出窗口 [popover showInView:triggerButton positionStyle:popover.style space:5]; ``` --- ### **4. 关键注意事项** #### **(1) 视图尺寸控制** - 控制器的 `view` 尺寸需在初始化时明确(通过 `frame` 或自动布局)。 - 如果视图为空,可能导致弹出窗口显示异常。 #### **(2) 内存管理** - 默认情况下,`TPBPopover` 可能不会强引用 `contentViewController`,需确保控制器在显示期间不被释放: ```objectivec // 持有控制器的引用 @property (nonatomic, strong) PopoverContentViewController *popoverVC; // 初始化时赋值 self.popoverVC = [[PopoverContentViewController alloc] init]; TPBPopover *popover = [[TPBPopover alloc] initWithContentViewController:self.popoverVC]; ``` #### **(3) 事件传递** - 如果弹出窗口需要响应键盘/鼠标事件,确保控制器的 `view` 或子视图已正确设置 `nextResponder`。 --- ### **5. 为么使用 `NSViewController` 而非直接传 `NSView`?** 1. **解耦逻辑与视图**: 将业务逻辑(如按钮点击、数据加载)封装在控制器中,而非直接操作视图。 2. **支持复杂交互**: 例如通过控制器管理多个子视图、表单验证等。 3. **与 AppKit 生态一致**: macOS 开发中,`NSViewController` 是标准的内容管理单元(类似 iOS 的 `UIViewController`)。 --- ### **6. 常见问题解决** #### **Q1: 弹出窗口内容未显示** - 检查控制器的 `view` 是否非空且尺寸正确。 - 确认 `TPBPopover` 是否正确调用了控制器的 `view` 属性。 #### **Q2: 控制器方法未触发** - 确保控制器未被提前释放(如未被强引用)。 - 检查 `viewDidLoad` 等生命周期方法是否被覆盖。 #### **Q3: 如何更新弹出窗口内容?** - 直接修改控制器的 `view` 或其子视图: ```objectivec // 动态添加子视图 [self.popoverVC.view addSubview:newSubview]; ``` --- ### **7. 总结** - **`contentViewController`** 是弹出窗口的内容管理者,提供动态视图和交互逻辑。 - **优势**:适合复杂内容场景,支持 MVC 模式。 - **关键点**: - 确保控制器生命周期与弹出窗口一致。 - 通过控制器的 `view` 属性管理布局。 - 在 Swift 中使用时,注意桥接后的内存管理(如 `unowned`/`weak` 引用)。 通过这种方式,可以更灵活地控制弹出窗口的行为和内容,同时保持代码的可维护性。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值