Objective-C
iOS
Xcode10

【Xcode10.1】におけるiOSアプリのWarning消し作業まとめ

はじめに

ある案件でiOSアプリの1000個以上のwarningを修正する必要があるため、パターン化したいので対応をつらつらと記載していく。というか1000個もwarningがあると数が安定しなくて800~1000というふうに姿を変えてしまう。リリースにも影響あるし、多少なりともビルド時間にも影響があるため対応を行う。

修正対象は、deprecated, block warning、影響が大きい箇所はなるべく変更を行わない。これらを対応した際に、どんな対応をしたのか記載していく。あくまで一例なので、参考程度によろしくお願いしますmm
また、筆者はobjective-c初心者なのでお手柔らかに。あとこっちの方が綺麗とか、スマートな書き方があれば編集リクエスト是非お願いしますmm

ちなみにwarningとは

ビルドする上でのエラーではないが、ソースを修正する必要がある箇所。放置しているとiOSアプリをリリースする際にリジェクトされる可能性もあるので、早いうちに直したほうが良い。

deprecated

Stringクラス

エンコードメソッド(CFURLCreateStringByAddingPercentEscapes)

warning message

'CFURLCreateStringByAddingPercentEscapes' is deprecated: first deprecated in iOS 9.0 - Use [NSString stringByAddingPercentEncodingWithAllowedCharacters:] instead, which always uses the recommended UTF-8 encoding, and which encodes for a specific URL component or subcomponent (since each URL component or subcomponent has different rules for what characters are valid).

before
CFStringRef strRef = CFURLCreateStringByAddingPercentEscapes(
    NULL,
    (CFStringRef)string,
    NULL,
    (CFStringRef)@"!*'();:@&=+$,/?%#[]~",
    kCFStringEncodingUTF8);
NSString *str = [NSString stringWithString:(__bridge NSString *)strRef];
CFRelease(strRef);
return str;
after
// Reserved characters defined by RFC 3986
NSString *genDelims = @":/?#[]@";
NSString *subDelims = @"!$&'()*+,;=";
NSString *reservedCharacters = [NSString stringWithFormat:@"%@%@",
                                genDelims,
                                subDelims];
// URLQueryAllowedCharacterSetからRFC 3986で予約されている文字を除いたもののみエスケープしない
NSMutableCharacterSet * allowedCharacterSet = [NSCharacterSet URLQueryAllowedCharacterSet].mutableCopy;
[allowedCharacterSet removeCharactersInString:reservedCharacters];
NSString *str = [sd stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
return str;

参考


デコードメソッド(stringByAddingPercentEscapesUsingEncoding)

warning message

'stringByAddingPercentEscapesUsingEncoding:' is deprecated: first deprecated in iOS 9.0 - Use -stringByAddingPercentEncodingWithAllowedCharacters: instead, which always uses the recommended UTF-8 encoding, and which encodes for a specific URL component or subcomponent since each URL component or subcomponent has different rules for what characters are valid.

before
[string stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
after
[string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];

CFURLCreateStringByReplacingPercentEscapesUsingEncoding

before
CFStringRef decodedString = CFURLCreateStringByReplacingPercentEscapesUsingEncoding(
    kCFAllocatorDefault,
    originalString,
    CFSTR(""),
    kCFStringEncodingUTF8);
after
CFStringRef decodedString = CFURLCreateStringByReplacingPercentEscapes(
    kCFAllocatorDefault,
    (CFStringRef) originalString,
    CFSTR(""));

stringByReplacingPercentEscapesUsingEncoding

warning message

'stringByReplacingPercentEscapesUsingEncoding:' is deprecated: first deprecated in iOS 9.0 - Use -stringByRemovingPercentEncoding instead, which always uses the recommended UTF-8 encoding.

before
[string stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
after
[string stringByRemovingPercentEncoding];

NSURLConnectionクラス

NSURLConnection:connectionWithRequest

warning message

'connectionWithRequest:delegate:' is deprecated: first deprecated in iOS 9.0 - Use NSURLSession (see NSURLSession.h)

before
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
after
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
[dataTask resume];

or

[[[NSURLSession sharedSession] dataTaskWithRequest:request] resume];
デリゲートあり、configuration設定を行う場合

デリゲートの際はヘッダーファイルに、<NSURLSessionDelegete>を記載する

ViewController.h
@interface ViewController () <NSURLSessionDelegete> // NSURLSessionDelegeteに準拠
ViewController.m
// configurationの設定
// NSURLSessionConfiguration configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

// NSURLSessionは指定しなければ、メインスレッドで処理されないため、メインスレッドで実行
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                                                      delegate:self
                                                 delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
[dataTask resume];

完了後、もしくはエラー時に処理が必要な場合は、デリゲートを設定したクラスに記載する。

ViewController.m
/**
 * HTTPリクエストのデリゲートメソッド(完了処理)
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    if (error) {
        // HTTPリクエスト失敗処理
        [self failureHttpRequest:error];
    } else {
        // HTTPリクエスト成功処理
        [self successHttpRequest];
    }
}

ALAssets系

カメラロールへのアクセス権あれこれ

warning message

'ALAssetsLibrary' is deprecated: first deprecated in iOS 9.0 - Use PHPhotoLibrary from the Photos framework instead

before
#import <AssetsLibrary/AssetsLibrary.h>

~~
~~

ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus];

switch (status) {
    case ALAuthorizationStatusAuthorized:
        // 写真へのアクセスが許可されている状態
        break;
    case ALAuthorizationStatusNotDetermined:
    {
        // 初回起動時に許可設定を促すダイアログが表示される
        ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
        [library enumerateGroupsWithTypes:ALAssetsGroupAll
                               usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
                                   // 許可された場合
                                   dispatch_async(dispatch_get_main_queue(), ^{
                                       // do something
                                   });
                                }
                              failureBlock:^(NSError *error) {
                                   // 許可してもらえない場合
                                   dispatch_async(dispatch_get_main_queue(), ^{
                                   });
                               }];
    }
        break;
    case ALAuthorizationStatusDenied:
        // プライバシーで許可されていない状態
        break;
    case ALAuthorizationStatusRestricted:
       // 機能制限されている場合
        break;
    default:
        break;
}

after
#import <Photos/Photos.h>

~~
~~

PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
if (status == PHAuthorizationStatusAuthorized) {
     // 写真へのアクセスを許可されている

} else if (status == PHAuthorizationStatusDenied) {
     // 写真へのアクセスを拒否されている

} else if (status == PHAuthorizationStatusNotDetermined) {
     // このアプリに与える権限を選択をしていない
     [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
         if (status == PHAuthorizationStatusAuthorized) {
             // Access has been granted.         
         }
         else {
             // Access has been denied.
         }
     }];

} else if (status == PHAuthorizationStatusRestricted) {
     // parental controlなどで制限されていて、ユーザーはアプリのアクセスの許可を変更できない
}

参考


画像データサイズの取得

warning message

'assetForURL:resultBlock:failureBlock:' is deprecated: first deprecated in iOS 9.0 - Use fetchAssetsWithLocalIdentifiers:options: on PHAsset to fetch assets by local identifier (or to lookup PHAssets by a previously known ALAssetPropertyAssetURL use fetchAssetsWithALAssetURLs:options:) from the Photos framework instead

before
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:url resultBlock:^(ALAsset *asset) {
    // Assetライブラリから画像データサイズを取得などあれこれ
    ALAssetRepresentation *rep = [asset defaultRepresentation];
    NSUInteger dataSize = (NSUInteger)rep.size;

} failureBlock:^(NSError *error) {
    NSLog(@"画像を読み込めませんでした");
}];
after
// urlはiOS端末内のURLを想定
PHFetchResult<PHAsset *> *assets = [PHAsset fetchAssetsWithALAssetURLs:@[url] options:nil];
[[PHImageManager defaultManager] requestImageDataForAsset:assets.firstObject
                                                  options:nil
                                            resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
        NSInteger dataSize = imageData.length;
        NSLog(@"画像サイズ : %ld", dataSize);
}];

参考

その他(CoreDataやMapKitなど)

CoreData:NSManagedObjectContext

warning message

'init()' was deprecated in iOS 9.0: Use -initWithConcurrencyType: instead

before
[[NSManagedObjectContext alloc] init];
after
// ViewController()内で完結する場合、バックグラウンドでの実行の場合には、PrivateQueueConcurrencyTypeも選択肢
[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

参考


MapKit:MKPinAnnotationColorRed

warning message

'MKPinAnnotationColorRed' is deprecated: first deprecated in iOS 9.0 - Use MKPinAnnotationView's pinTintColor instead

before
PinAnnotationView *annotationView = [PinAnnotationView new];
annotationView.pinColor = MKPinAnnotationColorRed;
after
PinAnnotationView *annotationView = [PinAnnotationView new];
annotationView.pinTintColor = [UIColor redColor];

block warning

このwarningは半自動的に修正ができるため、大方のパターンを記載していく。ちなみに修正方法は、warningをクリックしてFixボタンをポチーでおk。
XcodeのEditer - Fix All Issues での修正が便利。

  • This block declaration is not a prototype
void (^completionBlock)();
// ↓ voidが必要 () のみだと何らかの変数が入力可能な状態、要はちゃんと宣言してねということ
void (^completionBlock)(void);
  • Block implicitly retains 'self'; explicitly mention 'self' to indicate this is intended behavior
number
// ↓ プロパティを参照するときはself->をつける(なんかキモい)
self->number

修正の注意点としては、宣言時に()の場合に(void)としなければいけない点。当然だが、ヘッダーファイルにも記載がない場合、そちらも修正が必要なので数が多くなる。早めの修正をおすすめする。
また、既に(void)の状態で、何かしら変数が突っ込まれているとビルド時にエラーになるので、1ファイルずつ確認しながら行うのが個人的におすすめ。


まとめ

想像していたより大きな記事になりこんなにあるのかと驚き。まじで原因不明のwarningとの戦争なので落ち着いて調べていって少しずつパターン化していけば、確実に量は減るので同じような問題に陥った方の参考になれば幸いです。
あと確実に言えることは、warningを出したままCommitはやめましょうまじで。将来の技術的負債になります。。。

その他参考URL