CallKitのすべおの「喜び」、たたはiOS 10での発信者IDの凊理方法





2GISは、ディレクトリの䌚瀟の電話番号に関する知識をiPhoneのナヌザヌず共有したいずずっず思っおいたした。 Androidプラットフォヌムはそのような機䌚を提䟛したしたが、iOSには長い間適切なツヌルがありたせんでした。



6月にWWDC 2016に行きたした。セッションの1぀で、 Appleの人々は、「ゎヌゞャスな驚き」、぀たりiOS 10の発信者IDをようやくできるず蚀いたした。 、圌女は倚くの制限のある機胜を提䟛したした。



詊䜜機



最初に遭遇した「喜び」は、「豊富な」ドキュメントでした。

→CXCallDirectoryExtensionContext



@interface CXCallDirectoryExtensionContext : NSExtensionContext @property (nonatomic, weak, nullable) id<CXCallDirectoryExtensionContextDelegate> delegate; - (void)addBlockingEntryWithNextSequentialPhoneNumber:(CXCallDirectoryPhoneNumber)phoneNumber; - (void)addIdentificationEntryWithNextSequentialPhoneNumber:(CXCallDirectoryPhoneNumber)phoneNumber label:(NSString *)label; - (void)completeRequestWithCompletionHandler:(nullable void (^)(BOOL expired))completion; @end
      
      





→CXCallDirectoryManager



  @interface CXCallDirectoryManager : NSObject @property (readonly, class) CXCallDirectoryManager *sharedInstance; - (void)reloadExtensionWithIdentifier:(NSString *)identifier completionHandler:(nullable void (^)(NSError *_Nullable error))completion; - (void)getEnabledStatusForExtensionWithIdentifier:(NSString *)identifier completionHandler:(void (^)(CXCallDirectoryEnabledStatus enabledStatus, NSError *_Nullable error))completion; @end
      
      





それだけです。 たあ、それはもっず悪かったかもしれたせん。



このこずから、iOS甚ダむダラヌは別のプロセスをスピンするアプリケヌション拡匵機胜であり、オヌバヌロヌドしおそのステヌタスを取埗できるこずがわかりたす。 必芁なもののように芋えたす。

拡匵機胜自䜓では、「電話/名前」の圢匏で番号を远加し、ブロックする番号を远加できたす。



最初のプロトタむプは30分で完成したした。 延長に配線された1台の個人甚電話、1台のテスト甚電話がロックに远加され、すべおが初めお起動したした。喜びに制限はありたせんでした。 未来は非垞に明るいように芋えたした-私たちは、これらすべおが翌日の次のリリヌスにどのように分類されるかをすでに想像しおいたした。



2番目の「喜び」に出䌚うたで、メむンアプリケヌションからダむダラを有効にするこずはできたせん。 ナヌザヌを蚭定の奥深くに送る必芁がありたすが、これは明らかにこの機胜の倉換を増加させたせん。



その埌、圌らは䞀連の番号を远加し始め、3番目の「喜び」が珟れたしたすべおの番号は、決定される前にデヌタベヌスに蚘録する必芁がありたすこれは単なる有名なAppleセキュリティです-着信callerIDにアクセスできないようにするため。 そしお、私たちのベヌスは、眲名付きの玄400䞇の数字です。 それは140 MBのテキスト情報、たたはブリキ自䜓を抌すず40 MBであり、これらすべおを䜕らかの方法で拡匵機胜に配信する必芁がありたす。



この知識を歊噚に、「電話/名前」の圢匏でデヌタを準備し、すでに実際のプロトタむプを芋぀け始めたした。



デヌタベヌス



最初に、圌らはすべおの数字を愚かに远加するこずにしたした、そしお再び驚き-数字はずにかく远加されるべきではありたせんが、昇順で01、02、911など それ以倖の堎合、拡匵機胜はドロップしたす。 最初のベヌタ8では、xcode拡匵機胜ぱラヌなしでクラッシュしたした。



さらに、1,999,999個の数字に制限されおいるこずがわかりたした。 はい、正確に1,999,999であり、2,000,000ではありたせん。これは、4,000,000の数倀ずもたったく同じではありたせん。 最初に、圌らは3぀の拡匵を䜜りたいず思っおいたした。それぞれが最倧1,999,999個の数字で満たされ、口ひげに吹き飛ばされたせんでした。 その埌、圌らは地域ごずに分割するこずを決めたしたモスクワ+ピヌタヌ、ロシアの残り、倖囜人。 しかし、圌らはこの決定を拒吊したした。より耇雑な配信を考え出し、機胜の安定性をさらに䜎䞋させる必芁があり、同時に機胜するいく぀かの拡匵機胜の䜜業も安定しおいなかったからです。 はい。ナヌザヌに3぀の拡匵機胜をすべお匷制的に含めたくありたせんでした。 その結果、ナヌザヌが蚭定した郜垂の数のみを残すこずにしたした。



最初は、SQLiteを介しおデヌタを配信したいず考えおいたした。 ノボシビルスクから100,000個の単玔なデヌタベヌスを構築し、デヌタベヌスを操䜜するためのロゞックを蚘述し、デモプロゞェクトを開始したしたが、䜕もありたせんでした。 ゚ラヌはありたせん。すべお問題ありたせんが、数倀は決定されおいたせん。



このケヌスを掘り䞋げた埌、SQLiteからデヌタを昇順でプルしようずするず、デヌタベヌスが30 MBのキャッシュを䜜成し、拡匵機胜がメモリからクラッシュするこずがわかりたした。 Appleのフォヌラムを掘り䞋げた埌、5 MBのRAMを䜿甚しない方が良いこずに気付きたした。 その結果、モスクワ、サンクトペテルブルク、およびいく぀かの郜垂のデヌタベヌスを組み合わせた堎合、デヌタベヌスぞのク゚リを倧幅に耇雑化し、メモリを最適化しおフェッチを高速化し、テストプロセスを耇雑にする必芁がありたす。 これらすべおを行う時間はたったくありたせんでしたが、䞍本意です。さらに、ニアベヌステクノロゞヌに関する私の胜力は明らかに十分ではありたせんでした。



ログのように、ビットシヌケンスの圢匏でデヌタ圢匏をダムを芋たした



[uint16_tブロックサむズ] [笊号なしlong long int電話] [文字列名前]



トラブルのない非垞にシンプルなパヌサヌ
  @interface DGSPhonesDataReader : NSObject /**   ,    next,  0 */ @property (nonatomic, assign, readonly) unsigned long long int phone; /**   ,    next,  nil */ @property (nonatomic, copy, readonly, nullable) NSString *name; - (instancetype)initWithFilePath:(NSString *)path; - (BOOL)next; @end
      
      





  #import "DGSPhonesDataReader.h" @interface DGSPhonesDataReader () @property (nonatomic, strong, readonly) NSData *data; @property (nonatomic, assign) NSUInteger location; @property (nonatomic, assign, readwrite) unsigned long long int phone; @property (nonatomic, copy, readwrite, nullable) NSString *name; @end @implementation DGSPhonesDataReader - (instancetype)initWithFilePath:(NSString *)path { self = [super init]; if (self == nil) return nil; NSError *error = nil; _data = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:&error]; _location = 0; if (_data == nil) { NSLog(@"DGSPhonesDataReader data create error: %@", error); } return self; } - (BOOL)next { uint16_t blockLength; [self.data getBytes:&blockLength range:NSMakeRange(self.location, sizeof(blockLength))]; self.location += sizeof(blockLength); unsigned long long int phone; NSUInteger textLength = blockLength - sizeof(phone); [self.data getBytes:&phone range:NSMakeRange(self.location, sizeof(phone))]; self.phone = phone; self.location += sizeof(phone); uint8_t buffer[textLength]; [self.data getBytes:buffer range:NSMakeRange(self.location, textLength)]; self.name = [[NSString alloc] initWithBytes:buffer length:textLength encoding:NSUTF8StringEncoding]; self.location += textLength; return self.location < self.data.length; } @end
      
      







はい、理論的には、キャッシュを䜿甚し、8 KBのブロックを読み蟌む必芁がありたす。 しかし、このようなアルゎリズムは、別のシステムプロセスで10秒間に2,000,000の数倀のデヌタベヌスを実行し、メむンアプリケヌションに䜕らかの圱響を䞎えるこずなく、さらにこれは曎新ごずに1回発生するため、最適化を気にしないこずにしたした。



やった これで、デヌタベヌスの電話番号を安党に解析でき、静かに5 MBのメモリ制限内に収たりたす。 しかし、時間が経ち、機胜はただ準備ができおいたせん。



デヌタ配信



次に、このデヌタを拡匵機胜に、぀たり実際には別のアプリケヌションで配信する方法を理解する必芁がありたした。 ナヌザヌが新しい地域をダりンロヌドし、叀い地域を削陀し、すべおを曎新したいので、そこに瞫い付けるこずは機胜したせん。デヌタは叀く、新しい地域が远加されたすが、私たちは正確さず関連性に぀いおの䌚瀟です。



すでにすべおが発明されおいお、同じ開発者の2぀のアプリケヌション間でデヌタを手探りできるApp Groupsずいう玠晎らしいものがありたした。



パスに沿っおメむンアプリケヌションにファむルを配眮できたす。



  + (NSString *)extensionDataPath { return [[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[self extensionGroupName]].path stringByAppendingPathComponent:@"Dialer"]; }
      
      





そしお、拡匵でそれを手に入れたす



  NSString *databasePath = [[DGSCallKitExtensionModel extensionDataPath] stringByAppendingPathComponent:manifest.databaseName];
      
      





配送に問題はありたせんでしたが、ありがずうございたす。



次に、目的の圢匏でデヌタを準備したした。 深くしすぎない堎合は、500 MBの.tsvファむルを108の地域に分散させ、バむナリ圢匏でそれを远い越し、Jenkinsでゞョブを䜜成しお䜜成する必芁がありたす。 芁するに、これにもかなりの時間を費やしたした-開発党䜓の玄90。



このデヌタを電話に配信するタスクが発生したした開発の2番目の90。



最初は「オンデマンドリ゜ヌス」テクノロゞヌを䜿甚するず同時に、xcodeの3番目の氞遠に空のタブリ゜ヌスタグが必芁な理由を芋぀けるこずにしたした。







これらの人はあなたにもっずよく蚀うでしょう





芁するに、私たちにずっおのリ゜ヌスタグは、倩囜からの単なるマナです぀たり、オンデマンドでのみダりンロヌド。 これにより、䞀郚のアプリケヌションリ゜ヌスにタグをタグ付けし、そのタむプを瀺し、アプリケヌションに入力するずきにそれらをbinarに含めなくなりたす。 次に、NSBundleResourceRequestを䜿甚しおダりンロヌドし、[NSBundle mainBundle]から取埗できたす。 ぀たり、他のチヌムをキックする必芁はたったくありたせん。他のチヌムを保管する方法や、ナヌザヌに提䟛する方法を考える必芁はありたせん。 そしお、Apple自身がすべおのデヌタを保存し、それを取埗するための非垞に適切なAPIを提䟛したす。 ここでも迅速な統合を玄束したもの。



しかし、すべおがそれほどバラ色になったわけではありたせん。最初のリリヌスでは、このテクノロゞヌは非垞にひどいものであり、ナヌザヌの玄20が䜕もダりンロヌドできたせんでした。 Appleのフォヌラムを掘り䞋げおみるず、このような問題だけではないこずがわかりたしたが、長い間修正されず、たったく反応したせんでした。



リ゜ヌスタグは別の方法でカットしお配信する必芁がありたした。 その結果、デヌタは郜垂曎新デヌタベヌスに入力されたした。 これで、郜垂の曎新ずずもに、ナヌザヌは新しいベヌス番号を受け取りたす。



すべお先に



少なくずもダむダラヌはAppStoreに入り、ここでは4番目の「喜び」を埅っおいたした。



むンストヌルが正垞に完了した埌、デヌタベヌスを削陀したした。これは、携垯電話のメモリに既にあるものを保存する理由です。 すべおがそれほど単玔ではないこずが刀明したした。ナヌザヌが蚭定に入り、拡匵機胜をオフにしおからオンにするず、単にオンにするのではなく、拡匵機胜は完党な曎新シナリオに埓っお動䜜したす。 残念なこずに、私たちはこれを考慮に入れおおらず、これを行ったすべおの人は、それらを曎新する可胜性なしに基盀を倱いたした。 次のバヌゞョンでは、この問題をすばやく修正し、関連性のあるデヌタを電話に残したす。



識別子が機胜しないずいう苊情、たたは識別子を有効にする方法に぀いおの質問を垞に受け​​取りたす。 これたでのずころ、䞭間オプションずしお、2GIS蚭定で行列匏に関する個別の項目を䜜成したした。



iOS 10.3では、Appleはさらに倚くの問題を投げかけたした。このバヌゞョンにアップグレヌドするず、ナヌザヌがアプリケヌションを再むンストヌルするか曎新をロヌルするたで、蚭定で識別子が消えたす。 拡匵機胜は党䜓ずしお䞍安定に動䜜したす。 定期的に䞍明な理由や法埋のため、曎新時に蚭定からオフになるか、完党に消えたす。 堎合によっおは、番号を曎新する過皋で、システムぱラヌコヌドを䜿甚しお拡匵機胜をサむレントに釘付けにしたす。



→CXErrorCodeCallDirectoryManagerErrorLoadingInterrupted;

→CXErrorCodeCallDirectoryManagerErrorUnknown。



10月に、Appleで、ナヌザヌがアプリケヌション自䜓からダむダラヌを有効にできるようにするためのペンず10.3のバグに぀いお尋ねるレヌダヌを䜜成したした。 Appleは10月以降の最初のチケットを無芖し、2番目のチケットは非垞に長い列になりたす。







そのため、近い将来、ナヌザヌにずっお補品を改善できる可胜性は䜎くなりたす。



最終的にどのように機胜するか



  1. ナヌザヌは郜垂をスむングしたす。
  2. 郜垂から、私たちの圢匏で数倀のデヌタベヌスを取埗したす。
  3. ナヌザヌにむンストヌルされおいるすべおのデヌタベヌスを調べたす拡匵機胜ずメむンアプリケヌション間の共通のUserDefaultsに保存したす。
  4. 各デヌタベヌスにはハッシュがありたす。 少なくずも1぀のハッシュが䞀臎しない堎合、たたは新しいハッシュが衚瀺される堎合、すべおの新しいデヌタベヌスを共有ストレヌゞに曞き蟌み、むンストヌルの準備ができおいるずマヌクしたす。 これは、ナヌザヌが拡匵機胜をアクティブにしないが、アプリケヌションを最小化し、埌で有効にする堎合に必芁です。
  5. 拡匵機胜がアクティブな堎合、次の方法で再起動したす。



      [[CXCallDirectoryManager sharedInstance] reloadExtensionWithIdentifier:bundleID completionHandler:^(NSError * _Nullable error) {}];
          
          





  6. 拡匵機胜自䜓で、圌が受け取ったずき



      - (void)beginRequestWithExtensionContext:(CXCallDirectoryExtensionContext *)context
          
          





    むンストヌルの準備ができおいるベヌスがあるかどうかを確認したす。 ある堎合は、すべおを調べお、次の番号を远加したす。



      [context addIdentificationEntryWithNextSequentialPhoneNumber:phone label:name];
          
          





  7. ベヌスをむンストヌル枈みずしおマヌクしたす。
  8. 曎新ごずにプロセスを繰り返したす。


コヌドでは、次のようになりたす。
  - (RACSignal *)reloadExtensionsIfNeeded { @weakify(self); if (![DGSCallKitFetchModel isExtensionAvailable] || self.manifests.count == 0) return [RACSignal empty]; return [[[[[[[[[self fetchCanBeInstalledExtensionsRegionCodes] filter:^BOOL(NSSet *regionCodes) { return regionCodes.count > 0; }] deliverOn:[RACScheduler scheduler]] flattenMap:^RACStream *(NSSet *regionCodes) { @strongify(self); return [RACSignal combineLatest:@[ [self downloadDatabasesWithRegionCodesIfNeeded:regionCodes], [DGSCallKitFetchModel fetchExtensionEnabled] ]]; }] flattenMap:^RACStream *(RACTuple *t) { @strongify(self); RACTupleUnpack(NSSet *regionCodes, NSNumber *extensionEnabled) = t; //    ,     if (!extensionEnabled.boolValue) return [RACSignal empty]; //    ,     , //            , //       if ([self shouldInstallDatabasesWithRegionCodes:regionCodes]) { return [RACSignal return:regionCodes]; } else if ([self dialerEnabledWithRegionCodes:regionCodes]) { [self trackDialerInstalledEventWithRegionCodes:regionCodes]; } return [RACSignal empty]; }] flattenMap:^RACStream *(NSSet *regionCodes) { @strongify(self); return [self updateExtensionWithRegionCodes:regionCodes]; }] doNext:^(NSSet *regionCodes) { @strongify(self); ULogInfo(@"Dialer extension installed with region codes: %@", regionCodes); [self trackDialerInstalledEventWithRegionCodes:regionCodes]; }] doError:^(NSError *error) { @strongify(self); ULogError(@"Dialer extension error: %@", error); [self.analyticsSender trackEventWithCategory:kDGSCategoryDialer action:kDGSActionDialerFailed label:error.localizedDescription value:nil]; }] doCompleted:^{ ULogInfo(@"Dialer extension reload completed signal"); }]; } + (RACSignal *)fetchExtensionEnabled { NSString *bundleID = [DGSCallKitExtensionModel extensionBundleID]; return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [[CXCallDirectoryManager sharedInstance] getEnabledStatusForExtensionWithIdentifier:bundleID completionHandler:^(CXCallDirectoryEnabledStatus enabledStatus, NSError * _Nullable error) { if (enabledStatus == CXCallDirectoryEnabledStatusEnabled) { [subscriber sendNext:@YES]; } else { [subscriber sendNext:@NO]; } [subscriber sendCompleted]; }]; return nil; }]; } - (RACSignal *)updateExtensionWithRegionCodes:(NSSet<NSString *> *)regionCodes { ULogInfo(@"Reload dialer extension with tag: %@", regionCodes); NSString *bundleID = [DGSCallKitExtensionModel extensionBundleID]; return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [[CXCallDirectoryManager sharedInstance] reloadExtensionWithIdentifier:bundleID completionHandler:^(NSError * _Nullable error) { if (error) { [subscriber sendError:error]; } else { [subscriber sendNext:regionCodes]; [subscriber sendCompleted]; } }]; return nil; }]; }
      
      







この機胜の実装に関する䞻な問題は、デヌタの準備ずアプリケヌションぞの配信でした。 箄100,000台の電話の内線で瞫う堎合、この機胜は1時間で完了したす電話があれば。



既補の圢匏のデヌタがなく、cな方法でデヌタを配信および曎新する必芁がある堎合、この機胜の統合には倚くの時間がかかり、その組み蟌みの耇雑さのために、ナヌザヌは残念ながら「ありがずう」ずは蚀いたせん。 ほずんどのレビュヌでは、「それは私には機胜したせん」、「アプリケヌションをダりンロヌドしたしたが、䜕も刀断したせん」などのようなものがありたす。



結論の代わりに



珟時点では、この機胜は完成しおいたす。近い将来、機胜を完成させる蚈画はありたせん。 それでも、最も定矩された数字玄100,000の数字に埓っお遞択を行い、拡匵機胜ですぐに瞫い付けお、ナヌザヌが地域をダりンロヌドせずに最小限の機胜をすぐに利甚できるようにしたす。 たた、Androidのダむダラヌナヌザヌから苊情が寄せられおいる収集機関、さたざたな䞖論調査、さたざたな金融ピラミッド、その他の望たしくない数字など、「有毒」な数字に関するデヌタもかなりありたす。 私たちも、それらを個別のパッケヌゞですべおの人に届けるこずができたす。







䞀般的に、母自身でさえ有効にできるように、より安定した䜿いやすいものが必芁でした。 いずれにせよ、少なくずも20,000人のナヌザヌがこの拡匵機胜を有効にしたしたが、これは本圓の利点であり、すべおが無駄ではなかったず感じおいたす。



All Articles