lib/objcthin.rb in objcthin-0.2.0 vs lib/objcthin.rb in objcthin-0.3.0

- old
+ new

@@ -1,20 +1,23 @@ require "objcthin/version" require 'thor' require 'rainbow' require 'pathname' +require 'singleton' module Objcthin class Command < Thor desc 'findsel','find unused method sel' + method_option :prefix, :default => '', :type => :string, :desc => 'the class prefix you want find' def findsel(path) - Imp::UnusedClass.find_unused_sel(path) + Imp::UnusedClass.instance.find_unused_sel(path, options[:prefix]) end desc 'findclass', 'find unused class list' + method_option :prefix => :string, :default => '', :desc => 'the class prefix you want find' def findclass(path) - Imp::UnusedClass.find_unused_class(path) + Imp::UnusedClass.instance.find_unused_class(path, options[:prefix]) end desc'version','print version' def version puts Rainbow(Objcthin::VERSION).green @@ -22,11 +25,14 @@ end end module Imp class UnusedClass - def self.find_unused_sel(path) + + include Singleton + + def find_unused_sel(path, prefix) check_file_type(path) all_sels = find_impl_methods(path) used_sel = reference_selectors(path) unused_sel = [] @@ -35,16 +41,22 @@ unless used_sel.include?(sel) unused_sel += class_and_sels end end - puts Rainbow('below selector is unused:\n').red + puts Rainbow('below selector is unused:').red + if prefix + unused_sel.select! do |classname_selector| + current_prefix = classname_selector.byteslice(2, prefix.length) + current_prefix == prefix + end + end puts unused_sel end - def self.check_file_type(path) + def check_file_type(path) pathname = Pathname.new(path) unless pathname.exist? raise "#{path} not exit!" end @@ -56,59 +68,64 @@ end puts Rainbow('will begin process...').green pathname end - def self.find_impl_methods(path) - - app = %w[1 2] - + def find_impl_methods(path) apple_protocols = [ 'tableView:canEditRowAtIndexPath:', - 'commitEditingStyle:forRowAtIndexPath:', - 'tableView:viewForHeaderInSection:', - 'tableView:cellForRowAtIndexPath:', - 'tableView:canPerformAction:forRowAtIndexPath:withSender:', - 'tableView:performAction:forRowAtIndexPath:withSender:', - 'tableView:accessoryButtonTappedForRowWithIndexPath:', - 'tableView:willDisplayCell:forRowAtIndexPath:', + 'commitEditingStyle:forRowAtIndexPath:', + 'tableView:viewForHeaderInSection:', + 'tableView:cellForRowAtIndexPath:', + 'tableView:canPerformAction:forRowAtIndexPath:withSender:', + 'tableView:performAction:forRowAtIndexPath:withSender:', + 'tableView:accessoryButtonTappedForRowWithIndexPath:', + 'tableView:willDisplayCell:forRowAtIndexPath:', 'tableView:commitEditingStyle:forRowAtIndexPath:', 'tableView:didEndDisplayingCell:forRowAtIndexPath:', 'tableView:didEndDisplayingHeaderView:forSection:', 'tableView:heightForFooterInSection:', 'tableView:shouldHighlightRowAtIndexPath:', 'tableView:shouldShowMenuForRowAtIndexPath:', 'tableView:viewForFooterInSection:', 'tableView:willDisplayHeaderView:forSection:', 'tableView:willSelectRowAtIndexPath:', 'willMoveToSuperview:', - 'numberOfSectionsInTableView:', - 'actionSheet:willDismissWithButtonIndex:', - 'gestureRecognizer:shouldReceiveTouch:', - 'gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:', - 'gestureRecognizer:shouldReceiveTouch:', - 'imagePickerController:didFinishPickingMediaWithInfo:', - 'imagePickerControllerDidCancel:', - 'animateTransition:', - 'animationControllerForDismissedController:', - 'animationControllerForPresentedController:presentingController:sourceController:', - 'navigationController:animationControllerForOperation:fromViewController:toViewController:', - 'navigationController:interactionControllerForAnimationController:', - 'alertView:didDismissWithButtonIndex:', - 'URLSession:didBecomeInvalidWithError:', - 'setDownloadTaskDidResumeBlock:', - 'tabBarController:didSelectViewController:', - 'tabBarController:shouldSelectViewController:', - 'applicationDidReceiveMemoryWarning:', - 'application:didRegisterForRemoteNotificationsWithDeviceToken:', - 'application:didFailToRegisterForRemoteNotificationsWithError:', - 'application:didReceiveRemoteNotification:fetchCompletionHandler:', - 'application:didRegisterUserNotificationSettings:', - 'application:performActionForShortcutItem:completionHandler:', - 'application:continueUserActivity:restorationHandler:'].freeze + 'scrollViewDidEndScrollingAnimation:', + 'scrollViewDidZoom', + 'scrollViewWillEndDragging:withVelocity:targetContentOffset:', + 'searchBarTextDidEndEditing:', + 'searchBar:selectedScopeButtonIndexDidChange:', + 'shouldInvalidateLayoutForBoundsChange:', + 'textFieldShouldReturn:', + 'numberOfSectionsInTableView:', + 'actionSheet:willDismissWithButtonIndex:', + 'gestureRecognizer:shouldReceiveTouch:', + 'gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:', + 'gestureRecognizer:shouldReceiveTouch:', + 'imagePickerController:didFinishPickingMediaWithInfo:', + 'imagePickerControllerDidCancel:', + 'animateTransition:', + 'animationControllerForDismissedController:', + 'animationControllerForPresentedController:presentingController:sourceController:', + 'navigationController:animationControllerForOperation:fromViewController:toViewController:', + 'navigationController:interactionControllerForAnimationController:', + 'alertView:didDismissWithButtonIndex:', + 'URLSession:didBecomeInvalidWithError:', + 'setDownloadTaskDidResumeBlock:', + 'tabBarController:didSelectViewController:', + 'tabBarController:shouldSelectViewController:', + 'applicationDidReceiveMemoryWarning:', + 'application:didRegisterForRemoteNotificationsWithDeviceToken:', + 'application:didFailToRegisterForRemoteNotificationsWithError:', + 'application:didReceiveRemoteNotification:fetchCompletionHandler:', + 'application:didRegisterUserNotificationSettings:', + 'application:performActionForShortcutItem:completionHandler:', + 'application:continueUserActivity:restorationHandler:'].freeze # imp -[class sel] + sub_patten = /[+|-]\[.+\s(.+)\]/ patten = /\s*imp\s*(#{sub_patten})/ sel_set_patten = /set[A-Z].*:$/ sel_get_patten = /is[A-Z].*/ @@ -137,11 +154,11 @@ end imp.sort end - def self.reference_selectors(path) + def reference_selectors(path) patten = /__TEXT:__objc_methname:(.+)/ output = `/usr/bin/otool -v -s __DATA __objc_selrefs #{path}` sels = [] output.each_line do |line| @@ -157,11 +174,13 @@ module Imp class UnusedClass - def self.check_file_type(path) + include Singleton + + def check_file_type(path) pathname = Pathname.new(path) unless pathname.exist? raise "#{path} not exit!" end @@ -169,30 +188,49 @@ output = `#{cmd}` unless output.include?('Mach-O') raise 'input file not mach-o file type' end - puts Rainbow('will begin process...').green + #puts Rainbow('will begin process...').green pathname end - def self.split_segment_and_find(path) - command = "/usr/bin/otool -arch arm64 -V -o #{path}" + def split_segment_and_find(path, prefix) + + arch_command = "lipo -info #{path}" + arch_output = `#{arch_command}` + + arch = 'arm64' + if arch_output.include? 'arm64' + arch = 'arm64' + elsif arch_output.include? 'x86_64' + arch = 'x86_64' + elsif arch_output.include? 'armv7' + arch = 'armv7' + end + + command = "/usr/bin/otool -arch #{arch} -V -o #{path}" output = `#{command}` class_list_identifier = 'Contents of (__DATA,__objc_classlist) section' class_refs_identifier = 'Contents of (__DATA,__objc_classrefs) section' unless output.include? class_list_identifier raise Rainbow('only support iphone target, please use iphone build...').red end patten = /Contents of \(.*\) section/ - class_refs_patten = /^\d*\w*\s(0x\d*\w*).*/ + name_patten_string = '.*' + if prefix.length > 0 + name_patten_string = "#{prefix}.*" + end + vmaddress_to_class_name_patten = /^(\d*\w*)\s(0x\d*\w*)\s_OBJC_CLASS_\$_(#{name_patten_string})/ + class_list = [] class_refs = [] + used_vmaddress_to_class_name_hash = {} can_add_to_list = false can_add_to_refs = false output.each_line do |line| @@ -211,47 +249,41 @@ if can_add_to_list class_list << line end if can_add_to_refs && line - class_refs_patten.match(line) do |m| - class_refs << m[1] + vmaddress_to_class_name_patten.match(line) do |m| + unless used_vmaddress_to_class_name_hash[m[2]] + used_vmaddress_to_class_name_hash[m[2]] = m[3] + end end end end + # remove cocoapods class + podsd_dummy = 'PodsDummy' - class_list_address_patten = /^(\d*\w*)\s(0x\d*\w*)/ - class_name_patten = /name\s0x\d*\w*\s(.*)/ - - current_key = nil - class_name_address_hash = {} - + vmaddress_to_class_name_hash = {} class_list.each do |line| - if class_list_address_patten.match?(line) - current_key = class_list_address_patten.match(line)[2] + next if line.include? podsd_dummy + vmaddress_to_class_name_patten.match(line) do |m| + vmaddress_to_class_name_hash[m[2]] = m[3] end - - if class_name_patten.match?(line) && current_key - value = class_name_patten.match(line)[1] - class_name_address_hash[current_key] = value - current_key = nil - end end - result = class_name_address_hash - class_refs.each do |line| - if class_name_address_hash.keys.include?(line) - result.delete(line) + result = vmaddress_to_class_name_hash + vmaddress_to_class_name_hash.each do |key, value| + if used_vmaddress_to_class_name_hash.keys.include?(key) + result.delete(key) end end result end - def self.find_unused_class(path) + def find_unused_class(path, prefix) check_file_type(path) - result = split_segment_and_find(path) + result = split_segment_and_find(path, prefix) puts result.values end end \ No newline at end of file