How do you match strings in custom Instruments start-pattern? - instruments

I am having trouble matching a string literal in start-pattern in a custom instrument (see WWDC 2018 video Creating Custom Instruments) in Xcode’s Instruments.
For example, this start-pattern works ...
<start-pattern>
<message>?name ?value</message>
</start-pattern>
<column>
<mnemonic>name</mnemonic>
<title>Name</title>
<type>string</type>
<expression>?name</expression>
</column>
<column>
<mnemonic>value</mnemonic>
<title>Value</title>
<type>uint64</type>
<expression>?value</expression>
</column>
... with this OSSignpost:
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "Interval", signpostID: id, "Foo %d", 42)
...
os_signpost(.end, log: log, name: "Interval", signpostID: id)
The ?name ?value pattern works, capturing "Foo" as the name and 42 as the value. Fine.
But when I try using string literals in my message, it does not work, e.g. this start-pattern...
<start-pattern>
<message>"Name:" ?name ",Value:" ?value</message>
</start-pattern>
... with this os_signpost ...
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "Interval", signpostID: id, "Name:Foo,Value:%d", 42)
...
os_signpost(.end, log: log, name: "Interval", signpostID: id)
This "Name:" ?name ",Value:" ?value pattern does not work, though the documentation suggests it should.
What am I doing wrong?

If you use string literals in the start-pattern, you must use printf-style format string.
Thus, this will not work:
os_signpost(.begin,
log: log,
name: "Interval",
signpostID: id,
"Name:Foo,Value:%d",
42)
But if we move the "Foo" value out of the format string, and make it a parameter, it will work:
os_signpost(.begin,
log: log,
name: "Interval",
signpostID: id,
"Name:%{public}#,Value:%d",
"Foo",
42)
The issue rests in this idiosyncratic detail of the format string within the os_signpost call. One may have assumed that start-pattern/message parses the result of the os_signpost final output, but it appears (when using string literals in the message key, at least) that it is actually parsing the format string, itself.
FWIW, this is my final, admittedly basic, interval instrument:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Instruments Developer Help: https://help.apple.com/instruments/developer/mac/current/ -->
<package>
<id>com.robertmryan.CustomInterval</id>
<version>0.1</version>
<title>Custom OS Signpost Interval</title>
<owner>
<name>Robert Ryan</name>
</owner>
<import-schema>os-signpost</import-schema>
<!-- See https://help.apple.com/instruments/developer/mac/current/#/dev536412616 -->
<os-signpost-interval-schema>
<id>custom-interval-schema</id>
<title>Interval</title>
<owner>
<name>Robert Ryan</name>
</owner>
<purpose>Provide mechanism for multicolored intervals posted by `os_signpost`; The string generated by `os_signpost` must be in form of "Label:%d,Concept:%{public}#", where "Label" is string that will control what text appears in the interval, and "Concept" is one of the strings listed in https://help.apple.com/instruments/developer/mac/current/#/dev66257045 that dictates the color of the interval. No spaces after the commas within this string.</purpose>
<note>That message must use that printf-style format, not embedding the values in the format string literal.</note>
<!-- you can constrain this to a particular subsystem if you'd like:
<subsystem>"com.domain.MyApp"</subsystem>
-->
<category>"Interval"</category>
<name>?name</name>
<start-pattern>
<message>"Label:" ?label ",Concept:" ?concept</message>
</start-pattern>
<column>
<mnemonic>name</mnemonic>
<title>Name</title>
<type>string</type>
<expression>?name</expression>
</column>
<column>
<mnemonic>label</mnemonic>
<title>Label</title>
<type>string</type>
<expression>?label</expression>
</column>
<column>
<mnemonic>concept</mnemonic>
<title>Concept</title>
<type>event-concept</type>
<expression>?concept</expression>
</column>
</os-signpost-interval-schema>
<instrument>
<id>com.robertmryan.CustomInterval.instrument</id>
<title>Custom OS Signpost Interval</title>
<category>Behavior</category>
<purpose>Provide multi-colored intervals as dictated by the "event-concept" parsed from the `start-pattern` string.</purpose>
<icon>Generic</icon>
<create-table>
<id>custom-interval-table</id>
<schema-ref>custom-interval-schema</schema-ref>
</create-table>
<graph>
<title>Custom Interval Graph</title>
<lane>
<title>Interval</title>
<table-ref>custom-interval-table</table-ref>
<plot-template>
<instance-by>name</instance-by>
<label-format>%s</label-format>
<value-from>name</value-from>
<color-from>concept</color-from>
<label-from>label</label-from>
<qualified-by>layout-qualifier</qualified-by>
</plot-template>
</lane>
</graph>
<list>
<title>Custom Interval List</title>
<table-ref>custom-interval-table</table-ref>
<column>name</column>
<column>label</column>
<column>concept</column>
<column>start</column>
<column>duration</column>
</list>
</instrument>
</package>
And my type for posting a custom interval for that instrument:
//
// InstrumentsInterval.swift
//
// Created by Robert Ryan on 6/5/21.
//
import Foundation
import os.log
/// EventConcept enumeration
///
/// This is used to dictate the color of the intervals in our custom instrument.
/// See [Event Concept Engineering Type](https://help.apple.com/instruments/developer/mac/current/#/dev66257045).
enum EventConcept: String {
case success = "Success"
case failure = "Failure"
case fault = "Fault"
case critical = "Critical"
case error = "Error"
case debug = "Debug"
case pedantic = "Pedantic"
case info = "Info"
case signpost = "Signpost"
case veryLow = "Very Low"
case low = "Low"
case moderate = "Moderate"
case high = "High"
case red = "Red"
case orange = "Orange"
case blue = "Blue"
case purple = "Purple"
case green = "Green"
}
/// Interval to be shown in custom instrument when profiling app
struct InstrumentsInterval {
static let category = "Interval"
let name: StaticString
let label: String
let concept: EventConcept
let log: OSLog
let id: OSSignpostID
init(name: StaticString, label: String, concept: EventConcept = .debug, log: OSLog) {
self.name = name
self.concept = concept
self.label = label
self.log = log
self.id = OSSignpostID(log: log)
}
/// Block based interval
func perform<T>(block: () throws -> T) rethrows -> T {
begin()
defer { end() }
return try block()
}
/// Manually begin an interval
func begin() {
os_signpost(.begin, log: log, name: name, signpostID: id, "Label:%{public}#,Concept:%{public}#", label, concept.rawValue)
}
/// Manually end an interval
func end() {
os_signpost(.end, log: log, name: name, signpostID: id)
}
}
Which you can then use like so:
let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: InstrumentsInterval.category)
let interval = InstrumentsInterval(name: "Foo", label: "1", concept: .red, log: log)
interval.perform {
...
}
Which can yield the following in Instruments (in this example, I was constraining app to four concurrent tasks at a time):

Related

SwiftUI Can't Pass String to KeyPath

I have an app that references a large dataset that I load from an external public
site as a comma separated value file. I parse the data to a an array of a model called
WaterPointModel. An abbreviated version is:
struct WaterPointModel: Identifiable {
let id = UUID()
let STATE: String
let COUNTY: String
let AQWFrTo: Double
let AQWGWSa: Double
let AQWGWTo: Double
//many more
}
I then want to summarize(reduce) the data. My function for this is:
func sumOnAttributeUSList(sumField: KeyPath<WaterPointModel,Double>) -> Double {
return dataStore.waterPointModels.map({ $0[keyPath:sumField] }).reduce(0, +)
}
Next I want to call this to build a report:
let aqWFrTo = sumOnAttributeUSList(sumField: \.AQWFrTo)
This all works. However there are other reports where I need to pass a string to
create that keypath. Say I have a lookup table where I lookup "abc" and get "AQWFrTo". I
would like to do something like this in a loop:
let abc = "AQWFrTo"
let aqWFrTo = sumOnAttributeUSList(sumField: \WaterPointModel.abc)
I have not been able to make any version of this work. Any guidance would be appreciated.
Xcode 13.3.1, iOS 15.4
A simple approach is this:
func toKeypath(_ str: String) -> KeyPath<WaterPointModel,Double>? { // <-- with or without optional
switch str {
case "AQWFrTo": return \.AQWFrTo
case "AQWGWSa": return \.AQWGWSa
case "AQWGWTo": return \.AQWGWTo
// ...
default: return nil // <-- or a default Keypath
}
}
let aqWFrTo = sumOnAttributeUSList(sumField: toKeypath("AQWFrTo"))

Make categories from string in XML data

I looked everywhere but I don't see anything specific to the thing I am trying to achieve, especially with iOS/swift. I would like to make age categories from the XML parser request I am calling in my app.
The age information will have to be called from the tag in the xml data:
<item>
<description>ANTHONY JOHNSON, Age: 17 From: PEORIA, IL</description></item>
I already have a call to get the description tag in full:
func parser(_ parser: XMLParser, didEndElement elementName: String,
namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "description" {
self.person.desc = self.foundCharacters; }
if elementName == "item" {
tempData.desc = self.person.desc; }
Item class:
class Item {
var desc = "";
}
I just need to make categories from the age value only. I plan on using them with an enum:
enum ageCategories: String {
case ageUnder10 = "Age 10 and under"
case age10Plus = "Age 10-15"
case age15Plus = "Age 15+"
}
My questions are:
Do I make a call in the XML parser to get the ages? How would I organize the call to get the ages part of the description string only and put them in categories?
OR
Is there an easier way to call just the ages from the string I have already called in the XML parser and then make categories with that? i.e: replacingOccurrences(range searchRange:"Age")
Your question boils down to finding an age in the description string. The following shows one solution using Scanner. This code assumes that the age will be a number after the text "Age:".
let desc = "ANTHONY JOHNSON, Age: 17 From: PEORIA, IL"
var ageRange: ageCategories? = nil
let scanner = Scanner(string: desc)
if scanner.scanUpTo("Age:", into: nil) && scanner.scanString("Age:", into: nil) {
var age = 0
if scanner.scanInt(&age) {
switch age {
case 0..<10:
ageRange = .AgeUnder10
case 10..<15:
ageRange = .Age10Plus
case 15...:
ageRange = .Age15Plus
default:
break
}
}
}
if let ageRange = ageRange {
print("Found age range of \(ageRange)")
} else {
print("No valid age found")
}
Adjust the ranges in the case statements to match your actual needs. The strings you are assigning to your enum values are ambiguous. Is 10 supposed to be .AgeUnder10 or .Age10Plus?
FYI - enum values should start with lowercase letters and the enum name should start with uppercase. So your enum should be:
enum AgeCategories: String {
case ageUnder10 = "Age under 10"
case age10Plus = "Age 10-14"
case age15Plus = "Age 15+"
}

Swift: how to censor/filter text entered for swear words, etc?

I just want to see whether there is an established way to do this, or how one would go about this.
I have a text field that essentially acts as a form in my iOs app where a user can post something. I can't have users posting swear words/inappropriate crap, so I want to filter out and display an error if the string they enter contains one of these words.
How do other apps in Swift do this? Do they just search through the string to see if it contains the word (obviously not within other words, but standing alone) or is there another method?
How can I accurately filter out the swear words from my user post in Swift?
Construct a list of words you consider to be swear words, and simply check the user entered string whether any of these words are contained within the string.
Swift 3:
import Foundation
func containsSwearWord(text: String, swearWords: [String]) -> Bool {
return swearWords
.reduce(false) { $0 || text.contains($1.lowercased()) }
}
// example usage
let listOfSwearWords = ["darn", "crap", "newb"]
/* list as lower case */
let userEnteredText1 = "This darn didelo thread is a no no."
let userEnteredText2 = "This fine didelo thread is a go."
print(containsSwearWord(text: userEnteredText1, swearWords: listOfSwearWords)) // true
print(containsSwearWord(text: userEnteredText2, swearWords: listOfSwearWords)) // false
Swift 2.2:
import Foundation
func containsSwearWord(text: String, swearWords: [String]) -> Bool {
return swearWords
.reduce(false) { $0 || text.containsString($1.lowercaseString) }
}
// example usage
let listOfSwearWords = ["darn", "crap", "newb"]
/* list as lower case */
let userEnteredText1 = "This darn didelo thread is a no no."
let userEnteredText2 = "This fine didelo thread is a go."
print(containsSwearWord(userEnteredText1, swearWords: listOfSwearWords)) // true
print(containsSwearWord(userEnteredText2, swearWords: listOfSwearWords)) // false
I created a class that enables you to feed a string in and remove profanity. Here's a link to the repo.
Here's the code:
class ProfanityFilter: NSObject {
static let sharedInstance = ProfanityFilter()
private override init() {}
// Customize as needed
private let dirtyWords = "\\b(ducker|mother ducker|motherducker|shot|bad word|another bad word|)\\b"
// Courtesy of Martin R
// https://stackoverflow.com/users/1187415/martin-r
private func matches(for regex: String, in text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: [.caseInsensitive])
let nsString = text as NSString
let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
return results.map { nsString.substring(with: $0.range)}
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
public func cleanUp(_ string: String) -> String {
let dirtyWords = matches(for: self.dirtyWords, in: string)
if dirtyWords.count == 0 {
return string
} else {
var newString = string
dirtyWords.forEach({ dirtyWord in
let newWord = String(repeating: "😲", count: dirtyWord.characters.count)
newString = newString.replacingOccurrences(of: dirtyWord, with: newWord, options: [.caseInsensitive])
})
return newString
}
}
}
Usage:
yourLabel.text = ProfanityFilter.sharedInstance.cleanUp(yourString)
Extension checking for foul language.
Swift 4.2
Example Usage:
"poop".containsBadWord()
Extension:
extension String {
func containsBadWord()->Bool {
//Sorry for bad words
let badWords = ["insert","bad","words","here","poop"]
for word in badWords {
if lowercased().contains(word) {
return true
}
}
return false
}
}
I would suggest looking into an API to which you can submit a string and get a JSON response containing information such as:
Is the string bad?
Total # of bad words contained in string
An array containing all recognized bad words
A censored version of the input string
I found a couple sources via Google. Check these out and do a little more research to find if an API is the best fit for you and which one you should use. I would assume that using an API like the one I have listed below would be the most practical approach, as you would NOT have to compile a list of "bad" words yourself and use resources from the device to sort through the list (which can contain thousands of words).
Rather, you can simply submit a string using the API to get a network response containing the data in JSON format from the API server.
Why not let the API Server do the logic for you and just spit out an answer?
NeutrinoAPI
If this method returns a range,
str.range(of: "darn|crap|newb", options: [.regularExpressionSearch, .caseInsensitiveSearch], range: str.startIndex..<str.endIndex, locale:nil)
an offensive word has been found. While this method can be used to remove the offending strings:
str.replacingOccurrences(of: "darn|crap|newb", with: "", options: [.regularExpressionSearch, .caseInsensitiveSearch])
If you filter bad words locally you wouldn’t easily accommodate many languages, also as new bad words appear, you would have to waste a developers time manually putting in bad words, alternatively, there are apis designed for this purpose: https://www.moderatecontent.com/documentation/badwords

Is there any dump() like function returns a string?

I like Swift's dump() function like this,
class MyClass {
let a = "Hello"
let b = "Bye!"
init() {}
}
let myClass = MyClass()
dump(myClass) // Printed out these lines to Xcode's console
/*
▿ MyClass #0
- a: Hello
- b: Bye!
*/
But dump() doesn't return a string. It just prints out to the console, and returns 1st parameter itself.
public func dump<T>(x: T, name: String? = default, indent: Int = default, maxDepth: Int = default, maxItems: Int = default) -> T
Is there any dump() like function returns a string?
From: https://github.com/apple/swift/blob/master/stdlib/public/core/OutputStream.swift
/// You can send the output of the standard library's `print(_:to:)` and
/// `dump(_:to:)` functions to an instance of a type that conforms to the
/// `TextOutputStream` protocol instead of to standard output. Swift's
/// `String` type conforms to `TextOutputStream` already, so you can capture
/// the output from `print(_:to:)` and `dump(_:to:)` in a string instead of
/// logging it to standard output.
Example:
let myClass = MyClass()
var myClassDumped = String()
dump(myClass, to: &myClassDumped)
// myClassDumped now contains the desired content. Nothing is printed to STDOUT.
try this if you want :
let myClass = MyClass()
print("----> \(String(MyClass))")
print("----> \(String(dump(myClass))) ")
UPDATE:
you can combine the string you like use Mirror:
let myClass = MyClass()
let mirror = Mirror(reflecting: myClass)
var string = String(myClass) + "\n"
for case let (label?, value) in mirror.children {
string += " - \(label): \(value)\n"
}
print(string)
hope it be helpful :-)

Returning a processed String of an Enum on Swift iOS

I was just creating a localization module for my project, and as I'm new to Swift I had an idea of if the following was possible.
I have an enum like this:
enum Localizations : String
{
case StringId1 = "string_to_translate_1"
case StringId2 = "string_to_translate_2"
case StringId3 = "string_to_translate_3"
var localized : String {
return NSLocalizedString(self.rawValue, tableName: nil, bundle: NSBundle.mainBundle(), value: "", comment: "")
}
}
With this enum I can have the localized string with this command:
let myString = Localizations.StringId1.localized
But when you have to put lots of strings the .localized it's like redundant as you already have Localizations before.
So what I'm looking for is if I can do something like this:
let myString = Localizations.StringId1
And myString would be something like "Press Button to Continue"
I have managed to do something working, but not in all cases.
Found in this link: https://appventure.me/2015/10/17/advanced-practical-enum-examples/
On the 'Advanced Enum Usage Protocols' Step it suggests a modification like the following would get what I want:
protocol CustomStringConvertible {
var description: String { get }
}
enum Trade: CustomStringConvertible {
case Buy, Sell
var description: String {
switch self {
case Buy: return "We're buying something"
case Sell: return "We're selling something"
}
}
}
let action = Trade.Buy
print("this action is \(action)")
// prints: this action is We're buying something
My modifications are theses ones:
protocol CustomEnumString {
var localized: String { get }
}
enum Localizations : String, CustomEnumString
{
case StringId1 = "string_to_translate_1"
case StringId2 = "string_to_translate_2"
case StringId3 = "string_to_translate_3"
var localized : String {
return NSLocalizedString(self.rawValue, tableName: nil, bundle: NSBundle.mainBundle(), value: "", comment: "")
}
}
But when printing it if shows me the enum literal, and when passing to a function the compilers tells me it's invalid:
let localizedString = Localizations.StringId1
print("localization: \(localizedString)")
// prints: "localization: StringId1"
// note: AlertStrings is an struct with two strings
// this fails to compile saying that cannot convert value of type 'Localizations' to expected argument type 'String'
let alertStrings = AlertStrings(title: Localizations.StringId1, message: Localizations.StringId2)
// this one works, but it's not the purpose I had in mind
let alertStrings = AlertStrings(title: Localizations.StringId1.localized, message: Localizations.StringId2.localized)
So... in short, I would like to be able to do this:
let localizedString = Localizations.StringId1
print("localization: \(localizedString)")
// prints: "localization: Press Button To Continue"
let alertStrings = AlertStrings(title: Localizations.StringId1, message: Localizations.StringId2)
But in the enum I only would like to specify literals once, not first on case's and later inside a switch.
Thanks in advance!
phelgo, A member of NSBarcelona just told me about this, and works perfectly!
enum Localizations {
static let StringId1 = NSLocalizedString("string_to_translate_1", comment: "")
}
let myString = Localizations.StringId1
It may look unfamiliar to have an enum with no cases, but we get to
keep all of its safety (and code completion), while still preventing
Localizations from being instantiated by mistake (if it was a
struct)
Conform your enum to CustomStringConvertible and rename localized to description:
enum Localizations: String, CustomStringConvertible
{
case StringId1 = "string_to_translate_1"
case StringId2 = "string_to_translate_2"
case StringId3 = "string_to_translate_3"
var description : String {
return NSLocalizedString(self.rawValue, tableName: nil, bundle: NSBundle.mainBundle(), value: "", comment: "")
}
}
let alertStrings = AlertStrings(title: Localizations.StringId1.description, message: Localizations.StringId2.description)

Resources