Question

I know this is not allowed in real apps in favor of user's privacy and security. But for pure academical purpose I am trying to send a message without presenting MessageComposer UI like this.

MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init];

if([MFMessageComposeViewController canSendText]) {
    picker.recipients = [NSArray arrayWithObject:@"1234"];
    picker.body = @"Hello";
    [picker performSelector:@selector(smsComposeControllerSendStarted:) withObject:[UIButton buttonWithType:UIButtonTypeCustom]];
}

Nothing happens as a result. Not even a a failure exception on console. Neither I see a message on SMS app. The person I sent the message also didn't get it.

One Can find the list of iVars and Methods of MFMessageComposeViewController here.

https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks/MessageUI.framework/MFMessageComposeViewController.h

And I wrote a quick code to verify if these are the items actually present in MFMessageComposeViewController.

uint varCount = 0;
Class cls= [MFMessageComposeViewController class];
Ivar *vars =  class_copyIvarList(cls, &varCount);

for (uint i = 0; i < varCount; i++) {
   Ivar var = vars[i];
   const char* name = ivar_getName(var);
   const char* typeEncoding = ivar_getTypeEncoding(var);
   printf("iVar%i------------> %s\n",i+1,name);
}    
free(vars);

Method *imps = class_copyMethodList(cls, &varCount);
for (uint i = 0; i < varCount; i++) {
    SEL sel = method_getName(imps[i]);        
    printf("Method%i------------> %s\n",i+1,[(NSStringFromSelector(sel)) UTF8String]);
}

It produces the following output:

iVar1------------> _messageComposeDelegate
iVar2------------> _recipients
iVar3------------> _body
iVar4------------> _subject
iVar5------------> _mutableAttachmentURLs
iVar6------------> _currentAttachedVideoCount
iVar7------------> _currentAttachedAudioCount
iVar8------------> _currentAttachedImageCount
iVar9------------> _temporaryAttachmentURLs
iVar10------------> _attachments
Method1------------> disableUserAttachments
Method2------------> setCurrentAttachedVideoCount:
Method3------------> setCurrentAttachedAudioCount:
Method4------------> setCurrentAttachedImageCount:
Method5------------> _MIMETypeForURL:
Method6------------> _isVideoMIMEType:
Method7------------> _isAudioMIMEType:
Method8------------> _isImageMIMEType:
Method9------------> mutableAttachmentURLs
Method10------------> _contentTypeForMIMEType:
Method11------------> _updateAttachmentCountForAttachmentURL:
Method12------------> _buildAttachmentInfoForAttachmentURL:andAlternameFilename:
Method13------------> temporaryAttachmentURLs
Method14------------> canAddAttachmentURL:
Method15------------> addAttachmentData:withAlternateFilename:
Method16------------> _setCanEditRecipients:
Method17------------> messageComposeDelegate
Method18------------> setMutableAttachmentURLs:
Method19------------> currentAttachedVideoCount
Method20------------> currentAttachedAudioCount
Method21------------> currentAttachedImageCount
Method22------------> setTemporaryAttachmentURLs:
Method23------------> dealloc
Method24------------> viewWillAppear:
Method25------------> initWithNibName:bundle:
Method26------------> automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers
Method27------------> setModalPresentationStyle:
Method28------------> body
Method29------------> setSubject:
Method30------------> subject
Method31------------> setMessageComposeDelegate:
Method32------------> setBody:
Method33------------> addAttachmentURL:withAlternateFilename:
Method34------------> addAttachmentData:typeIdentifier:filename:
Method35------------> attachmentURLs
Method36------------> attachments
Method37------------> recipients
Method38------------> smsComposeControllerCancelled:
Method39------------> smsComposeControllerSendStarted:
Method40------------> setRecipients:

It appears to me that smsComposeControllerSendStarted: method could be more of a delegate than actual function that starts the message sending. In the above method list none of the method signatures looks closer to sendMessage: or something similar to a function that actually sends the message.

My Questions are:

1) Is that all really MFMessageComposeViewController has under the hood. Or does it have some class clusters that are not accessible via runtime functions?

2) How to find out the actual messageSend method and it's implementation's class?

Any ideas would be greatly appreciated.

Thanks.

Était-ce utile?

La solution

Inside MFMessageComposeViewController uses CKSMSComposeController as the actual UI from private ChatKit.framework which uses a whole bunch of other classes (CKSMSComposeQueuingRemoteViewControllerProxy, CKSMSComposeRemoteViewController, CKSMSComposeViewServiceController) including XPC interface XPCProxy<CKSMSCompose>.

UPDATE 1

After reading quellish comment I've found this http://oleb.net/blog/2012/10/remote-view-controllers-in-ios-6/ Looks like MFMessageComposeViewController and similar classes launch another process for their purposes. These processes are XPC services that implement certaint protocols through which your app communicates with them. These processes are signed with all required entitlements. Probably /Applications/MessagesViewService.app/MessagesViewService is what actually sends SMS messages. This binary is signed with com.apple.messages.composeclient which is required in order to send messages with this code that I've found in ChatKit.framework. I think there is a way to communicate with that XPC service manually to send SMS but it will be difficult. Not something you could do fooling around with class-dump and similar simple tools that don't really give you much information.

UPDATE 2

I've managed to get to the bottom of this. I've thrown away all the helpers classes, all UI stuff that doesn't really communicates with XPC service. What's left is enough to display SMS view controller and send some methods to the XPC service without anything in the way.

When we display SMS composition UI iOS indeed launches /Applications/MessagesViewService.app/MessagesViewService process. That's the XPC service. You can try to kill it while your app is displaying it - you'll get black screen which means it's the right one.

CKSMSComposeRemoteViewController is the view controller that displays SMS UI. It's a subclass of _UIRemoteViewController class. Basically, it manages connection between our app and the actual UI that is running inside XPC service in other process. Here is how I obtain it's instance inside my -(void)viewDidLoad method

_UIAsyncInvocation* cancelationInvocation = 
[CKSMSComposeRemoveViewController requestViewController:@"CKSMSComposeViewServiceController"
                        fromServiceWithBundleIdentifier:@"com.apple.mobilesms.compose"
                        connectionHandler:
^(CKSMSComposeRemoteViewController* obj, NSError* error){
    smsViewController = obj;
    [smsViewController setDelegate:self];

    smsViewControllerProxy = [smsViewController serviceViewControllerProxy];
}]; 

smsViewController is a remote view controller instance. smsViewControllerProxy is XPCProxy<CKSMSCompose> instance that implements CKSMSComposeViewServiceProtocol protocol - method calls will be forwarded to the XPC service. XPCProxy doesn't really implements those methods. It implements forwardInvocation: method in order to forward calls to XPC connection.

_UIAsyncInvocation, looking at the ivar name cancelationInvocation, is used in order to cancel XPC messages. It's invoked only in CKSMSComposeController viewServiceDidTerminateWithError: method.

Here is how I display the controller:

[self presentViewController:smsViewController animated:YES completion:NULL];

You probably noticed [smsViewController setDelegate:self]. Delegate must implement CKSMSComposeRemoteViewControllerDelegate protocol or app will crash with exception (unrecognized selector). Here is what I implemented:

-(void)smsComposeControllerAppeared
{
}

-(void)smsComposeControllerCancelled
{
}

-(void)smsComposeControllerDataInserted
{
}

That's enough to get going.

And here is how can we send message to the remote view controller:

[smsViewControllerProxy insertTextPart:@"Some text"];

This will insert the text into SMS text field and call smsComposeControllerDataInserted delegate method.

Bearing all of this in mind, I no longer think we can send SMS messages without the UI. Actual UI is running in another process, we don't have any control over it. We can set some fields but that's it. I was hoping that there is a way but, the truth is, it's no wonder we can't. Remote views were introduced in iOS 6 to solve this exact security issue - on iOS 5 there was a way to send SMS without user permission and some AppStore apps did that. So kudos to Apple.

UPDATE 3

I've managed to dump XPC messages being sent while we interact with SMS UI. A few words about logs format. First line - where message was intercepted. It's either function name or Incoming event which means it's incoming message from the service. Connection name is just XPC connection name. Message is XPC message dictionary contents. Message data - some XPC messages contain binary data at "d"->"r" keys. It's a serialized binary property list that can't be deserialized with NSPropertyListSerialization - it's in some new format. Instead you need to use NSXPCDecoder -(id)_initWithRootXPCObject:(xpc_object_t) from Foundation.framework. And now the dumps:

SMS UI being presented http://pastebin.com/NVEpujSh

SMS UI after "Send" button is pressed http://pastebin.com/BYXd2djF

There are messages sent and received while you edit SMS fields but they correspond only to UI events. For example, who's become first responder, stuff that doesn't really tell anything about what's going on in SMS UI.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top