self.navigationcontroller是nil吗?

本文探讨了iOS应用中导航控制器的工作原理及其与视图控制器的关系。通过具体实例解释了self.navigationItem和self.navigationController的作用及其实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有这个疑惑是因为在看别人的代码时遇到的一个疑问。
具体的代码是下面这样的。

/**
 * IoTAbout.m
 *
 * Copyright (c) 2014~2015 Xtreme Programming Group, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#import "IoTAbout.h"

@interface IoTAbout ()

@property (weak, nonatomic) IBOutlet UIImageView *imageAbout;

@end

@implementation IoTAbout

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    self.navigationItem.title = @"关于";
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"return_icon"] style:UIBarButtonItemStylePlain target:self action:@selector(onBack)];

    CGSize size = [UIApplication sharedApplication].keyWindow.frame.size;

    //iphone4s
    if(size.width == 320 && size.height <= 480)
    {
        self.imageAbout.image = [UIImage imageNamed:@"about.png"];
    }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)onBack {
    [self.navigationController popViewControllerAnimated:YES];
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

这段代码是从机智云的开源app中找到的,它的主要功能是显示平常我们看到的“关于”这样的一个页面。在看这页面的代码时突然发现,在方法viewDidLoad()中竟然就直接给self.navigationItem 以及onback()中的self.navigationController 赋值和调用方法了。很奇怪,是在哪里给这两个指针附上对象的?
我曾尝试使用ViewController的presention的方法把上面的页面显示出来,但是失败了。首页显示是正常的,当点击按钮跳转后就一直黑屏了,没有期望的界面出现。然后就只好去搜了,关键字就是self.navigationController,于是搜到了一篇文章,里面说只有当把这个页面放入Navigation的stack之后,self.navigationController等指针才不为空,于是就是试了一下,还真是,果然如此。
下面上源码:

//
//  SecondViewController.h
//  test-navigation
//
//  Created by ChrisBluez on 9/27/16.
//  Copyright © 2016 ChrisBluez. All rights reserved.
//

#import <UIKit/UIKit.h>




@interface SecondViewController : UIViewController

@end
//
//  SecondViewController.m
//  test-navigation
//
//  Created by ChrisBluez on 9/27/16.
//  Copyright © 2016 ChrisBluez. All rights reserved.
//

#import "SecondViewController.h"

@interface SecondViewController ()

@end

@implementation SecondViewController

- (void)viewDidLoad {

    UILabel *label1 = [[UILabel alloc] init];
    label1.frame=CGRectMake(38, 80, 300, 30);
    label1.backgroundColor =  [UIColor whiteColor];
    label1.text = @"this is secondviewcontroller";
    [self.view addSubview:label1];

    NSLog(@"enter secondviewcontroller");

    UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    btn1.frame  = CGRectMake(38, 120, 300, 30);
    [btn1 setTitle:@"backTo" forState:UIControlStateNormal];
    btn1.backgroundColor = [UIColor blueColor];
    [self.view addSubview:btn1];
    [btn1 addTarget:self action:@selector(backto) forControlEvents:UIControlEventTouchUpInside];


    // Do any additional setup after loading the view.
    self.navigationItem.title = @"what the fuck";
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"try" style:UIBarButtonItemStylePlain target:self action:@selector(backto)];


    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)backto
{
    [self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:0] animated:YES];
}



/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end
//
//  AppDelegate.h
//  test-navigation
//
//  Created by ChrisBluez on 9/26/16.
//  Copyright © 2016 ChrisBluez. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;


@end

//
//  AppDelegate.m
//  test-navigation
//
//  Created by ChrisBluez on 9/26/16.
//  Copyright © 2016 ChrisBluez. All rights reserved.
//

#import "AppDelegate.h"
#import "ViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    ViewController *root1 = [[ViewController alloc] init];
    UINavigationController *nav1 = [[UINavigationController alloc] initWithRootViewController:root1];
    self.window.rootViewController = nav1;




    return YES;
}


- (void)applicationWillResignActive:(UIApplication *)application {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}


- (void)applicationDidEnterBackground:(UIApplication *)application {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}


- (void)applicationWillEnterForeground:(UIApplication *)application {
    // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}


- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}


- (void)applicationWillTerminate:(UIApplication *)application {
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}


@end

这里写图片描述

    总感觉目前的学习方法是打一枪换一个地方,非常的不系统,唉,算了,就这样来吧。
处理下面代码可能出现线程问题的情况- (void)shutterCamera { AVCaptureConnection *videoConnection = [self.ImageOutPut connectionWithMediaType:AVMediaTypeVideo]; UIDeviceOrientation curDeviceOrientation = [[UIDevice currentDevice] orientation]; if (!videoConnection || !self.session.isRunning) { ATLog(@"take photo failed!"); return; } AVCaptureVideoOrientation avcaptureOrientation = [self avOrientationForDeviceOrientation:curDeviceOrientation]; [videoConnection setVideoOrientation:avcaptureOrientation]; [videoConnection setVideoScaleAndCropFactor:self.effectiveScale]; MJWeakSelf; [self.ImageOutPut captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; if (imageDataSampleBuffer) { NSData * imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer]; strongSelf.image = [UIImage imageWithData:imageData]; if (strongSelf.image) { strongSelf.image = [UIImage updateImageSize:strongSelf.image]; } strongSelf.image = [[TZImageManager manager] fixOrientation:strongSelf.image];//[UIImage fixOrientation:weakSelf.image]; PHAuthorizationStatus authorStatus = [PHPhotoLibrary authorizationStatus]; if (authorStatus == PHAuthorizationStatusAuthorized) { [[TZImageManager manager] savePhotoWithImage:strongSelf.image completion:^(PHAsset *asset, NSError *error) { [strongSelf addPHAsset:asset]; }]; }else{ [strongSelf gotoEditImageController:strongSelf.image asset:nil]; } } }); }]; } - (void)gotoEditImageController:(UIImage *)image asset:(PHAsset *)asset { if (!image) { [ATMBPHud showErrorToView:self.view text:@"获取图片失败,请重新再试"]; return; } if (image) { image = [UIImage updateImageSize:image]; } YasicClipPage *vc = [[YasicClipPage alloc] init]; vc.userId = self.userId; vc.attention = self.attention; vc.targetImage = image; vc.searchType = self.searchType; vc.asset = asset; // [self stopSession]; dispatch_async(dispatch_get_main_queue(), ^{ [[KDUserInterfaceTool currenrtopViewController].navigationController pushViewController:vc animated:YES]; [self removeViewController:self]; }); } - (void)addPHAsset:(PHAsset *)asset { MJWeakSelf; TZAssetModel *assetModel = [[TZImageManager manager] createModelWithAsset:asset]; [ATHUD show]; [[TZImageManager manager] requestImageDataForAsset:assetModel.asset completion:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) { [ATHUD dismiss]; UIImage *image = [UIImage imageWithData:imageData];//[[TZImageManager manager] fixOrientation:[UIImage imageWithData:imageData]]; image = [[TZImageManager manager] fixOrientation:image]; [weakSelf gotoEditImageController:image asset:asset]; } progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { ATLog(@"%f",progress); }]; } #pragma --mark 获取大图 - (void)getImageWithModel:(TZAssetModel *)model { if (model == nil) { ATLog(@"图片为空"); return; } [ATHUD show]; WS(weakSelf) [[TZImageManager manager] requestImageDataForAsset:model.asset completion:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) { [ATHUD dismiss]; UIImage *image = [UIImage imageWithData:imageData]; if (weakSelf.downBtnClick) { weakSelf.downBtnClick(image, model); } } progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { ATLog(@"%f",progress); }]; }
最新发布
07-25
<think>我们正在处理关于AVCaptureStillImageOutput的captureStillImageAsynchronouslyFromConnection方法的线程安全问题。 根据引用[1]:AVCaptureStillImageOutput用于静态图片输出。 根据引用[2]:我们在视图出现和消失时启动和停止会话,这涉及到主线程。 根据引用[3]:人脸识别示例中,我们看到了一个委托回调,它可能会在不同的线程中执行。 关键点: 1. captureStillImageAsynchronouslyFromConnection:completionHandler: 是一个异步方法,它的完成回调可能在非主线程中执行。 2. 在完成回调中,我们通常需要处理图像数据(如保存到相册、显示预览等),这些操作可能涉及UI更新或资源访问,需要线程安全。 解决方案: - 在完成回调中,如果需要更新UI,必须将相关代码放到主线程执行。 - 对于共享资源的访问(如写入文件、修改状态等),需要使用适当的同步机制(如锁、串行队列)来避免竞态条件。 示例代码: 假设我们有一个拍照按钮,点击时触发拍照: ```objective-c - (IBAction)takePhoto:(id)sender { AVCaptureConnection *connection = [self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo]; [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { if (imageDataSampleBuffer) { NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer]; UIImage *image = [UIImage imageWithData:imageData]; // 回到主线程更新UI dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); // 保存到相册(注意:写入相册的权限和线程) // 由于写入相册的操作本身是异步且线程安全的,但为了统一,我们也可以放在主线程,或者使用PHPhotoLibrary(iOS8+) // 但是,PHPhotoLibrary的保存操作需要在主线程执行。所以,我们回到主线程保存。 dispatch_async(dispatch_get_main_queue(), ^{ UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), NULL); }); } }]; } - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { if (error) { // 保存出错 } else { // 保存成功 } } ``` 注意: 1. 上述代码中,我们将更新UI和保存图片都放到了主线程。因为UIKit和Photos框架要求在主线程使用。 2. 如果我们需要进行大量的图像处理,那么不应该在主线程进行,而应该在一个后台线程处理,处理完成后再回到主线程更新UI。 另外,关于线程安全的其他考虑: - 如果我们在拍照过程中有状态标记(例如isCapturing),那么修改和读取这个状态标记应该使用线程安全的方式(例如使用串行队列或者使用atomic属性,但更推荐使用串行队列来确保操作的顺序性)。 例如,我们可以设置一个串行队列来管理拍照状态: ```objective-c @property (nonatomic, strong) dispatch_queue_t captureQueue; @property (nonatomic, assign) BOOL isCapturing; // 注意:在多个线程中修改和读取 // 初始化队列 self.captureQueue = dispatch_queue_create("com.example.captureQueue", DISPATCH_QUEUE_SERIAL); // 在拍照方法中: - (IBAction)takePhoto:(id)sender { // 防止连续点击 dispatch_async(self.captureQueue, ^{ if (self.isCapturing) { return; } self.isCapturing = YES; // 回到主线程进行拍照操作(因为AVFoundation的调用不需要在特定队列,但通常UI操作在主线程,所以这里我们为了简单,将拍照触发放在主线程) dispatch_async(dispatch_get_main_queue(), ^{ AVCaptureConnection *connection = [self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo]; [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { // 处理图像(可能耗时,所以放在后台队列) dispatch_async(self.captureQueue, ^{ // 处理图像数据 // ... // 处理完成后,更新状态 self.isCapturing = NO; }); // 更新UI需要主线程 dispatch_async(dispatch_get_main_queue(), ^{ // 更新UI }); }]; }); }); } ``` 但是注意:上面的代码中,我们在captureQueue中修改isCapturing,并且通过captureQueue来防止连续点击。同时,我们将实际的拍照触发放在主线程(因为AVFoundation的调用通常可以在任意线程,但是涉及到UI(比如显示预览)最好在主线程)。在完成回调中,我们将图像处理放到captureQueue,这样就不会阻塞主线程,并且处理完成后将isCapturing设为NO。 总结: 1. 使用串行队列来管理状态,避免多个线程同时修改状态。 2. 将UI操作和需要主线程的操作(如保存到相册)放到主线程。 3. 对于耗时的图像处理,使用后台队列,避免阻塞主线程。 这样,我们可以确保线程安全,避免竞态条件。 注意:在iOS中,AVFoundation的会话(AVCaptureSession)本身是线程安全的,可以在任意线程创建和修改,但是最好在一个串行队列中统一管理配置(比如开始、停止、修改输入输出等)。但是,在这个问题中,我们主要关注的是拍照的瞬间,所以使用一个队列来管理拍照状态即可。 根据引用[4],我们也可以了解到AVFoundation提供了强大的多媒体支持,但同时也需要注意线程安全。 因此,处理线程安全的关键是:将状态管理放在串行队列中,将UI操作放在主线程,合理使用队列来隔离线程。 相关问题: 1. 在AVFoundation中,除了拍照,还有哪些操作需要注意线程安全? 2. 如何设计一个线程安全的相机管理类? 3. 在iOS中,处理图像数据时有哪些常见的线程安全问题?</think>### 处理 AVCaptureStillImageOutput 的线程安全问题 在 Objective-C 中使用 `AVCaptureStillImageOutput` 的 `captureStillImageAsynchronouslyFromConnection:completionHandler:` 方法时,需特别注意以下线程安全问题及解决方案: #### 1. **主线程原则** - **UI 操作必须回到主线程**:完成处理器(completionHandler)在后台线程执行,所有 UI 更新(如显示预览图)必须调度到主线程。 ```objectivec [stillImageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:^(CMSampleBufferRef buffer, NSError *error) { // 后台线程执行 NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:buffer]; UIImage *image = [UIImage imageWithData:imageData]; dispatch_async(dispatch_get_main_queue(), ^{ // 切换到主线程 self.previewImageView.image = image; // 安全更新UI }); }]; ``` #### 2. **资源竞争防护** - **使用串行队列**:创建专用队列管理拍照状态,避免同时触发多次拍照。 ```objectivec @property (nonatomic, strong) dispatch_queue_t captureQueue; @property (nonatomic, assign) BOOL isCapturing; // 拍照状态标记 // 初始化队列(ViewDidLoad中) self.captureQueue = dispatch_queue_create("com.example.captureQueue", DISPATCH_QUEUE_SERIAL); // 拍照方法 - (void)capturePhoto { dispatch_async(self.captureQueue, ^{ if (self.isCapturing) return; // 避免重入 self.isCapturing = YES; dispatch_async(dispatch_get_main_queue(), ^{ AVCaptureConnection *conn = [self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo]; [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:conn completionHandler:...]; }); }); } // 在完成处理器中重置状态 completionHandler:^(...){ dispatch_async(self.captureQueue, ^{ self.isCapturing = NO; // 安全修改状态 }); } ``` #### 3. **会话生命周期管理** - **会话启停与视图生命周期同步**:根据引用[2],在 `viewDidAppear`/`viewDidDisappear` 中控制会话运行,避免后台线程冲突。 ```objectivec - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self.captureSession startRunning]; // 主线程启动 } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self.captureSession stopRunning]; // 主线程停止 } ``` #### 4. **内存管理** - **避免循环引用**:使用 `__weak` 防止 completionHandler 捕获 self 导致内存泄漏。 ```objectivec __weak typeof(self) weakSelf = self; [stillImageOutput captureStillImageAsynchronouslyFromConnection:conn completionHandler:^(...){ __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; // 处理逻辑 }]; ``` #### 完整示例 ```objectivec - (void)safeCapturePhoto { dispatch_async(self.captureQueue, ^{ if (self.isCapturing) return; self.isCapturing = YES; __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ AVCaptureConnection *conn = [self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo]; [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:conn completionHandler:^(CMSampleBufferRef buffer, NSError *error) { // 后台线程 NSData *data = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:buffer]; UIImage *image = [UIImage imageWithData:data]; // 更新UI需切回主线程 dispatch_async(dispatch_get_main_queue(), ^{ weakSelf.previewImageView.image = image; }); // 重置状态(通过安全队列) dispatch_async(weakSelf.captureQueue, ^{ weakSelf.isCapturing = NO; }); }]; }); }); } ``` ### 关键总结 1. **线程分工**: - 主线程:会话控制、UI 更新。 - 专用串行队列:状态管理。 - 后台线程:图像数据处理。 2. **冲突避免**:通过状态标记 (`isCapturing`) 防止并发拍照请求。 3. **内存安全**:使用 `__weak` 打破循环引用链。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值