需求: 在IOS10系统上 实现备忘录字数统计功能
环境:5S 10.1.1系统
1. 在界面上显示数字
打印界面UI
Z0041:~ root# ps -e | grep /Applications/
584 ?? 0:07.08 /Applications/MobileMail.app/MobileMail
686 ?? 0:00.36 /Applications/ServerDocuments.app/PlugIns/ServerFileProvider.appex/ServerFileProvider
896 ?? 0:02.39 /Applications/MobileNotes.app/MobileNotes
912 ttys000 0:00.01 grep /Applications/
Z0041:~ root#
找到 ICTextView 显示的内容
UIApp.keyWindow.recursiveDescription().toString()
<ICTextView: 0x122031e00; baseClass = UITextView; frame = (0 0; 320 568);
text = '\u5b9e\u6218\u5907\u5fd8\u5f55iOS\u5907\u5fd8\u5f55\u60f3\u5fc5\u662f\u679c\u7c89\u6700\u719f\u6089\u7684app\u4e4b...';
clipsToBounds = YES; gestureRecognizers = <NSArray: 0x17405dc10>;
layer = <CALayer: 0x1742200e0>; contentOffset: {0, -64}; contentSize: {320, 85}>
通过 nextResponder找到 Controller:ICTextViewController
cy# [#0x122031e00 nextResponder]
#"<ICTextViewController: 0x121e77d90>"
尝试设置:ICTextViewController setTitle:@"ccc"发现无效
然后就想着在 ICTextView 中添加一个Label 来显示
cy# @import mjcript
{}
cy# var lb=[[UILabel alloc] init]
#"<UILabel: 0x121d228b0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x174282e40>>"
cy# lb.frame=MJRectMake(265,0,50,20);
{0:{0:265,1:0},1:{0:50,1:20}}
cy# lb.text="20"
"20"
cy# [#0x122031e00 addSubview:lb]
样子的确不咋滴,不过功能实现应该没问题
2. 找到数据
查看 ICTextViewController 的头文件,发现textview,那么从控制器就可以直接访问,textview的text的值
@property(retain, nonatomic) ICNote *note; // @synthesize note=_note;
@property(readonly, nonatomic) ICTextView *textView;
cy# var ict=#0x121e77d90
#"<ICTextViewController: 0x121e77d90>"
cy# ict.textView
#"<ICTextView: 0x122031e00; baseClass = UITextView; frame = (0 0; 320 568); text = '\xe5\xae\x9e\xe6\x88\x98\xe5\xa4\x87\xe5\xbf\x98\xe5\xbd\x95\niOS\xe5\xa4\x87\xe5\xbf\x98\xe5\xbd\x95\xe6\x83\xb3\xe5\xbf\x85\xe6\x98\xaf\xe6\x9e\x9c\xe7\xb2\x89\xe6\x9c\x80\xe7\x86\x9f\xe6\x82\x89\xe7\x9a\x84app\xe4\xb9\x8b...'; clipsToBounds = YES; gestureRecognizers = <NSArray: 0x17405dc10>; layer = <CALayer: 0x1742200e0>; contentOffset: {0, -64}; contentSize: {320, 85}>"
cy# ict.textView.text
@"\xe5\xae\x9e\xe6\x88\x98\xe5\xa4\x87\xe5\xbf\x98\xe5\xbd\x95\niOS\xe5\xa4\x87\xe5\xbf\x98\xe5\xbd\x95\xe6\x83\xb3\xe5\xbf\x85\xe6\x98\xaf\xe6\x9e\x9c\xe7\xb2\x89\xe6\x9c\x80\xe7\x86\x9f\xe6\x82\x89\xe7\x9a\x84app\xe4\xb9\x8b\xe4\xb8\x80 "
cy#
ICTextViewController 中也存在note 数据
查看NotesShared.framework 私有框架下的ICNote 文件,以及 noteData 数据,打印结果,发现其 data 数据是加密的,不知道是不是 文本信息,也尝试浏览其他属性,没有发现可以的数据对象
cy# ict.note
#"<ICNote: 0x170382630> (entity: ICNote; id: 0xd000000000140000 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICNote/p5> ; data: {\n account = \"0xd0000000000c0004 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICAccount/p3>\";\n assetCryptoInitializationVector = nil;\n assetCryptoTag = nil;\n attachmentViewType = 0;\n attachments = (\n );\n cloudState = \"0xd000000000140002 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICCloudState/p5>\";\n creationDate = \"2019-03-25 05:54:21 +0000\";\n cryptoInitializationVector = nil;\n cryptoIterationCount = 0;\n cryptoSalt = nil;\n cryptoTag = nil;\n cryptoWrappedKey = nil;\n encryptedValuesJSON = nil;\n folders = (\n \"0xd000000000040008 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICFolder/p1>\"\n );\n foldersModificationDate = nil;\n identifier = \"73463C4C-2A5F-4C74-BF8E-4E438C41DF60\";\n integerId = nil;\n isPasswordProtected = 0;\n lastViewedModificationDate = \"1983-11-07 17:37:00 +0000\";\n legacyContentHashAtImport = nil;\n legacyImportDeviceIdentifier = nil;\n legacyManagedObjectIDURIRepresentation = nil;\n legacyModificationDateAtImport = nil;\n legacyNoteIntegerId = 0;\n legacyNoteWasPlainText = nil;\n markedForDeletion = 0;\n minimumSupportedNotesVersion = 0;\n modificationDate = \"2019-03-27 08:14:38 +0000\";\n needsInitialFetchFromCloud = 0;\n needsToBeFetchedFromCloud = nil;\n needsToSaveUserSpecificRecord = 1;\n noteData = \"0xd000000000080006 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICNoteData/p2>\";\n noteHasChanges = 0;\n passwordHint = nil;\n serverRecord = nil;\n serverShare = nil;\n snippet = \"iOS\\U5907\\U5fd8\\U5f55\\U60f3\\U5fc5\\U662f\\U679c\\U7c89\\U6700\\U719f\\U6089\\U7684app\\U4e4b\\U4e00\";\n thumbnailAttachmentIdentifier = nil;\n title = \"\\U5b9e\\U6218\\U5907\\U5fd8\\U5f55\";\n unappliedEncryptedRecord = nil;\n userSpecificServerRecord = nil;\n zoneOwnerName = nil;\n})"
cy# ict.note.noteData
#"<ICNoteData: 0x1742c5a90> (entity: ICNoteData; id: 0xd000000000080006 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICNoteData/p2> ; data: {\n cryptoInitializationVector = nil;\n cryptoTag = nil;\n data = <1f8b0800 00000000 00034d93 4b6bd450 14c77373 6fc6dbdb 69e74c5a c79a3a1a a7b5a663 153f8120 d69d20c5 4fa0a2d2 8d74>;\n note = \"0xd000000000140000 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICNote/p5>\";\n})"
3. 寻找实时监测数据数据变化的方法
直接用logfiy跟踪,发现其在初始化时会执行loadView 和 layoutManager方法,loadView执行之后,界面的view应该时初始化好了,并且每一次修改内容时也会调用 layoutManager,就想着在这两个方法上做文章,loadView 创建label ,layoutManager 计算字数
使用IDA找到这两个方法的,并且在layoutManager方法基地址上下断点
(lldb) image list -f -o
[ 0] /Applications/MobileNotes.app/MobileNotes 0x00000000000fc000(0x00000001000fc000)
(lldb) br s -a 0x1001CB170
Breakpoint 2: where = MobileNotes`_mh_execute_header + 837984, address = 0x00000001001cb170
Process 1005 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
frame #0: 0x00000001001cb170 MobileNotes`_mh_execute_header + 848240
MobileNotes`_mh_execute_header:
-> 0x1001cb170 <+848240>: stp x22, x21, [sp, #-0x30]!
0x1001cb174 <+848244>: stp x20, x19, [sp, #0x10]
0x1001cb178 <+848248>: stp x29, x30, [sp, #0x20]
0x1001cb17c <+848252>: add x29, sp, #0x20 ; =0x20
Target 0: (MobileNotes) stopped.
(lldb) po $x0
<ICTextViewController: 0x121ec8850>
(lldb) po [$x0 textView]
<ICTextView: 0x122189000; baseClass = UITextView; frame = (0 0; 0 0); text = '实战备忘录
iOS备忘录想必是果粉最熟悉的app之...'; clipsToBounds = YES; gestureRecognizers = <NSArray: 0x174444b60>; layer = <CALayer: 0x174225d60>; contentOffset: {0, 8}; contentSize: {0, 61}>
(lldb) po [[$x0 textView] text]
实战备忘录
iOS备忘录想必是果粉最熟悉的app之一
LayoutManager 方法中可以获取到控制器,并且可以访问到数据
4.整理结果,编写代码
构造类对应的头文件
Characount.h
@interface ICTextView : UITextView
@end
@interface ICTextViewController:UIViewController
@property(readonly, nonatomic) ICTextView *textView;
@end
编写 Tweak.xm
#import <CoreGraphics/CGGeometry.h>
#import "Characount.h"
%hook ICTextViewController
- (id *)layoutManager {
%log;
NSString *content=self.textView.text;
NSString *contentLength=[NSString stringWithFormat:@"%lu",(unsigned long)[content length]];
NSLog(@"contentLength:%@",contentLength);
UILabel *lb =(UILabel *)[self.textView viewWithTag:1000];
lb.text=contentLength;
id * r = %orig;
return r;
}
- (void)loadView {
%log;
%orig;
UILabel *lb=[[[UILabel alloc] initWithFrame:CGRectMake(265,0,50,20)] autorelease];
lb.tag=1000;
[self.textView addSubview:lb];
}
%end