我给 iOS 系统打了个补丁——修复 iOS 16 系统键盘重大 Crash(下)

简介: 我给 iOS 系统打了个补丁——修复 iOS 16 系统键盘重大 Crash(下)

更多精彩内容,欢迎观看:

我给 iOS 系统打了个补丁——修复 iOS 16 系统键盘重大 Crash(上)


六、总结根因


通过上述分析推演,iOS 16 键盘 Crash 的根因已查明,即-[UIKeyboardTaskQueue continueExecutionOnMainThread]方法内执行-[UIKeyboardTaskQueue tryLockWhenReadyForMainThread]尝试加锁失败后,不return继续向下执行读写不安全内存以及解锁,导致存在锁失效的情况,使得UIKeyboardTaskQueue成员变量_deferredTasks数组在多线程下出现并发添加UIKeyboardTaskEntry实例而引起野指针,导致最终 Crash。


注:该根因除了导致数组读写异常而 Crash,也可能导致其他变量的状态不一致性,只是不一定表现为 Crash 而已,建议用本文方案修复。


解决方案(App 内置补丁源码)


明确根因后,解决方案就比较明确了,写一个 App 内置补丁代码使得-[UIKeyboardTaskQueue continueExecutionOnMainThread]方法内执行-[UIKeyboardTaskQueue tryLockWhenReadyForMainThread]尝试加锁失败后,正常return即可。补丁方案有两个:


  1. 重写-[UIKeyboardTaskQueue continueExecutionOnMainThread]方法。在原汇编基础上新增一条指令,即在bl _objc_msgSend$tryLockWhenReadyForMainThread后添加一条汇编指令cbz w0, return_labelreturn_label对应源码return对应的汇编指令地址),如失败则return。但该方案涉及的原汇编指令较多,有 95 条汇编指令(见下文附件中 iOS 系统汇编),容易踩坑。
  2. 重写-[UIKeyboardTaskQueue tryLockWhenReadyForMainThread]方法。在该方法内如加锁失败则模拟两次return,回到-[UIKeyboardTaskQueue continueExecutionOnMainThread]的上一个函数栈,改造的汇编指令较少,安全性较好,也确认了除-[UIKeyboardTaskQueue continueExecutionOnMainThread]调用外,无其他方法调用-[UIKeyboardTaskQueue tryLockWhenReadyForMainThread]


最终,支付宝 App 基于稳定性的考虑,采用第 2 种补丁方案修复键盘 Crash。


补丁原理



图 14 修复键盘 Crash 的补丁原理


-[UIKeyboardTaskQueue tryLockWhenReadyForMainThread]实现以下逻辑:

  • 如加锁成功,则return 1 次,返回到-[UIKeyboardTaskQueue continueExecutionOnMainThread]方法的下一条指令继续执行;
  • 如加锁失败,则模拟return 2 次,返回到-[UIKeyboardTaskQueue continueExecutionOnMainThread]的函数栈的上一层函数的地址继续执行,也就是模拟了从-[UIKeyboardTaskQueue continueExecutionOnMainThread]中执行return操作。


源码return语句,对应汇编的 4 步:

  1. 恢复fplr寄存器。fp(也称x29)记录当前帧的内存地址,lr(也称x30)记录从当前函数返回时跳转到哪个地址继续执行。运行时就是通过fplr寄存器,输出线程的函数栈的。如 Crash 函数栈,或从lldbbt输出的函数栈;
  2. 恢复callee-saved寄存器。即x19-x28的寄存器,try-catch的实现就涉及该类寄存器,一般按需执行;
  3. 恢复sp寄存器。sp记录当前帧的栈顶地址,,当前函数的局部变量所在的内存地址就在(fp, sp]之间;
  4. 执行ret指令。执行ret指令后,pc就指向lr寄存器的值,然后继续执行;


本文补丁方案的原理中,tryLock 失败时就是通过:恢复fplr寄存器 + 恢复callee-saved寄存器 + 恢复sp寄存器 + 再次恢复fplr寄存器 + 再次恢复callee-saved寄存器 + 再次恢复sp寄存器 +ret指令 来模拟在-[UIKeyboardTaskQueue tryLockWhenReadyForMainThread]方法内return 2 次直接返回到-[UIKeyboardTaskQueue continueExecutionOnMainThread]的函数栈的上一层函数的。


补丁实现


有两部分组成:

  1. 重写方法:对应 fix_UIKeyboardTaskQueue.S 文件;
  2. Hook 入口:对应 fix_UIKeyboardTaskQueue.m 文件;


重写方法


重写-[UIKeyboardTaskQueue tryLockWhenReadyForMainThread]方法实现,对应下文附件中补丁源码的 fix_UIKeyboardTaskQueue.S 文件。


图 15 重写 -[UIKeyboardTaskQueue tryLockWhenReadyForMainThread] 方法实现

Hook 入口


借助+ (void)load方法在 App 启动时执行的特点实现对-[UIKeyboardTaskQueue tryLockWhenReadyForMainThread]方法的 Hook,仅在 iOS 16 的 Arm64 架构上生效,对应下文附件中补丁源码的 fix_UIKeyboardTaskQueue.m 文件。


图 16 Hook 入口的代码


方案效果


于 2023.8.25 在支付宝 App 近期版本 10.5.16.6000 上全量开启解决方案的开关后,该版本上的 Crash 日 PV 已经降到 0 了


图 17 支付宝 App 近期版本 10.5.16.6000 上键盘 Crash 日 PV


同时,支付宝 App 的全量版本(包括所有历史版本)的键盘 Crash 日 PV 下降了近 90%,随着更多用户升级到支付宝 App 最新版本,预计会降到个位数



图 18 方案上线后键盘 Crash 日 PV 明显下降的趋势图


最终该方案由验收人确认有效,键盘 Crash 已解决,揭榜挑战成功,附上一张挑战成功捷报图收个尾。


图 19 蚂蚁内部的技术英雄榜捷报


附件


1、补丁源码


补丁源码包括两部分:fix_UIKeyboardTaskQueue.S 和 fix_UIKeyboardTaskQueue.m。使用时将该两文件直接内置在 App 中即可,也可在 App 启动时加开关控制 Hook 入口的时机。


#ifdef __arm64__////  fix_UIKeyboardTaskQueue.S//  fix_UIKeyboardTaskQueue////  Created by Alipay on 2023/8/10.//  Copyright © 2023 Alipay. All rights reserved.///**原实现-[UIKeyboardTaskQueue tryLockWhenReadyForMainThread]:ldr    x0, [x0, #0x10]mov    x2, #0x0b    "_objc_msgSend$tryLockWhenCondition:"*/// 重写实现.section__TEXT,__cstring,cstring_literalstryLockWhenCondition.str:
.asciz"tryLockWhenCondition:".text.align4.global_fix_UIKeyboardTaskQueue_tryLockWhenReadyForMainThread.cfi_startproc_fix_UIKeyboardTaskQueue_tryLockWhenReadyForMainThread:
stpx20, x19, [sp, #-0x20]!stpx29, x30, [sp, #0x10]
addx29, sp, #0x10movx19, x0                     ; selfadrpx0, tryLockWhenCondition.str@PAGEaddx0, x0, tryLockWhenCondition.str@PAGEOFFbl_sel_registerName           ; @selector(tryLockWhenCondition:)
movx1, x0ldrx0, [x19, #0x10]            ; _lockmovx2, #0x0bl_objc_msgSend               ; -[_locktryLockWhenCondition:0]
ldpx29, x30, [sp, #0x10]       ; 恢复fp和lrldpx20, x19, [sp], #0x20       ; 恢复callee-saved寄存器、并恢复spcbzx0, 1f// 如tryLock成功,则继续执行-[UIKeyboardTaskQueue continueExecutionOnMainThread]的指令ret// 如tryLock失败,则模拟从-[UIKeyboardTaskQueue continueExecutionOnMainThread] return,不再继续执行1:
ldpx29, x30, [sp, #0x20]       ; 恢复fp和lrldpx20, x19, [sp, #0x10]       ; 恢复callee-saved寄存器addsp, sp, #0x30               ; 恢复spautibsp                            ; AuthenticateInstructionaddressret.cfi_endproc#endif


////  fix_UIKeyboardTaskQueue.m//  fix_UIKeyboardTaskQueue////  Created by Alipay on 2023/9/4.//#ifdef __arm64__#import <UIKit/UIKit.h>#include <objc/runtime.h>@interfacefix_UIKeyboardTaskQueue : NSObject@end@implementationfix_UIKeyboardTaskQueue+ (void)load {
externBOOLfix_UIKeyboardTaskQueue_tryLockWhenReadyForMainThread(idself, SELselector);
if (@available(iOS16.0, *)) {
NSString*systemVersion= [[UIDevicecurrentDevice] systemVersion];
NSArray*verInfos= [systemVersioncomponentsSeparatedByString:@"."];
NSUIntegercount= [verInfoscount];
if (count>=2) {
if ([verInfos[0] isEqualToString:@"16"]) {
class_replaceMethod(objc_getClass("UIKeyboardTaskQueue"), sel_getUid("tryLockWhenReadyForMainThread"), (IMP)fix_UIKeyboardTaskQueue_tryLockWhenReadyForMainThread, "B16@0:8");
            }
        }
    }
}
@end#endif


2、Demo 关键源码


////  ViewController.m//  UIKeyboardTaskQueueDemo////  Created by Alipay on 2023/8/30.//#import "ViewController.h"#include <objc/runtime.h>// #import <DebugKit/DebugKit.h>@interfaceViewController ()
@end@implementationViewController {
NSMutableArray*_tasks;
NSMutableArray*_deferredTasks;
NSConditionLock*_lock;
}
- (void)viewDidLoad {
    [superviewDidLoad];
// 输出UIKeyboardTaskQueue的所有实例方法和类方法// dk_print_all_methods_of_class("UIKeyboardTaskQueue");// 输出UIKeyboardTaskQueue的所有property// dk_print_all_properties("UIKeyboardTaskQueue");// 输出UIKeyboardTaskQueue的所有ivars// dk_print_class_all_ivars("UIKeyboardTaskQueue");UITextView*textView= [[UITextViewalloc] initWithFrame:self.view.bounds];
    [self.viewaddSubview:textView];
    [selftest_crash];
    [selftest_ok];
}
- (void)unlock {
    [_lockunlockWithCondition:0];
}
- (void)lock {
    [_locklock];
}
- (BOOL)tryLock {
return [_locktryLockWhenCondition:0];
}
- (void)addEntry_ok {
    [selflock];
    [_deferredTasksaddObject:[[NSObjectalloc] init]];
NSLog(@"add,    %lu", _deferredTasks.count);
    [selfunlock];
}
- (void)removeEntry_crash {
    [selftryLock];
if (_deferredTasks.count) {
        [_tasksaddObject:[_deferredTasksobjectAtIndex:0]];
if (_deferredTasks.count) {
            [_deferredTasksremoveObjectAtIndex:0];
NSLog(@"remove, %lu", _deferredTasks.count);
//            NSLog(@"%@, %lu -[_deferredTasks removeObjectAtIndex:0]", [NSThread currentThread], _deferredTasks.count);        }
    }
    [selfunlock];
}
- (void)removeEntry_ok {
if (![selftryLock]) return;
if (_deferredTasks.count) {
        [_tasksaddObject:[_deferredTasksobjectAtIndex:0]];
if (_deferredTasks.count) {
            [_deferredTasksremoveObjectAtIndex:0];
NSLog(@"remove, %lu", _deferredTasks.count);
//            NSLog(@"%@, %lu -[_deferredTasks removeObjectAtIndex:0]", [NSThread currentThread], _deferredTasks.count);        }
    }
    [selfunlock];
}
- (void)test_crash {
// init_tasks= [NSMutableArrayarray];
_deferredTasks= [NSMutableArrayarray];
_lock= [[NSConditionLockalloc] initWithCondition:0];
for (inti=0; i<10000; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [selfaddEntry_ok];
        });
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//            dispatch_async(dispatch_get_main_queue(), ^{                [selfremoveEntry_crash];
//            });        });
    }
}
- (void)test_ok {
// init_tasks= [NSMutableArrayarray];
_deferredTasks= [NSMutableArrayarray];
_lock= [[NSConditionLockalloc] initWithCondition:0];
for (inti=0; i<1000; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [selfaddEntry_ok];
        });
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [selfremoveEntry_ok];
        });
    }
}
@end


3、脚本源码


#!/bin/sh# FileTEXT_FILE="$1";
# ClassCLASS_NAME="$2"; 
cat"$TEXT_FILE"|tr'\n''&'|sed's/&\-\[/\n\-\[/g'|grep"^\-\[$CLASS_NAME "|tr'&''\n';


4、iOS 系统汇编(关键方法)


将 iOS 16.6 的 iPhone 12 Pro Max(Hardware Mode: iPhone13 4)设备连接到 Xcode 后,按如下操作可获取到 UIKeyboardTaskQueue 类的实现汇编,即UIKitCore_20G75_arm64e_TEXT.txt 文件。


otool-s__TEXT__text-v~/Library/Developer/Xcode/iOS\DeviceSupport/16.6\\(20G75\)\arm64e/Symbols/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore>~/Desktop/UIKitCore_20G75_arm64e_TEXT.txt./fetch_class_text_from_all.sh~/Desktop/UIKitCore_20G75_arm64e_TEXT.txtUIKeyboardTaskQueue>~/Desktop/UIKeyboardTaskQueue_20G75_arm64e_TEXT.txt


-[UIKeyboardTaskQueuecontinueExecutionOnMainThread]:
0000000189466fd0pacibsp0000000189466fd4subsp, sp, #0x300000000189466fd8stpx20, x19, [sp, #0x10]
0000000189466fdcstpx29, x30, [sp, #0x20]
0000000189466fe0addx29, sp, #0x200000000189466fe4movx19, x00000000189466fe8bl0x18c9df5e00000000189466feccmpw0, #0x10000000189466ff0b.ne0x1894670240000000189466ff4movx0, x190000000189466ff8bl_objc_msgSend$tryLockWhenReadyForMainThread0000000189466ffcldrx8, [x19, #0x28]
0000000189467000cbzx8, 0x1894670580000000189467004ldrx8, [x19, #0x30]
0000000189467008cbzx8, 0x1894670b4000000018946700cbl0x18c9df2f00000000189467010strx0, [sp, #0x8]
0000000189467014ldrx8, [x19, #0x30]
0000000189467018strxzr, [x19, #0x30]
000000018946701cbl0x18c9df1500000000189467020b0x1894670ac0000000189467024adrpx8, -26465 ; 0x182d060000000000189467028addx2, x8, #0xe19000000018946702cmovx0, x190000000189467030movx3, #0x00000000189467034movw4, #0x00000000189467038ldpx29, x30, [sp, #0x20]
000000018946703cldpx20, x19, [sp, #0x10]
0000000189467040addsp, sp, #0x300000000189467044autibsp0000000189467048eorx16, x30, x30, lsl#1000000018946704ctbzx16, #0x3e, 0x1894670540000000189467050brk#0xc4710000000189467054b"_objc_msgSend$performSelectorOnMainThread:withObject:waitUntilDone:"0000000189467058ldrx0, [x19, #0x18]
000000018946705cbl_objc_msgSend$count0000000189467060cbzx0, 0x1894670b80000000189467064adrpx8, 333019 ; 0x1da9420000000000189467068ldrx0, [x8, #0x500]
000000018946706cbl0x18c9dee300000000189467070movx2, x190000000189467074bl"_objc_msgSend$initWithExecutionQueue:"0000000189467078movx20, x0000000018946707cmovx0, x190000000189467080movx2, x200000000189467084bl"_objc_msgSend$setExecutionContext:"0000000189467088bl0x18c9df0a0000000018946708cldrx0, [x19, #0x18]
0000000189467090movx2, #0x00000000189467094bl"_objc_msgSend$objectAtIndex:"0000000189467098bl0x18c9deec0000000018946709cstrx0, [sp, #0x8]
00000001894670a0ldrx0, [x19, #0x18]
00000001894670a4movx2, #0x000000001894670a8bl"_objc_msgSend$removeObjectAtIndex:"00000001894670acldrx0, [sp, #0x8]
00000001894670b0b0x1894670b800000001894670b4movx0, #0x000000001894670b8strx0, [sp, #0x8]
00000001894670bcbl_objc_msgSend$originatingStack00000001894670c0bl0x18c9deec000000001894670c4movx20, x000000001894670c8movx0, x1900000001894670ccmovx2, x2000000001894670d0bl"_objc_msgSend$setActiveOriginator:"00000001894670d4bl0x18c9df0a000000001894670d8movx0, x1900000001894670dcbl_objc_msgSend$unlock00000001894670e0ldrx1, [sp, #0x8]
00000001894670e4ldrbw20, [x19, #0x8]
00000001894670e8movw8, #0x100000001894670ecstrbw8, [x19, #0x8]
00000001894670f0ldrx2, [x19, #0x28]
00000001894670f4cbzx1, 0x18946710800000001894670f8movx0, x100000001894670fcbl"_objc_msgSend$execute:"0000000189467100ldrx1, [sp, #0x8]
0000000189467104b0x18946710c0000000189467108cbzx2, 0x189467130000000018946710cstrbw20, [x19, #0x8]
0000000189467110ldpx29, x30, [sp, #0x20]
0000000189467114ldpx20, x19, [sp, #0x10]
0000000189467118addsp, sp, #0x30000000018946711cautibsp0000000189467120eorx16, x30, x30, lsl#10000000189467124tbzx16, #0x3e, 0x18946712c0000000189467128brk#0xc471000000018946712cb0x18c9df0600000000189467130ldrx0, [x19, #0x20]
0000000189467134bl_objc_msgSend$count0000000189467138ldrx1, [sp, #0x8]
000000018946713ccbzx0, 0x18946710c0000000189467140movx0, x190000000189467144bl_objc_msgSend$performDeferredTaskIfIdle0000000189467148b0x189467100-[UIKeyboardTaskQueuetryLockWhenReadyForMainThread]:
0000000189467738ldrx0, [x0, #0x10]
000000018946773cmovx2, #0x00000000189467740b"_objc_msgSend$tryLockWhenCondition:"-[UIKeyboardTaskQueueperformDeferredTaskIfIdle]:
0000000189c814b4pacibsp0000000189c814b8stpx20, x19, [sp, #-0x20]!0000000189c814bcstpx29, x30, [sp, #0x10]
0000000189c814c0addx29, sp, #0x100000000189c814c4movx19, x00000000189c814c8bl_objc_msgSend$lock0000000189c814ccmovx0, x190000000189c814d0bl_objc_msgSend$promoteDeferredTaskIfIdle0000000189c814d4movx0, x190000000189c814d8bl_objc_msgSend$unlock0000000189c814dcmovx0, x190000000189c814e0ldpx29, x30, [sp, #0x10]
0000000189c814e4ldpx20, x19, [sp], #0x200000000189c814e8autibsp0000000189c814eceorx16, x30, x30, lsl#10000000189c814f0tbzx16, #0x3e, 0x189c814f80000000189c814f4brk#0xc4710000000189c814f8b_objc_msgSend$continueExecutionOnMainThread-[UIKeyboardTaskQueuepromoteDeferredTaskIfIdle]:
0000000189c814fcpacibsp0000000189c81500subsp, sp, #0x300000000189c81504stpx20, x19, [sp, #0x10]
0000000189c81508stpx29, x30, [sp, #0x20]
0000000189c8150caddx29, sp, #0x200000000189c81510ldrx8, [x0, #0x28]
0000000189c81514cbzx8, 0x189c815280000000189c81518ldpx29, x30, [sp, #0x20]
0000000189c8151cldpx20, x19, [sp, #0x10]
0000000189c81520addsp, sp, #0x300000000189c81524retab0000000189c81528movx19, x00000000189c8152cldrx0, [x0, #0x20]
0000000189c81530bl_objc_msgSend$count0000000189c81534cbzx0, 0x189c815180000000189c81538ldrx0, [x19, #0x20]
0000000189c8153cmovx2, #0x00000000189c81540bl"_objc_msgSend$objectAtIndex:"0000000189c81544bl0x18c9deec00000000189c81548movx2, x00000000189c8154cstrx0, [sp, #0x8]
0000000189c81550ldrx0, [x19, #0x18]
0000000189c81554bl"_objc_msgSend$addObject:"0000000189c81558ldrx0, [x19, #0x20]
0000000189c8155cmovx2, #0x00000000189c81560bl"_objc_msgSend$removeObjectAtIndex:"0000000189c81564ldrx0, [sp, #0x8]
0000000189c81568ldpx29, x30, [sp, #0x20]
0000000189c8156cldpx20, x19, [sp, #0x10]
0000000189c81570addsp, sp, #0x300000000189c81574autibsp0000000189c81578eorx16, x30, x30, lsl#10000000189c8157ctbzx16, #0x3e, 0x189c815840000000189c81580brk#0xc4710000000189c81584b0x18c9df050-[UIKeyboardTaskQueueaddDeferredTask:]:
0000000189c815fcpacibsp0000000189c81600subsp, sp, #0x300000000189c81604stpx20, x19, [sp, #0x10]
0000000189c81608stpx29, x30, [sp, #0x20]
0000000189c8160caddx29, sp, #0x200000000189c81610movx19, x00000000189c81614bl0x18c9df2000000000189c81618movx20, x00000000189c8161cmovx0, x190000000189c81620bl_objc_msgSend$lock0000000189c81624adrpx8, 330945 ; 0x1da9420000000000189c81628ldrx0, [x8, #0x510]
0000000189c8162cbl0x18c9dee300000000189c81630movx2, x200000000189c81634bl"_objc_msgSend$initWithTask:"0000000189c81638strx0, [sp, #0x8]
0000000189c8163cbl0x18c9df0a00000000189c81640ldrx0, [x19, #0x20]
0000000189c81644ldrx2, [sp, #0x8]
0000000189c81648bl"_objc_msgSend$addObject:"0000000189c8164cmovx0, x190000000189c81650bl_objc_msgSend$unlock0000000189c81654movx0, x190000000189c81658bl_objc_msgSend$continueExecutionOnMainThread0000000189c8165cldrx0, [sp, #0x8]
0000000189c81660ldpx29, x30, [sp, #0x20]
0000000189c81664ldpx20, x19, [sp, #0x10]
0000000189c81668addsp, sp, #0x300000000189c8166cautibsp0000000189c81670eorx16, x30, x30, lsl#10000000189c81674tbzx16, #0x3e, 0x189c8167c0000000189c81678brk#0xc4710000000189c8167cb0x18c9df050


🔗 相关链接

[1] Arm64 寄存器说明:https://developer.arm.com/documentation/den0024/a/The-ABI-for-ARM-64-bit-Architecture/Register-use-in-the-AArch64-Procedure-Call-Standard/Parameters-in-general-purpose-registers

[2] Arm64 汇编指令集说明:https://documentation-service.arm.com/static/6023d5512cb3723f20208db2

[3] NSConditionLock 条件状态锁:https://developer.apple.com/documentation/foundation/nsconditionlock/



相关文章
|
24天前
|
安全 Android开发 数据安全/隐私保护
深入探讨iOS与Android系统安全性对比分析
在移动操作系统领域,iOS和Android无疑是两大巨头。本文从技术角度出发,对这两个系统的架构、安全机制以及用户隐私保护等方面进行了详细的比较分析。通过深入探讨,我们旨在揭示两个系统在安全性方面的差异,并为用户提供一些实用的安全建议。
|
20天前
|
安全 数据安全/隐私保护 Android开发
深入探索iOS系统安全机制:从基础到高级
本文旨在全面解析iOS操作系统的安全特性,从基础的权限管理到高级的加密技术,揭示苹果如何构建一个既开放又安全的移动平台。我们将通过实例和分析,探讨iOS系统如何保护用户数据免受恶意软件、网络攻击的威胁,并对比Android系统在安全性方面的差异。
|
26天前
|
安全 搜索推荐 Android开发
揭秘安卓与iOS系统的差异:技术深度对比
【10月更文挑战第27天】 本文深入探讨了安卓(Android)与iOS两大移动操作系统的技术特点和用户体验差异。通过对比两者的系统架构、应用生态、用户界面、安全性等方面,揭示了为何这两种系统能够在市场中各占一席之地,并为用户提供不同的选择。文章旨在为读者提供一个全面的视角,理解两种系统的优势与局限,从而更好地根据自己的需求做出选择。
68 2
|
25天前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统的差异性及优化策略
在当今数字化时代,移动操作系统的竞争尤为激烈,其中iOS和Android作为市场上的两大巨头,各自拥有庞大的用户基础和独特的技术特点。本文旨在通过对比分析iOS与Android的核心差异,探讨各自的优势与局限,并提出针对性的优化策略,以期为用户提供更优质的使用体验和为开发者提供有价值的参考。
|
10天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
2月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
118 1
|
1天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
3天前
|
存储 前端开发 Swift
探索iOS开发:从新手到专家的旅程
本文将带您领略iOS开发的奇妙之旅,从基础概念的理解到高级技巧的掌握,逐步深入iOS的世界。文章不仅分享技术知识,还鼓励读者在编程之路上保持好奇心和创新精神,实现个人成长与技术突破。
|
18天前
|
安全 数据处理 Swift
深入探索iOS开发中的Swift语言特性
本文旨在为开发者提供对Swift语言在iOS平台开发的深度理解,涵盖从基础语法到高级特性的全面分析。通过具体案例和代码示例,揭示Swift如何简化编程过程、提高代码效率,并促进iOS应用的创新。文章不仅适合初学者作为入门指南,也适合有经验的开发者深化对Swift语言的认识。
38 9
|
14天前
|
设计模式 Swift iOS开发
探索iOS开发:从基础到高级,打造你的第一款App
【10月更文挑战第40天】在这个数字时代,掌握移动应用开发已成为许多技术爱好者的梦想。本文将带你走进iOS开发的世界,从最基础的概念出发,逐步深入到高级功能实现,最终指导你完成自己的第一款App。无论你是编程新手还是有志于扩展技能的开发者,这篇文章都将为你提供一条清晰的学习路径。让我们一起开始这段旅程吧!